Exemple #1
0
class Window(QWidget):
    def __init__(self, *args, **kwargs):
        super(Window, self).__init__(*args, **kwargs)
        layout = QVBoxLayout(self)
        self.progressBar = QProgressBar(self)
        self.progressBar.setRange(0, 100)
        layout.addWidget(self.progressBar)
        layout.addWidget(QPushButton('开启线程', self, clicked=self.onStart))

        # 当前线程id
        print('main id', QThread.currentThread())

        # 启动线程更新进度条值
        self._thread = QThread(self)
        self._worker = Worker()
        self._worker.moveToThread(self._thread)  # 移动到线程中执行
        self._thread.finished.connect(self._worker.deleteLater)
        self._thread.started.connect(self._worker.run)
        self._worker.valueChanged.connect(self.progressBar.setValue)

    def onStart(self):
        if not self._thread.isRunning():
            print('main id', QThread.currentThread())
            self._thread.start()  # 启动线程

    def closeEvent(self, event):
        if self._thread.isRunning():
            self._thread.requestInterruption()
            self._thread.quit()
            self._thread.wait()
            # 强制
            # self._thread.terminate()
        self._thread.deleteLater()
        super(Window, self).closeEvent(event)
Exemple #2
0
class Windows(QDialog, mainUI.Ui_Dialog):
    isRunning = False

    def __init__(self, parent=None):
        super(Windows, self).__init__(parent)
        self.selectedDict = None
        self.currentConfig = dict()
        self.localWords = []
        self.selectedGroups = []

        self.workerThread = QThread(self)
        self.workerThread.start()
        self.updateCheckThead = QThread(self)
        self.updateCheckThead.start()
        self.audioDownloadThread = QThread(self)

        self.updateCheckWork = None
        self.loginWorker = None
        self.queryWorker = None
        self.pullWorker = None
        self.audioDownloadWorker = None

        self.setupUi(self)
        self.setWindowTitle(MODEL_NAME)
        self.setupLogger()
        self.initCore()
        self.checkUpdate()
        # self.__dev() # 以备调试时使用

    def __dev(self):
        def on_dev():
            logger.debug('whatever')

        self.devBtn = QPushButton('Magic Button', self.mainTab)
        self.devBtn.clicked.connect(on_dev)
        self.gridLayout_4.addWidget(self.devBtn, 4, 3, 1, 1)

    def closeEvent(self, event):
        # 插件关闭时退出所有线程
        if self.workerThread.isRunning():
            self.workerThread.requestInterruption()
            self.workerThread.quit()
            self.workerThread.wait()

        if self.updateCheckThead.isRunning():
            self.updateCheckThead.quit()
            self.updateCheckThead.wait()

        if self.audioDownloadThread.isRunning():
            self.audioDownloadThread.requestInterruption()
            self.workerThread.quit()
            self.workerThread.wait()

        event.accept()

    def setupLogger(self):
        """初始化 Logger """
        def onDestroyed():
            logger.removeHandler(QtHandler)

        # 防止 debug 信息写入stdout/stderr 导致 Anki 崩溃
        logging.basicConfig(
            handlers=[logging.FileHandler('dict2anki.log', 'w', 'utf-8')],
            level=logging.DEBUG,
            format='[%(asctime)s][%(levelname)8s] -- %(message)s - (%(name)s)')

        logTextBox = QPlainTextEdit(self)
        logTextBox.setLineWrapMode(QPlainTextEdit.NoWrap)
        layout = QVBoxLayout()
        layout.addWidget(logTextBox)
        self.logTab.setLayout(layout)
        QtHandler = Handler(self)
        logger.addHandler(QtHandler)
        QtHandler.newRecord.connect(logTextBox.appendPlainText)

        # 日志Widget销毁时移除 Handlers
        logTextBox.destroyed.connect(onDestroyed)

    def setupGUIByConfig(self):
        config = mw.addonManager.getConfig(__name__)
        self.deckComboBox.setCurrentText(config['deck'])
        self.dictionaryComboBox.setCurrentIndex(config['selectedDict'])
        self.apiComboBox.setCurrentIndex(config['selectedApi'])
        self.usernameLineEdit.setText(
            config['credential'][config['selectedDict']]['username'])
        self.passwordLineEdit.setText(
            config['credential'][config['selectedDict']]['password'])
        self.cookieLineEdit.setText(
            config['credential'][config['selectedDict']]['cookie'])
        self.definitionCheckBox.setChecked(config['definition'])
        self.imageCheckBox.setChecked(config['image'])
        self.sentenceCheckBox.setChecked(config['sentence'])
        self.phraseCheckBox.setChecked(config['phrase'])
        self.AmEPhoneticCheckBox.setChecked(config['AmEPhonetic'])
        self.BrEPhoneticCheckBox.setChecked(config['BrEPhonetic'])
        self.BrEPronRadioButton.setChecked(config['BrEPron'])
        self.AmEPronRadioButton.setChecked(config['AmEPron'])
        self.noPronRadioButton.setChecked(config['noPron'])
        self.selectedGroups = config['selectedGroup']

    def initCore(self):
        self.dictionaryComboBox.addItems([d.name for d in dictionaries])
        self.apiComboBox.addItems([d.name for d in apis])
        self.deckComboBox.addItems(getDeckList())
        self.setupGUIByConfig()

    def getAndSaveCurrentConfig(self) -> dict:
        """获取当前设置"""
        currentConfig = dict(
            selectedDict=self.dictionaryComboBox.currentIndex(),
            selectedApi=self.apiComboBox.currentIndex(),
            selectedGroup=self.selectedGroups,
            deck=self.deckComboBox.currentText(),
            username=self.usernameLineEdit.text(),
            password=Mask(self.passwordLineEdit.text()),
            cookie=Mask(self.cookieLineEdit.text()),
            definition=self.definitionCheckBox.isChecked(),
            sentence=self.sentenceCheckBox.isChecked(),
            image=self.imageCheckBox.isChecked(),
            phrase=self.phraseCheckBox.isChecked(),
            AmEPhonetic=self.AmEPhoneticCheckBox.isChecked(),
            BrEPhonetic=self.BrEPhoneticCheckBox.isChecked(),
            BrEPron=self.BrEPronRadioButton.isChecked(),
            AmEPron=self.AmEPronRadioButton.isChecked(),
            noPron=self.noPronRadioButton.isChecked(),
        )
        logger.info(f'当前设置:{currentConfig}')
        self._saveConfig(currentConfig)
        self.currentConfig = currentConfig
        return currentConfig

    @staticmethod
    def _saveConfig(config):
        _config = deepcopy(config)
        _config['credential'] = [dict(username='', password='', cookie='')
                                 ] * len(dictionaries)
        _config['credential'][_config['selectedDict']] = dict(
            username=_config.pop('username'),
            password=str(_config.pop('password')),
            cookie=str(_config.pop('cookie')))
        maskedConfig = deepcopy(_config)
        maskedCredential = [
            dict(username=c['username'],
                 password=Mask(c['password']),
                 cookie=Mask(c['cookie'])) for c in maskedConfig['credential']
        ]
        maskedConfig['credential'] = maskedCredential
        logger.info(f'保存配置项:{maskedConfig}')
        mw.addonManager.writeConfig(__name__, _config)

    def checkUpdate(self):
        @pyqtSlot(str, str)
        def on_haveNewVersion(version, changeLog):
            if askUser(f'有新版本:{version}是否更新?\n\n{changeLog.strip()}'):
                openLink(RELEASE_URL)

        self.updateCheckWork = VersionCheckWorker()
        self.updateCheckWork.moveToThread(self.updateCheckThead)
        self.updateCheckWork.haveNewVersion.connect(on_haveNewVersion)
        self.updateCheckWork.finished.connect(self.updateCheckThead.quit)
        self.updateCheckWork.start.connect(self.updateCheckWork.run)
        self.updateCheckWork.start.emit()

    @pyqtSlot(int)
    def on_dictionaryComboBox_currentIndexChanged(self, index):
        """词典候选框改变事件"""
        self.currentDictionaryLabel.setText(
            f'当前选择词典: {self.dictionaryComboBox.currentText()}')
        config = mw.addonManager.getConfig(__name__)
        self.usernameLineEdit.setText(config['credential'][index]['username'])
        self.passwordLineEdit.setText(config['credential'][index]['password'])
        self.cookieLineEdit.setText(config['credential'][index]['cookie'])

    @pyqtSlot()
    def on_pullRemoteWordsBtn_clicked(self):
        """获取单词按钮点击事件"""
        if not self.deckComboBox.currentText():
            showInfo('\n请选择或输入要同步的牌组')
            return

        self.mainTab.setEnabled(False)
        self.progressBar.setValue(0)
        self.progressBar.setMaximum(0)

        currentConfig = self.getAndSaveCurrentConfig()
        self.selectedDict = dictionaries[currentConfig['selectedDict']]()

        # 登陆线程
        self.loginWorker = LoginWorker(
            self.selectedDict.login, str(currentConfig['username']),
            str(currentConfig['password']),
            json.loads(str(currentConfig['cookie']) or '{}'))
        self.loginWorker.moveToThread(self.workerThread)
        self.loginWorker.logSuccess.connect(self.onLogSuccess)
        self.loginWorker.start.connect(self.loginWorker.run)
        self.loginWorker.logFailed.connect(self.onLoginFailed)
        self.loginWorker.start.emit()

    @pyqtSlot()
    def onLoginFailed(self):
        showCritical('登录失败!')
        self.tabWidget.setCurrentIndex(1)
        self.progressBar.setValue(0)
        self.progressBar.setMaximum(1)
        self.mainTab.setEnabled(True)
        self.cookieLineEdit.clear()

    @pyqtSlot(str)
    def onLogSuccess(self, cookie):
        self.cookieLineEdit.setText(cookie)
        self.selectedDict.getGroups()

        container = QDialog(self)
        group = wordGroup.Ui_Dialog()
        group.setupUi(container)

        for groupName in [
                str(group_name) for group_name, _ in self.selectedDict.groups
        ]:
            item = QListWidgetItem()
            item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
                          | Qt.ItemIsEnabled)
            item.setText(groupName)
            item.setCheckState(Qt.Unchecked)
            group.wordGroupListWidget.addItem(item)

        # 恢复上次选择的单词本分组
        if self.selectedGroups:
            for groupName in self.selectedGroups[
                    self.currentConfig['selectedDict']]:
                items = group.wordGroupListWidget.findItems(
                    groupName, Qt.MatchExactly)
                for item in items:
                    item.setCheckState(Qt.Checked)
        else:
            self.selectedGroups = [list()] * len(dictionaries)

        def onAccepted():
            """选择单词本弹窗确定事件"""
            # 清空 listWidget
            self.newWordListWidget.clear()
            self.needDeleteWordListWidget.clear()
            self.mainTab.setEnabled(False)

            selectedGroups = [
                group.wordGroupListWidget.item(index).text()
                for index in range(group.wordGroupListWidget.count())
                if group.wordGroupListWidget.item(index).checkState() ==
                Qt.Checked
            ]
            # 保存分组记录
            self.selectedGroups[
                self.currentConfig['selectedDict']] = selectedGroups
            self.progressBar.setValue(0)
            self.progressBar.setMaximum(1)
            logger.info(f'选中单词本{selectedGroups}')
            self.getRemoteWordList(selectedGroups)

        def onRejected():
            """选择单词本弹窗取消事件"""
            self.progressBar.setValue(0)
            self.progressBar.setMaximum(1)
            self.mainTab.setEnabled(True)

        group.buttonBox.accepted.connect(onAccepted)
        group.buttonBox.rejected.connect(onRejected)
        container.exec()

    def getRemoteWordList(self, selected_groups: [str]):
        """根据选中到分组获取分组下到全部单词,并添加到 newWordListWidget"""
        group_map = dict(self.selectedDict.groups)
        self.localWords = getWordsByDeck(self.deckComboBox.currentText())

        # 启动单词获取线程
        self.pullWorker = RemoteWordFetchingWorker(self.selectedDict, [(
            group_name,
            group_map[group_name],
        ) for group_name in selected_groups])
        self.pullWorker.moveToThread(self.workerThread)
        self.pullWorker.start.connect(self.pullWorker.run)
        self.pullWorker.tick.connect(
            lambda: self.progressBar.setValue(self.progressBar.value() + 1))
        self.pullWorker.setProgress.connect(self.progressBar.setMaximum)
        self.pullWorker.doneThisGroup.connect(self.insertWordToListWidget)
        self.pullWorker.done.connect(self.on_allPullWork_done)
        self.pullWorker.start.emit()

    @pyqtSlot(list)
    def insertWordToListWidget(self, words: list):
        """一个分组获取完毕事件"""
        for word in words:
            wordItem = QListWidgetItem(word, self.newWordListWidget)
            wordItem.setData(Qt.UserRole, None)
        self.newWordListWidget.clearSelection()

    @pyqtSlot()
    def on_allPullWork_done(self):
        """全部分组获取完毕事件"""
        localWordList = set(getWordsByDeck(self.deckComboBox.currentText()))
        remoteWordList = set([
            self.newWordListWidget.item(row).text()
            for row in range(self.newWordListWidget.count())
        ])

        newWords = remoteWordList - localWordList  # 新单词
        needToDeleteWords = localWordList - remoteWordList  # 需要删除的单词
        logger.info(f'本地: {localWordList}')
        logger.info(f'远程: {remoteWordList}')
        logger.info(f'待查: {newWords}')
        logger.info(f'待删: {needToDeleteWords}')
        waitIcon = QIcon(':/icons/wait.png')
        delIcon = QIcon(':/icons/delete.png')
        self.newWordListWidget.clear()
        self.needDeleteWordListWidget.clear()

        for word in needToDeleteWords:
            item = QListWidgetItem(word)
            item.setCheckState(Qt.Checked)
            item.setIcon(delIcon)
            self.needDeleteWordListWidget.addItem(item)

        for word in newWords:
            item = QListWidgetItem(word)
            item.setIcon(waitIcon)
            self.newWordListWidget.addItem(item)
        self.newWordListWidget.clearSelection()

        self.dictionaryComboBox.setEnabled(True)
        self.apiComboBox.setEnabled(True)
        self.deckComboBox.setEnabled(True)
        self.pullRemoteWordsBtn.setEnabled(True)
        self.queryBtn.setEnabled(self.newWordListWidget.count() > 0)
        self.syncBtn.setEnabled(self.newWordListWidget.count() == 0
                                and self.needDeleteWordListWidget.count() > 0)
        if self.needDeleteWordListWidget.count(
        ) == self.newWordListWidget.count() == 0:
            logger.info('无需同步')
            tooltip('无需同步')
        self.mainTab.setEnabled(True)

    @pyqtSlot()
    def on_queryBtn_clicked(self):
        logger.info('点击查询按钮')
        currentConfig = self.getAndSaveCurrentConfig()
        self.queryBtn.setEnabled(False)
        self.pullRemoteWordsBtn.setEnabled(False)
        self.syncBtn.setEnabled(False)

        wordList = []
        selectedWords = self.newWordListWidget.selectedItems()
        if selectedWords:
            # 如果选中单词则只查询选中的单词
            for wordItem in selectedWords:
                wordBundle = dict()
                row = self.newWordListWidget.row(wordItem)
                wordBundle['term'] = wordItem.text()
                for configName in BASIC_OPTION + EXTRA_OPTION:
                    wordBundle[configName] = currentConfig[configName]
                    wordBundle['row'] = row
                wordList.append(wordBundle)
        else:  # 没有选择则查询全部
            for row in range(self.newWordListWidget.count()):
                wordBundle = dict()
                wordItem = self.newWordListWidget.item(row)
                wordBundle['term'] = wordItem.text()
                for configName in BASIC_OPTION + EXTRA_OPTION:
                    wordBundle[configName] = currentConfig[configName]
                    wordBundle['row'] = row
                wordList.append(wordBundle)

        logger.info(f'待查询单词{wordList}')
        # 查询线程
        self.progressBar.setMaximum(len(wordList))
        self.queryWorker = QueryWorker(wordList,
                                       apis[currentConfig['selectedApi']])
        self.queryWorker.moveToThread(self.workerThread)
        self.queryWorker.thisRowDone.connect(self.on_thisRowDone)
        self.queryWorker.thisRowFailed.connect(self.on_thisRowFailed)
        self.queryWorker.tick.connect(
            lambda: self.progressBar.setValue(self.progressBar.value() + 1))
        self.queryWorker.allQueryDone.connect(self.on_allQueryDone)
        self.queryWorker.start.connect(self.queryWorker.run)
        self.queryWorker.start.emit()

    @pyqtSlot(int, dict)
    def on_thisRowDone(self, row, result):
        """该行单词查询完毕"""
        doneIcon = QIcon(':/icons/done.png')
        wordItem = self.newWordListWidget.item(row)
        wordItem.setIcon(doneIcon)
        wordItem.setData(Qt.UserRole, result)

    @pyqtSlot(int)
    def on_thisRowFailed(self, row):
        failedIcon = QIcon(':/icons/failed.png')
        failedWordItem = self.newWordListWidget.item(row)
        failedWordItem.setIcon(failedIcon)

    @pyqtSlot()
    def on_allQueryDone(self):
        failed = []

        for i in range(self.newWordListWidget.count()):
            wordItem = self.newWordListWidget.item(i)
            if not wordItem.data(Qt.UserRole):
                failed.append(wordItem.text())

        if failed:
            logger.warning(f'查询失败或未查询:{failed}')

        self.pullRemoteWordsBtn.setEnabled(True)
        self.queryBtn.setEnabled(True)
        self.syncBtn.setEnabled(True)

    @pyqtSlot()
    def on_syncBtn_clicked(self):

        failedGenerator = (self.newWordListWidget.item(row).data(Qt.UserRole)
                           is None
                           for row in range(self.newWordListWidget.count()))
        if any(failedGenerator):
            if not askUser(
                    '存在未查询或失败的单词,确定要加入单词本吗?\n 你可以选择失败的单词点击 "查询按钮" 来重试。'):
                return

        currentConfig = self.getAndSaveCurrentConfig()
        model = getOrCreateModel(MODEL_NAME)
        getOrCreateModelCardTemplate(model, 'default')
        deck = getOrCreateDeck(self.deckComboBox.currentText())

        logger.info('同步点击')
        audiosDownloadTasks = []
        newWordCount = self.newWordListWidget.count()

        # 判断是否需要下载发音
        if currentConfig['noPron']:
            logger.info('不下载发音')
            whichPron = None
        else:
            whichPron = 'AmEPron' if self.AmEPronRadioButton.isChecked(
            ) else 'BrEPron'
            logger.info(f'下载发音{whichPron}')

        added = 0
        for row in range(newWordCount):
            wordItem = self.newWordListWidget.item(row)
            wordItemData = wordItem.data(Qt.UserRole)
            if wordItemData:
                addNoteToDeck(deck, model, currentConfig, wordItemData)
                added += 1
                # 添加发音任务
                if whichPron and wordItemData.get(whichPron):
                    audiosDownloadTasks.append((
                        f"{whichPron}_{wordItemData['term']}.mp3",
                        wordItemData[whichPron],
                    ))
        mw.reset()

        logger.info(f'发音下载任务:{audiosDownloadTasks}')

        if audiosDownloadTasks:
            self.syncBtn.setEnabled(False)
            self.progressBar.setValue(0)
            self.progressBar.setMaximum(len(audiosDownloadTasks))
            if self.audioDownloadThread is not None:
                self.audioDownloadThread.requestInterruption()
                self.audioDownloadThread.quit()
                self.audioDownloadThread.wait()

            self.audioDownloadThread = QThread(self)
            self.audioDownloadThread.start()
            self.audioDownloadWorker = AudioDownloadWorker(audiosDownloadTasks)
            self.audioDownloadWorker.moveToThread(self.audioDownloadThread)
            self.audioDownloadWorker.tick.connect(
                lambda: self.progressBar.setValue(self.progressBar.value() + 1
                                                  ))
            self.audioDownloadWorker.start.connect(
                self.audioDownloadWorker.run)
            self.audioDownloadWorker.done.connect(lambda: tooltip(f'发音下载完成'))
            self.audioDownloadWorker.done.connect(
                self.audioDownloadThread.quit)
            self.audioDownloadWorker.start.emit()

        self.newWordListWidget.clear()

        needToDeleteWordItems = [
            self.needDeleteWordListWidget.item(row)
            for row in range(self.needDeleteWordListWidget.count()) if
            self.needDeleteWordListWidget.item(row).checkState() == Qt.Checked
        ]
        needToDeleteWords = [i.text() for i in needToDeleteWordItems]

        deleted = 0

        if needToDeleteWords and askUser(
                f'确定要删除这些单词吗:{needToDeleteWords[:3]}...({len(needToDeleteWords)}个)',
                title='Dict2Anki',
                parent=self):
            needToDeleteWordNoteIds = getNotes(needToDeleteWords,
                                               currentConfig['deck'])
            mw.col.remNotes(needToDeleteWordNoteIds)
            deleted += 1
            mw.col.reset()
            mw.reset()
            for item in needToDeleteWordItems:
                self.needDeleteWordListWidget.takeItem(
                    self.needDeleteWordListWidget.row(item))
            logger.info('删除完成')
        logger.info('完成')

        if not audiosDownloadTasks:
            tooltip(f'添加{added}个笔记\n删除{deleted}个笔记')
Exemple #3
0
class PointLayerImport:
    def __init__(self, parent, target_layout):
        self.parent = parent
        self.canvas = iface.mapCanvas()
        self.ui = UI(target_layout)
        self.__init_ui()

    def search(self):
        layer = self.source_layer

        target_layer_name = self.ui.text_edit_target_layer_name.text()

        selected_only = bool(self.ui.checkbox_selected_only.checkState())
        selected_field_names = self.ui.combobox_fields_select.checkedItems()
        fields_to_copy = [
            field for field in layer.dataProvider().fields()
            if field.name() in selected_field_names
        ]

        features_iterator = layer.getSelectedFeatures(
        ) if selected_only else layer.getFeatures()
        count = sum(1 for i in features_iterator)
        self.source_features_count = count

        self.__cleanup_before_search()

        self.worker = PointLayerImportWorker(layer, selected_only,
                                             target_layer_name, fields_to_copy)
        self.thread = QThread()
        self.worker.moveToThread(self.thread)
        self.worker.progressed.connect(self.__progressed)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.__handle_finished)
        self.worker.interrupted.connect(self.__handle_interrupted)
        self.worker.interrupted.connect(self.thread.quit)
        self.thread.started.connect(self.worker.search)

        self.thread.start()

        self.ui.label_status.setText(f"Trwa wyszukiwanie {count} obiektów...")

    def __init_ui(self):
        self.ui.button_start.clicked.connect(self.search)
        self.ui.button_cancel.clicked.connect(self.__stop)
        self.__on_layer_changed(self.ui.layer_select.currentLayer())
        self.ui.layer_select.layerChanged.connect(self.__on_layer_changed)
        self.ui.label_status.setText("")
        self.ui.label_found_count.setText("")
        self.ui.label_not_found_count.setText("")

    def __on_layer_changed(self, layer):
        self.ui.combobox_fields_select.clear()
        self.ui.button_start.setEnabled(False)
        if layer:
            if layer.dataProvider().featureCount() == 0:
                iface.messageBar().pushCritical(
                    "Wtyczka ULDK",
                    f"Warstwa <b>{layer.sourceName()} nie zawiera żadnych obiektów.</b>"
                )
                return
            self.source_layer = layer
            layer.selectionChanged.connect(
                self.__on_layer_features_selection_changed)
            layer.updatedFields.connect(self.__fill_combobox_fields_select)
            self.ui.button_start.setEnabled(True)
            current_layer_name = layer.sourceName()
            suggested_target_layer_name = f"{current_layer_name} - Działki ULDK"
            fields = layer.dataProvider().fields()
            self.ui.text_edit_target_layer_name.setText(
                suggested_target_layer_name)
            self.ui.combobox_fields_select.addItems(
                map(lambda x: x.name(), fields))
            self.ui.button_start.setEnabled(True)
        else:
            self.source_layer = None
            self.ui.text_edit_target_layer_name.setText("")
            self.ui.checkbox_selected_only.setText(
                "Tylko zaznaczone obiekty [0]")

    def __on_layer_features_selection_changed(self, selected_features):
        if not self.source_layer:
            selected_features = []
        self.ui.checkbox_selected_only.setText(
            f"Tylko zaznaczone obiekty [{len(selected_features)}]")

    def __fill_combobox_fields_select(self):
        self.ui.combobox_fields_select.clear()
        fields = self.source_layer.dataProvider().fields()
        self.ui.combobox_fields_select.addItems(map(lambda x: x.name(),
                                                    fields))

    def __progressed(self, found, omitted_count, saved):
        if saved:
            self.saved_count += 1
        if found:
            self.found_count += 1
        else:
            self.not_found_count += 1
        self.omitted_count += omitted_count
        progressed_count = self.found_count + self.not_found_count
        self.ui.progress_bar.setValue(progressed_count /
                                      self.source_features_count * 100)
        self.ui.label_status.setText(
            f"Przetworzono {progressed_count} z {self.source_features_count} obiektów"
        )
        found_message = f"Znaleziono: {self.saved_count}"
        if self.omitted_count:
            found_message += f" (pominięto: {self.omitted_count})"
        self.ui.label_found_count.setText(found_message)
        self.ui.label_not_found_count.setText(
            f"Nie znaleziono: {self.not_found_count}")

    def __handle_finished(self, layer_found, layer_not_found):
        self.__cleanup_after_search()

        if layer_found.dataProvider().featureCount():
            QgsProject.instance().addMapLayer(layer_found)
        if layer_not_found.dataProvider().featureCount():
            QgsProject.instance().addMapLayer(layer_not_found)

        iface.messageBar().pushWidget(
            QgsMessageBarItem(
                "Wtyczka ULDK",
                f"Wyszukiwarka działek z warstwy: zakończono wyszukiwanie. Zapisano {self.saved_count} {get_obiekty_form(self.saved_count)} do warstwy <b>{self.ui.text_edit_target_layer_name.text()}</b>"
            ))

    def __handle_interrupted(self, layer_found, layer_not_found):
        self.__cleanup_after_search()

        if layer_found.dataProvider().featureCount():
            QgsProject.instance().addMapLayer(layer_found)
        if layer_not_found.dataProvider().featureCount():
            QgsProject.instance().addMapLayer(layer_not_found)

    def __cleanup_after_search(self):
        self.__set_controls_enabled(True)
        self.ui.button_cancel.setText("Anuluj")
        self.ui.button_cancel.setEnabled(False)
        self.ui.progress_bar.setValue(0)

    def __cleanup_before_search(self):
        self.__set_controls_enabled(False)
        self.ui.button_cancel.setEnabled(True)
        self.ui.label_status.setText("")
        self.ui.label_found_count.setText("")
        self.ui.label_not_found_count.setText("")

        self.found_count = 0
        self.not_found_count = 0
        self.omitted_count = 0
        self.saved_count = 0

    def __set_controls_enabled(self, enabled):
        self.ui.text_edit_target_layer_name.setEnabled(enabled)
        self.ui.button_start.setEnabled(enabled)
        self.ui.layer_select.setEnabled(enabled)

    def __stop(self):
        self.thread.requestInterruption()
        self.ui.button_cancel.setEnabled(False)
        self.ui.button_cancel.setText("Przerywanie...")
class CSVImport:
    def __init__(self, parent, target_layout, uldk_api,
                 result_collector_factory, layer_factory):
        self.parent = parent
        self.iface = parent.iface
        self.ui = UI(parent.dockwidget, target_layout)

        self.uldk_api = uldk_api
        self.result_collector_factory = result_collector_factory
        self.layer_factory = layer_factory

        self.file_path = None

        self.__init_ui()

        self.uldk_search = uldk_api.ULDKSearchParcel(
            "dzialka", ("geom_wkt", "wojewodztwo", "powiat", "gmina", "obreb",
                        "numer", "teryt"))

    def start_import(self):
        self.__cleanup_before_search()

        layer_name = self.ui.text_edit_layer_name.text()
        layer = self.layer_factory(name=layer_name,
                                   custom_properties={"ULDK": layer_name})

        self.result_collector = self.result_collector_factory(
            self.parent, layer)
        self.uldk_received_rows = []

        teryts = []
        with open(self.file_path) as f:
            csv_read = csv.DictReader(f)
            teryt_column = self.ui.combobox_teryt_column.currentText()
            for row in csv_read:
                teryt = row[teryt_column]
                teryts.append(teryt)
        self.csv_rows_count = len(teryts)

        self.worker = self.uldk_api.ULDKSearchWorker(self.uldk_search, teryts)
        self.thread = QThread()
        self.worker.moveToThread(self.thread)
        self.worker.found.connect(self.__handle_found)
        self.worker.found.connect(self.__progressed)
        self.worker.not_found.connect(self.__handle_not_found)
        self.worker.not_found.connect(self.__progressed)
        self.worker.found.connect(self.__progressed)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.__handle_finished)
        self.worker.interrupted.connect(self.__handle_interrupted)
        self.worker.interrupted.connect(self.thread.quit)
        self.thread.started.connect(self.worker.search)

        self.thread.start()

        self.ui.label_status.setText(
            f"Trwa wyszukiwanie {self.csv_rows_count} obiektów...")

    def __init_ui(self):
        self.ui.button_start.clicked.connect(self.start_import)
        self.ui.label_status.setText("")
        self.ui.label_found_count.setText("")
        self.ui.label_not_found_count.setText("")
        self.ui.button_cancel.clicked.connect(self.__stop)
        self.ui.file_select.fileChanged.connect(self.__on_file_changed)
        self.__init_table()

    def __init_table(self):
        table = self.ui.table_errors
        table.setEditTriggers(QTableWidget.NoEditTriggers)
        table.setColumnCount(2)
        table.setHorizontalHeaderLabels(("TERYT", "Treść błędu"))
        header = table.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.Interactive)
        header.setSectionResizeMode(1, QHeaderView.Stretch)
        teryt_column_size = table.width() / 3
        header.resizeSection(0, 200)

    def __on_file_changed(self, path):
        suggested_target_layer_name = ""
        if os.path.exists(path):
            self.ui.button_start.setEnabled(True)
            self.file_path = path
            self.__fill_column_select()
            suggested_target_layer_name = os.path.splitext(
                os.path.relpath(path))[0]
        else:
            self.file_path = None
            self.ui.combobox_teryt_column.clear()
        self.ui.text_edit_layer_name.setText(suggested_target_layer_name)

    def __fill_column_select(self):
        self.ui.combobox_teryt_column.clear()
        with open(self.file_path) as f:
            csv_read = csv.DictReader(f)
            columns = csv_read.fieldnames
        self.ui.combobox_teryt_column.addItems(columns)

    def __handle_found(self, uldk_response_rows):
        for row in uldk_response_rows:
            self.uldk_received_rows.append(row)
            self.found_count += 1

    def __handle_not_found(self, teryt, exception):
        row = self.ui.table_errors.rowCount()
        self.ui.table_errors.insertRow(row)
        self.ui.table_errors.setItem(row, 0, QTableWidgetItem(teryt))
        self.ui.table_errors.setItem(row, 1, QTableWidgetItem(str(exception)))
        self.not_found_count += 1

    def __progressed(self):
        found_count = self.found_count
        not_found_count = self.not_found_count
        progressed_count = found_count + not_found_count
        self.ui.progress_bar.setValue(progressed_count / self.csv_rows_count *
                                      100)
        self.ui.label_status.setText("Przetworzono {} z {} obiektów".format(
            progressed_count, self.csv_rows_count))
        self.ui.label_found_count.setText("Znaleziono: {}".format(found_count))
        self.ui.label_not_found_count.setText(
            "Nie znaleziono: {}".format(not_found_count))

    def __handle_finished(self):
        self.__collect_received_rows()
        form = "obiekt"
        found_count = self.found_count
        if found_count == 1:
            pass
        elif 2 <= found_count <= 4:
            form = "obiekty"
        elif 5 <= found_count <= 15:
            form = "obiektów"
        else:
            units = found_count % 10
            if units in (2, 3, 4):
                form = "obiekty"

        self.iface.messageBar().pushWidget(
            QgsMessageBarItem(
                "Wtyczka ULDK",
                f"Import CSV: zakończono wyszukiwanie. Zapisano {found_count} {form} do warstwy <b>{self.ui.text_edit_layer_name.text()}</b>"
            ))
        self.__cleanup_after_search()

    def __handle_interrupted(self):
        self.__collect_received_rows()
        self.__cleanup_after_search()

    def __collect_received_rows(self):
        if self.uldk_received_rows:
            self.result_collector.update(self.uldk_received_rows)

    def __cleanup_after_search(self):
        self.__set_controls_enabled(True)
        self.ui.button_cancel.setText("Anuluj")
        self.ui.button_cancel.setEnabled(False)
        self.ui.progress_bar.setValue(0)

    def __cleanup_before_search(self):
        self.__set_controls_enabled(False)
        self.ui.button_cancel.setEnabled(True)
        self.ui.table_errors.setRowCount(0)
        self.ui.label_status.setText("")
        self.ui.label_found_count.setText("")
        self.ui.label_not_found_count.setText("")

        self.found_count = 0
        self.not_found_count = 0

    def __set_controls_enabled(self, enabled):
        self.ui.text_edit_layer_name.setEnabled(enabled)
        self.ui.button_start.setEnabled(enabled)
        self.ui.file_select.setEnabled(enabled)
        self.ui.combobox_teryt_column.setEnabled(enabled)

    def __stop(self):
        self.thread.requestInterruption()
        self.ui.button_cancel.setEnabled(False)
        self.ui.button_cancel.setText("Przerywanie...")
Exemple #5
0
class TVLinker(QWidget):
    def __init__(self, settings: QSettings, parent=None):
        super(TVLinker, self).__init__(parent)
        self.firstrun = True
        self.rows, self.cols = 0, 0
        self.parent = parent
        self.settings = settings
        self.taskbar = TaskbarProgress(self)
        self.init_styles()
        self.init_settings()
        self.init_icons()
        if sys.platform.startswith('linux'):
            notify.init(qApp.applicationName())
        layout = QVBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(15, 15, 15, 0)
        form_groupbox = QGroupBox(self, objectName='mainForm')
        form_groupbox.setLayout(self.init_form())
        self.table = TVLinkerTable(0, 4, self)
        self.table.doubleClicked.connect(self.show_hosters)
        layout.addWidget(form_groupbox)
        layout.addWidget(self.table)
        layout.addLayout(self.init_metabar())
        self.setLayout(layout)
        qApp.setWindowIcon(self.icon_app)
        self.resize(FixedSettings.windowSize)
        self.show()
        self.start_scraping()
        self.firstrun = False

    class ProcError(Enum):
        FAILED_TO_START = 0
        CRASHED = 1
        TIMED_OUT = 2
        READ_ERROR = 3
        WRITE_ERROR = 4
        UNKNOWN_ERROR = 5

    class NotifyIcon(Enum):
        SUCCESS = ':assets/images/tvlinker.png'
        DEFAULT = ':assets/images/tvlinker.png'

    def init_threads(self, threadtype: str = 'scrape') -> None:
        if threadtype == 'scrape':
            if hasattr(self, 'scrapeThread'):
                if not sip.isdeleted(
                        self.scrapeThread) and self.scrapeThread.isRunning():
                    self.scrapeThread.terminate()
                    del self.scrapeWorker
                    del self.scrapeThread
            self.scrapeThread = QThread(self)
            self.scrapeWorker = ScrapeWorker(self.source_url, self.user_agent,
                                             self.dl_pagecount)
            self.scrapeThread.started.connect(self.show_progress)
            self.scrapeThread.started.connect(self.scrapeWorker.begin)
            self.scrapeWorker.moveToThread(self.scrapeThread)
            self.scrapeWorker.addRow.connect(self.add_row)
            self.scrapeWorker.workFinished.connect(self.scrape_finished)
            self.scrapeWorker.workFinished.connect(
                self.scrapeWorker.deleteLater, Qt.DirectConnection)
            self.scrapeWorker.workFinished.connect(self.scrapeThread.quit,
                                                   Qt.DirectConnection)
            self.scrapeThread.finished.connect(self.scrapeThread.deleteLater,
                                               Qt.DirectConnection)
        elif threadtype == 'unrestrict':
            pass

    @staticmethod
    def load_stylesheet(qssfile: str) -> None:
        if QFileInfo(qssfile).exists():
            qss = QFile(qssfile)
            qss.open(QFile.ReadOnly | QFile.Text)
            qApp.setStyleSheet(QTextStream(qss).readAll())

    def init_styles(self) -> None:
        if sys.platform == 'darwin':
            qss_stylesheet = self.get_path('%s_osx.qss' %
                                           qApp.applicationName().lower())
        else:
            qss_stylesheet = self.get_path('%s.qss' %
                                           qApp.applicationName().lower())
        TVLinker.load_stylesheet(qss_stylesheet)
        QFontDatabase.addApplicationFont(':assets/fonts/opensans.ttf')
        QFontDatabase.addApplicationFont(':assets/fonts/opensans-bold.ttf')
        QFontDatabase.addApplicationFont(':assets/fonts/opensans-semibold.ttf')
        qApp.setFont(QFont('Open Sans',
                           12 if sys.platform == 'darwin' else 10))

    def init_icons(self) -> None:
        self.icon_app = QIcon(
            self.get_path('images/%s.png' % qApp.applicationName().lower()))
        self.icon_faves_off = QIcon(':assets/images/star_off.png')
        self.icon_faves_on = QIcon(':assets/images/star_on.png')
        self.icon_refresh = QIcon(':assets/images/refresh.png')
        self.icon_menu = QIcon(':assets/images/menu.png')
        self.icon_settings = QIcon(':assets/images/cog.png')
        self.icon_updates = QIcon(':assets/images/cloud.png')

    def init_settings(self) -> None:
        self.provider = 'Scene-RLS'
        self.select_provider(0)
        self.user_agent = self.settings.value('user_agent')
        self.dl_pagecount = self.settings.value('dl_pagecount', 20, int)
        self.dl_pagelinks = FixedSettings.linksPerPage
        self.realdebrid_api_token = self.settings.value('realdebrid_apitoken')
        self.realdebrid_api_proxy = self.settings.value('realdebrid_apiproxy')
        self.download_manager = self.settings.value('download_manager')
        self.persepolis_cmd = self.settings.value('persepolis_cmd')
        self.pyload_host = self.settings.value('pyload_host')
        self.pyload_username = self.settings.value('pyload_username')
        self.pyload_password = self.settings.value('pyload_password')
        self.idm_exe_path = self.settings.value('idm_exe_path')
        self.kget_cmd = self.settings.value('kget_cmd')
        self.favorites = self.settings.value('favorites')

    def init_form(self) -> QHBoxLayout:
        self.search_field = QLineEdit(self,
                                      clearButtonEnabled=True,
                                      placeholderText='Enter search criteria')
        self.search_field.setObjectName('searchInput')
        self.search_field.setSizePolicy(QSizePolicy.Expanding,
                                        QSizePolicy.Fixed)
        self.search_field.setFocus()
        self.search_field.textChanged.connect(self.clear_filters)
        self.search_field.returnPressed.connect(
            lambda: self.filter_table(self.search_field.text()))
        self.favorites_button = QPushButton(parent=self,
                                            flat=True,
                                            cursor=Qt.PointingHandCursor,
                                            objectName='favesButton',
                                            toolTip='Favorites',
                                            checkable=True,
                                            toggled=self.filter_faves,
                                            checked=self.settings.value(
                                                'faves_filter', False, bool))
        self.refresh_button = QPushButton(parent=self,
                                          flat=True,
                                          cursor=Qt.PointingHandCursor,
                                          objectName='refreshButton',
                                          toolTip='Refresh',
                                          clicked=self.start_scraping)
        self.dlpages_field = QComboBox(self,
                                       toolTip='Pages',
                                       editable=False,
                                       cursor=Qt.PointingHandCursor)
        self.dlpages_field.addItems(
            ('10', '20', '30', '40', '50', '60', '70', '80'))
        self.dlpages_field.setCurrentIndex(
            self.dlpages_field.findText(str(self.dl_pagecount),
                                        Qt.MatchFixedString))
        self.dlpages_field.currentIndexChanged.connect(self.update_pagecount)
        self.settings_button = QPushButton(parent=self,
                                           flat=True,
                                           toolTip='Menu',
                                           objectName='menuButton',
                                           cursor=Qt.PointingHandCursor)
        self.settings_button.setMenu(self.settings_menu())
        layout = QHBoxLayout(spacing=10)
        # providerCombo = QComboBox(self, toolTip='Provider', editable=False, cursor=Qt.PointingHandCursor)
        # providerCombo.setObjectName('providercombo')
        # providerCombo.addItem(QIcon(':assets/images/provider-scenerls.png'), '')
        # providerCombo.addItem(QIcon(':assets/images/provider-tvrelease.png'), '')
        # providerCombo.setIconSize(QSize(146, 36))
        # providerCombo.setMinimumSize(QSize(160, 40))
        # providerCombo.setStyleSheet('''
        #     QComboBox, QComboBox::drop-down { background-color: transparent; border: none; margin: 5px; }
        #     QComboBox::down-arrow { image: url(:assets/images/down_arrow.png); }
        #     QComboBox QAbstractItemView { selection-background-color: #DDDDE4; }
        # ''')
        # providerCombo.currentIndexChanged.connect(self.select_provider)
        layout.addWidget(
            QLabel(pixmap=QPixmap(':assets/images/provider-scenerls.png')))
        layout.addWidget(self.search_field)
        layout.addWidget(self.favorites_button)
        layout.addWidget(self.refresh_button)
        layout.addWidget(QLabel('Pages:'))
        layout.addWidget(self.dlpages_field)
        layout.addWidget(self.settings_button)
        return layout

    @pyqtSlot(int)
    def select_provider(self, index: int):
        if index == 0:
            self.provider = 'Scene-RLS'
            self.source_url = 'http://scene-rls.net/releases/index.php?p={0}&cat=TV%20Shows'
        elif index == 1:
            self.provider = 'TV-Release'
            self.source_url = 'http://tv-release.pw/?cat=TV'
        self.setWindowTitle('%s :: %s' %
                            (qApp.applicationName(), self.provider))

    def settings_menu(self) -> QMenu:
        settings_action = QAction(self.icon_settings,
                                  'Settings',
                                  self,
                                  triggered=self.show_settings)
        updates_action = QAction(self.icon_updates,
                                 'Check for updates',
                                 self,
                                 triggered=self.check_update)
        aboutqt_action = QAction('About Qt', self, triggered=qApp.aboutQt)
        about_action = QAction('About %s' % qApp.applicationName(),
                               self,
                               triggered=self.about_app)
        menu = QMenu()
        menu.addAction(settings_action)
        menu.addAction(updates_action)
        menu.addSeparator()
        menu.addAction(aboutqt_action)
        menu.addAction(about_action)
        return menu

    def init_metabar(self) -> QHBoxLayout:
        self.meta_template = 'Total number of links retrieved: <b>%i</b> / <b>%i</b>'
        self.progress = QProgressBar(parent=self,
                                     minimum=0,
                                     maximum=(self.dl_pagecount *
                                              self.dl_pagelinks),
                                     visible=False)
        self.taskbar.setProgress(0.0, True)
        if sys.platform == 'win32':
            self.win_taskbar_button = QWinTaskbarButton(self)

        self.meta_label = QLabel(textFormat=Qt.RichText,
                                 alignment=Qt.AlignRight,
                                 objectName='totals')
        self.update_metabar()
        layout = QHBoxLayout()
        layout.setContentsMargins(10, 5, 10, 10)
        layout.addWidget(self.progress, Qt.AlignLeft)
        layout.addWidget(self.meta_label, Qt.AlignRight)
        return layout

    @pyqtSlot()
    def check_update(self) -> None:
        QDesktopServices.openUrl(QUrl(FixedSettings.latest_release_url))

    @pyqtSlot()
    def show_settings(self) -> None:
        settings_win = Settings(self, self.settings)
        settings_win.exec_()

    def update_metabar(self) -> bool:
        rowcount = self.table.rowCount()
        self.meta_label.setText(
            self.meta_template %
            (rowcount, self.dl_pagecount * self.dl_pagelinks))
        self.progress.setValue(rowcount)
        self.taskbar.setProgress(rowcount / self.progress.maximum())
        if sys.platform == 'win32':
            self.win_taskbar_button.progress().setValue(self.progress.value())
        return True

    def start_scraping(self) -> None:
        self.init_threads('scrape')
        self.rows = 0
        self.table.clearContents()
        self.table.setRowCount(0)
        self.table.setSortingEnabled(False)
        self.update_metabar()
        self.scrapeThread.start()

    @pyqtSlot()
    def about_app(self) -> None:
        about_html = '''<style>
        a { color:#441d4e; text-decoration:none; font-weight:bold; }
        a:hover { text-decoration:underline; }
    </style>
    <p style="font-size:24pt; font-weight:bold; color:#6A687D;">%s</p>
    <p>
        <span style="font-size:13pt;"><b>Version: %s</b></span>
        <span style="font-size:10pt;position:relative;left:5px;">( %s )</span>
    </p>
    <p style="font-size:13px;">
        Copyright &copy; %s <a href="mailto:[email protected]">Pete Alexandrou</a>
        <br/>
        Web: <a href="%s">%s</a>
    </p>
    <p style="font-size:11px;">
        This program is free software; you can redistribute it and/or
        modify it under the terms of the GNU General Public License
        as published by the Free Software Foundation; either version 2
        of the License, or (at your option) any later version.
    </p>''' % (qApp.applicationName(), qApp.applicationVersion(),
               platform.architecture()[0], datetime.now().year,
               qApp.organizationDomain(), qApp.organizationDomain())
        QMessageBox.about(self, 'About %s' % qApp.applicationName(),
                          about_html)

    @pyqtSlot(int)
    def update_pagecount(self, index: int) -> None:
        self.dl_pagecount = int(self.dlpages_field.itemText(index))
        self.scrapeWorker.maxpages = self.dl_pagecount
        self.progress.setMaximum(self.dl_pagecount * self.dl_pagelinks)
        self.settings.setValue('dl_pagecount', self.dl_pagecount)
        if sys.platform == 'win32':
            self.win_taskbar_button.progress().setMaximum(self.dl_pagecount *
                                                          self.dl_pagelinks)
        if self.scrapeThread.isRunning():
            self.scrapeThread.requestInterruption()
        self.start_scraping()

    @pyqtSlot()
    def show_progress(self):
        self.progress.show()
        self.taskbar.setProgress(0.0, True)
        if sys.platform == 'win32':
            self.win_taskbar_button.setWindow(self.windowHandle())
            self.win_taskbar_button.progress().setRange(
                0, self.dl_pagecount * self.dl_pagelinks)
            self.win_taskbar_button.progress().setVisible(True)
            self.win_taskbar_button.progress().setValue(self.progress.value())

    @pyqtSlot()
    def scrape_finished(self) -> None:
        self.progress.hide()
        self.taskbar.setProgress(0.0, False)
        if sys.platform == 'win32':
            self.win_taskbar_button.progress().setVisible(False)
        self.table.setSortingEnabled(True)
        self.filter_table(text='')

    @pyqtSlot(list)
    def add_row(self, row: list) -> None:
        if self.scrapeThread.isInterruptionRequested():
            self.scrapeThread.terminate()
        else:
            self.cols = 0
            self.table.setRowCount(self.rows + 1)
            if self.table.cursor() != Qt.PointingHandCursor:
                self.table.setCursor(Qt.PointingHandCursor)
            for item in row:
                table_item = QTableWidgetItem(item)
                table_item.setToolTip(
                    '%s\n\nDouble-click to view hoster links.' % row[1])
                table_item.setFont(QFont('Open Sans', weight=QFont.Normal))
                if self.cols == 2:
                    if sys.platform == 'win32':
                        table_item.setFont(
                            QFont('Open Sans Semibold', pointSize=10))
                    elif sys.platform == 'darwin':
                        table_item.setFont(
                            QFont('Open Sans Bold', weight=QFont.Bold))
                    else:
                        table_item.setFont(
                            QFont('Open Sans',
                                  weight=QFont.DemiBold,
                                  pointSize=10))
                    table_item.setText('  ' + table_item.text())
                elif self.cols in (0, 3):
                    table_item.setTextAlignment(Qt.AlignCenter)
                self.table.setItem(self.rows, self.cols, table_item)
                self.update_metabar()
                self.cols += 1
            self.rows += 1

    @pyqtSlot(list)
    def add_hosters(self, links: list) -> None:
        self.hosters_win.show_hosters(links)

    @pyqtSlot(QModelIndex)
    def show_hosters(self, index: QModelIndex) -> None:
        qApp.setOverrideCursor(Qt.BusyCursor)
        self.hosters_win = HosterLinks(self)
        self.hosters_win.downloadLink.connect(self.download_link)
        self.hosters_win.copyLink.connect(self.copy_download_link)
        self.links = HostersThread(
            self.table.item(self.table.currentRow(), 1).text(),
            self.user_agent)
        self.links.setHosters.connect(self.add_hosters)
        self.links.noLinks.connect(self.no_links)
        self.links.start()

    @pyqtSlot()
    def no_links(self) -> None:
        self.hosters_win.loading_progress.cancel()
        self.hosters_win.close()
        QMessageBox.warning(
            self, 'No Links Available',
            'No links are available yet for the chosen TV show. ' +
            'This is most likely due to the files still being uploaded. This is normal if the '
            +
            'link was published 30-45 mins ago.\n\nPlease check back again in 10-15 minutes.'
        )

    @pyqtSlot(bool)
    def filter_faves(self, checked: bool) -> None:
        self.settings.setValue('faves_filter', checked)
        # if hasattr(self, 'scrapeWorker') and (sip.isdeleted(self.scrapeWorker) or self.scrapeWorker.complete):
        if not self.firstrun:
            self.filter_table()

    @pyqtSlot(str)
    @pyqtSlot()
    def filter_table(self, text: str = '') -> None:
        filters = []
        if self.favorites_button.isChecked():
            filters = self.favorites
            self.table.sortItems(2, Qt.AscendingOrder)
        else:
            self.table.sortItems(0, Qt.DescendingOrder)
        if len(text):
            filters.append(text)
        if not len(filters) or not hasattr(self, 'valid_rows'):
            self.valid_rows = []
        for search_term in filters:
            for item in self.table.findItems(search_term, Qt.MatchContains):
                self.valid_rows.append(item.row())
        for row in range(0, self.table.rowCount()):
            if not len(filters):
                self.table.showRow(row)
            else:
                if row not in self.valid_rows:
                    self.table.hideRow(row)
                else:
                    self.table.showRow(row)

    @pyqtSlot()
    def clear_filters(self):
        if not len(self.search_field.text()):
            self.filter_table('')

    @pyqtSlot(bool)
    def aria2_confirmation(self, success: bool) -> None:
        qApp.restoreOverrideCursor()
        if success:
            if sys.platform.startswith('linux'):
                self.notify(
                    title=qApp.applicationName(),
                    msg='Your download link has been unrestricted and now ' +
                    'queued in Aria2 RPC Daemon',
                    icon=self.NotifyIcon.SUCCESS)
            else:
                QMessageBox.information(
                    self, qApp.applicationName(),
                    'Download link has been queued in Aria2.', QMessageBox.Ok)
        else:
            QMessageBox.critical(
                self, 'Aria2 RPC Daemon',
                'Could not connect to Aria2 RPC Daemon. ' +
                'Check your %s settings and try again.' %
                qApp.applicationName(), QMessageBox.Ok)

    @pyqtSlot(str)
    def download_link(self, link: str) -> None:
        if len(self.realdebrid_api_token) > 0 and 'real-debrid.com' not in link \
            and 'rdeb.io' not in link:
            qApp.setOverrideCursor(Qt.BusyCursor)
            self.unrestrict_link(link, True)
        else:
            if self.download_manager == 'aria2':
                self.aria2 = Aria2Thread(settings=self.settings, link_url=link)
                self.aria2.aria2Confirmation.connect(self.aria2_confirmation)
                self.aria2.start()
                self.hosters_win.close()
            elif self.download_manager == 'pyload':
                self.pyload_conn = PyloadConnection(self.pyload_host,
                                                    self.pyload_username,
                                                    self.pyload_password)
                pid = self.pyload_conn.addPackage(name='TVLinker',
                                                  links=[link])
                qApp.restoreOverrideCursor()
                self.hosters_win.close()
                if sys.platform.startswith('linux'):
                    self.notify(title='Download added to %s' %
                                self.download_manager,
                                icon=self.NotifyIcon.SUCCESS)
                else:
                    QMessageBox.information(
                        self, self.download_manager,
                        'Your link has been queued in %s.' %
                        self.download_manager, QMessageBox.Ok)
                # open_pyload = msgbox.addButton('Open pyLoad', QMessageBox.AcceptRole)
                # open_pyload.clicked.connect(self.open_pyload)
            elif self.download_manager in ('kget', 'persepolis'):
                provider = self.kget_cmd if self.download_manager == 'kget' else self.persepolis_cmd
                cmd = '{0} "{1}"'.format(provider, link)
                if self.cmdexec(cmd):
                    qApp.restoreOverrideCursor()
                    self.hosters_win.close()
                    if sys.platform.startswith('linux'):
                        self.notify(title='Download added to %s' %
                                    self.download_manager,
                                    icon=self.NotifyIcon.SUCCESS)
                    else:
                        QMessageBox.information(
                            self, self.download_manager,
                            'Your link has been queued in %s.' %
                            self.download_manager, QMessageBox.Ok)
            elif self.download_manager == 'idm':
                cmd = '"%s" /n /d "%s"' % (self.idm_exe_path, link)
                if self.cmdexec(cmd):
                    qApp.restoreOverrideCursor()
                    self.hosters_win.close()
                    QMessageBox.information(
                        self, 'Internet Download Manager',
                        'Your link has been queued in IDM.')
                else:
                    print('IDM QProcess error = %s' %
                          self.ProcError(self.idm.error()).name)
                    qApp.restoreOverrideCursor()
                    self.hosters_win.close()
                    QMessageBox.critical(
                        self, 'Internet Download Manager',
                        '<p>Could not connect to your local IDM application instance. '
                        +
                        'Please check your settings and ensure the IDM executable path is correct '
                        +
                        'according to your installation.</p><p>Error Code: %s</p>'
                        % self.ProcError(self.idm.error()).name,
                        QMessageBox.Ok)
            else:
                dlpath, _ = QFileDialog.getSaveFileName(
                    self, 'Save File',
                    link.split('/')[-1])
                if dlpath != '':
                    self.directdl_win = DirectDownload(parent=self)
                    self.directdl = DownloadThread(link_url=link,
                                                   dl_path=dlpath)
                    self.directdl.dlComplete.connect(
                        self.directdl_win.download_complete)
                    if sys.platform.startswith('linux'):
                        self.directdl.dlComplete.connect(
                            lambda: self.notify(qApp.applicationName(
                            ), 'Download complete', self.NotifyIcon.SUCCESS))
                    else:
                        self.directdl.dlComplete.connect(
                            lambda: QMessageBox.information(
                                self, qApp.applicationName(),
                                'Download complete', QMessageBox.Ok))
                    self.directdl.dlProgressTxt.connect(
                        self.directdl_win.update_progress_label)
                    self.directdl.dlProgress.connect(
                        self.directdl_win.update_progress)
                    self.directdl_win.cancelDownload.connect(
                        self.cancel_download)
                    self.directdl.start()
                    self.hosters_win.close()

    def _init_notification_icons(self):
        for icon in self.NotifyIcon:
            icon_file = QPixmap(icon.value, 'PNG')
            icon_file.save(
                os.path.join(FixedSettings.config_path,
                             os.path.basename(icon.value)), 'PNG', 100)

    def notify(self,
               title: str,
               msg: str = '',
               icon: Enum = None,
               urgency: int = 1) -> bool:
        icon_path = icon.value if icon is not None else self.NotifyIcon.DEFAULT.value
        icon_path = os.path.join(FixedSettings.config_path,
                                 os.path.basename(icon_path))
        if not os.path.exists(icon_path):
            self._init_notification_icons()
        notification = notify.Notification(title, msg, icon_path)
        notification.set_urgency(urgency)
        return notification.show()

    def cmdexec(self, cmd: str) -> bool:
        self.proc = QProcess()
        self.proc.setProcessChannelMode(QProcess.MergedChannels)
        if hasattr(self.proc, 'errorOccurred'):
            self.proc.errorOccurred.connect(lambda error: print(
                'Process error = %s' % self.ProcError(error).name))
        if self.proc.state() == QProcess.NotRunning:
            self.proc.start(cmd)
            self.proc.waitForFinished(-1)
            rc = self.proc.exitStatus(
            ) == QProcess.NormalExit and self.proc.exitCode() == 0
            self.proc.deleteLater()
            return rc
        return False

    @pyqtSlot()
    def cancel_download(self) -> None:
        self.directdl.cancel_download = True
        self.directdl.quit()
        self.directdl.deleteLater()

    def open_pyload(self) -> None:
        QDesktopServices.openUrl(QUrl(self.pyload_config.host))

    @pyqtSlot(str)
    def copy_download_link(self, link: str) -> None:
        if len(self.realdebrid_api_token) > 0 and 'real-debrid.com' not in link \
            and 'rdeb.io' not in link:
            qApp.setOverrideCursor(Qt.BusyCursor)
            self.unrestrict_link(link, False)
        else:
            clip = qApp.clipboard()
            clip.setText(link)
            self.hosters_win.close()
            qApp.restoreOverrideCursor()

    def unrestrict_link(self, link: str, download: bool = True) -> None:
        caller = inspect.stack()[1].function
        self.realdebrid = RealDebridThread(
            settings=self.settings,
            api_url=FixedSettings.realdebrid_api_url,
            link_url=link,
            action=RealDebridThread.RealDebridAction.UNRESTRICT_LINK)
        self.realdebrid.errorMsg.connect(self.error_handler)
        if download:
            self.realdebrid.unrestrictedLink.connect(self.download_link)
        else:
            self.realdebrid.unrestrictedLink.connect(self.copy_download_link)
        self.realdebrid.start()

    def closeEvent(self, event: QCloseEvent) -> None:
        if hasattr(self, 'scrapeThread'):
            if not sip.isdeleted(
                    self.scrapeThread) and self.scrapeThread.isRunning():
                self.scrapeThread.requestInterruption()
                self.scrapeThread.quit()
        qApp.quit()

    def error_handler(self, props: list) -> None:
        qApp.restoreOverrideCursor()
        QMessageBox.critical(self, props[0], props[1], QMessageBox.Ok)

    @staticmethod
    def get_path(path: str = None, override: bool = False) -> str:
        if override:
            if getattr(sys, 'frozen', False):
                return os.path.join(sys._MEIPASS, path)
            return os.path.join(QFileInfo(__file__).absolutePath(), path)
        return ':assets/%s' % path

    @staticmethod
    def get_version(filename: str = '__init__.py') -> str:
        with open(TVLinker.get_path(filename, override=True), 'r') as initfile:
            for line in initfile.readlines():
                m = re.match('__version__ *= *[\'](.*)[\']', line)
                if m:
                    return m.group(1)
Exemple #6
0
class LabJackWidget(QWidget):

    view_name = "LabJackView.ui"

    def __init__(self, labjack, labjack_states, parent=None):
        super().__init__(parent=parent)
        self.TableView = None
        self.labjack_device = labjack
        self.labjack_states = labjack_states
        self.is_executing = False
        self.setup_ui()
        self.connect()

    def connect(self):
        self.labjack_device.status_changed.connect(
            lambda text: self.labjackStatus.setText(text))

    def on_executeStatesButton_pressed(self):
        if self.is_executing:
            self.interrupt_execution()
        elif self.labjack_device.available():
            logging.debug('Starting execute states.')
            self.labjack_device.clear()
            count = int(self.ExecuteStatesLoopCount.value())
            delay = float(self.ExecuteStatesLoopDelay.value())

            self.thread = QThread()
            self.worker = execute_states_thread.ExecuteStatesWorker(
                number_of_times=count,
                delay=delay,
                device=self.labjack_device,
                states=self.labjack_states.states)
            self.worker.moveToThread(self.thread)

            self.thread.started.connect(self.worker.run)

            self.worker.started.connect(self.on_execute_states_started)
            self.worker.loop_progress.connect(self.update_loop_progress)
            self.worker.finished.connect(self.on_execute_states_finished)
            self.worker.interrupted.connect(self.on_execute_states_finished)
            self.worker.finished.connect(self.thread.quit)

            self.thread.finished.connect(self.thread.deleteLater)
            self.worker.finished.connect(self.worker.deleteLater)

            self.thread.start()

    def interrupt_execution(self):
        self.executeStatesButton.setText('Aborting...')
        self.thread.requestInterruption()

    def on_execute_states_finished(self):
        self.is_executing = False
        self.executeStatesButton.setText('Execute')
        self.labjack_device.clear()
        self.set_input_enabled(enabled=True)

    def on_execute_states_started(self):
        self.is_executing = True
        self.executeStatesButton.setText('Interrupt')
        self.set_input_enabled(enabled=False)
        self.LoopProgress.setValue(0)

    def update_loop_progress(self, value):
        self.LoopProgress.setValue(value * 100)
        logging.debug(value)

    def set_input_enabled(self, enabled):
        logging.debug('Input enabled: %i' % enabled)
        self.deleteStates.setEnabled(enabled)
        self.addStateButton.setEnabled(enabled)

    def setup_ui(self):
        p = Path(__file__).parents[1]
        view_location = p.joinpath('views/LabJackView.ui').__str__()

        uic.loadUi(view_location, self)

        self.TableView.setModel(self.labjack_states)

        self.TableView.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.TableView.resizeColumnsToContents()

        self.TableView.setAcceptDrops(True)
        self.TableView.setDragEnabled(True)
        self.TableView.setDefaultDropAction(Qt.MoveAction)
        self.TableView.setDragDropMode(QAbstractItemView.InternalMove)
        self.TableView.setDragDropOverwriteMode(False)
        self.TableView.setSelectionBehavior(QAbstractItemView.SelectRows)

        width = self.TableView.horizontalHeader().sizeHint().width(
        ) * self.TableView.horizontalHeader().count()
        self.TableView.setMinimumWidth(width)

        self.fio_states = [
            self.FIO4State, self.FIO5State, self.FIO6State, self.FIO7State
        ]

        for fio in self.labjack_device.fios:
            getattr(self, "fio{}Label".format(fio.number)).setText(fio.label)

        getattr(self, "DAC0Label").setText(self.labjack_device.DAC0.label)
        getattr(self, "DAC1Label").setText(self.labjack_device.DAC1.label)

    def on_addStateButton_pressed(self):
        self.labjack_states.add_state(self.FIO4State.isChecked(),
                                      self.FIO5State.isChecked(),
                                      self.FIO6State.isChecked(),
                                      self.FIO7State.isChecked(),
                                      self.DAC0Input.value(),
                                      self.DAC1Input.value(),
                                      self.DurationInput.value())
        self.clear_new_input_state()

    def clear_new_input_state(self):
        [state.setChecked(False) for state in self.fio_states]
        self.DAC0Input.setValue(0.00)
        self.DAC1Input.setValue(0.00)
        self.DurationInput.setValue(0.00)

    def on_deleteStates_pressed(self):
        selection = self.TableView.selectionModel()
        model_indices = selection.selectedRows()
        [
            self.labjack_states.removeRow(model_index.row() - i)
            for i, model_index in enumerate(model_indices)
        ]

    def on_section_moved(self, logical_index, old_visual_index,
                         new_visual_index):
        self.model.move_state(old_visual_index, new_visual_index)
class CSVImport:
    def __init__(self, parent, target_layout, result_collector_factory,
                 layer_factory):
        self.parent = parent
        self.ui = UI(parent.dockwidget, target_layout)

        self.result_collector_factory = result_collector_factory
        self.layer_factory = layer_factory

        self.file_path = None

        self.__init_ui()

        uldk_search = ULDKSearchParcel("dzialka",
                                       ("geom_wkt", "wojewodztwo", "powiat",
                                        "gmina", "obreb", "numer", "teryt"))

        self.uldk_search = ULDKSearchLogger(uldk_search)

    def start_import(self):
        self.__cleanup_before_search()

        teryts = []
        self.additional_attributes = defaultdict(list)
        with open(self.file_path) as f:
            teryt_column = self.ui.combobox_teryt_column.currentText()
            csv_read = csv.DictReader(f)
            additional_fields = [
                name for name in csv_read.fieldnames if name != teryt_column
            ]
            for row in csv_read:
                teryt = row.pop(teryt_column)
                teryts.append(teryt)
                if additional_fields:
                    for value in row.values():
                        self.additional_attributes[teryt].append(value)

        layer_name = self.ui.text_edit_layer_name.text()
        layer = self.layer_factory(name=layer_name,
                                   custom_properties={"ULDK": layer_name},
                                   additional_fields=[
                                       QgsField(field, QVariant.String)
                                       for field in additional_fields
                                   ])

        self.result_collector = self.result_collector_factory(
            self.parent, layer)
        self.features_found = []
        self.csv_rows_count = len(teryts)

        self.worker = ULDKSearchWorker(self.uldk_search, teryts)
        self.thread = QThread()
        self.worker.moveToThread(self.thread)
        self.worker.found.connect(self.__handle_found)
        self.worker.found.connect(self.__progressed)
        self.worker.not_found.connect(self.__handle_not_found)
        self.worker.not_found.connect(self.__progressed)
        self.worker.found.connect(self.__progressed)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.__handle_finished)
        self.worker.interrupted.connect(self.__handle_interrupted)
        self.worker.interrupted.connect(self.thread.quit)
        self.thread.started.connect(self.worker.search)

        self.thread.start()

        self.ui.label_status.setText(
            f"Trwa wyszukiwanie {self.csv_rows_count} obiektów...")

    def __init_ui(self):
        self.ui.button_start.clicked.connect(self.start_import)
        self.ui.label_status.setText("")
        self.ui.label_found_count.setText("")
        self.ui.label_not_found_count.setText("")
        self.ui.button_cancel.clicked.connect(self.__stop)
        self.ui.file_select.fileChanged.connect(self.__on_file_changed)
        self.ui.button_save_not_found.clicked.connect(
            self._export_table_errors_to_csv)
        self.__init_table()

    def __init_table(self):
        table = self.ui.table_errors
        table.setEditTriggers(QTableWidget.NoEditTriggers)
        table.setColumnCount(2)
        table.setHorizontalHeaderLabels(("TERYT", "Treść błędu"))
        header = table.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.Interactive)
        header.setSectionResizeMode(1, QHeaderView.Stretch)
        teryt_column_size = table.width() / 3
        header.resizeSection(0, 200)

    def __on_file_changed(self, path):
        suggested_target_layer_name = ""
        if os.path.exists(path):
            self.ui.button_start.setEnabled(True)
            self.file_path = path
            self.__fill_column_select()
            suggested_target_layer_name = os.path.splitext(
                os.path.relpath(path))[0]
        else:
            self.file_path = None
            self.ui.combobox_teryt_column.clear()
        self.ui.text_edit_layer_name.setText(suggested_target_layer_name)

    def __fill_column_select(self):
        self.ui.combobox_teryt_column.clear()
        with open(self.file_path) as f:
            csv_read = csv.DictReader(f)
            columns = csv_read.fieldnames
        self.ui.combobox_teryt_column.addItems(columns)

    def __handle_found(self, uldk_response_rows):
        for row in uldk_response_rows:
            try:
                teryt = row.split("|")[6]
                attributes = self.additional_attributes.get(teryt)
                feature = self.result_collector.uldk_response_to_qgs_feature(
                    row, attributes)
            except self.result_collector.BadGeometryException as e:
                e = self.result_collector.BadGeometryException(
                    e.feature, "Niepoprawna geometria")
                self._handle_bad_geometry(e.feature, e)
                return
            self.features_found.append(feature)
            self.found_count += 1

    def __handle_not_found(self, teryt, exception):
        self._add_table_errors_row(teryt, str(exception))
        self.not_found_count += 1

    def _handle_bad_geometry(self, feature, exception):
        self._add_table_errors_row(feature.attribute("teryt"), str(exception))
        self.not_found_count += 1

    def __progressed(self):
        found_count = self.found_count
        not_found_count = self.not_found_count
        progressed_count = found_count + not_found_count
        self.ui.progress_bar.setValue(progressed_count / self.csv_rows_count *
                                      100)
        self.ui.label_status.setText("Przetworzono {} z {} obiektów".format(
            progressed_count, self.csv_rows_count))
        self.ui.label_found_count.setText("Znaleziono: {}".format(found_count))
        self.ui.label_not_found_count.setText(
            "Nie znaleziono: {}".format(not_found_count))

    def __handle_finished(self):
        self.__collect_received_features()
        form = "obiekt"
        found_count = self.found_count
        if found_count == 1:
            pass
        elif 2 <= found_count <= 4:
            form = "obiekty"
        elif 5 <= found_count <= 15:
            form = "obiektów"
        else:
            units = found_count % 10
            if units in (2, 3, 4):
                form = "obiekty"
            else:
                form = "obiektów"

        iface.messageBar().pushWidget(
            QgsMessageBarItem(
                "Wtyczka ULDK",
                f"Import CSV: zakończono wyszukiwanie. Zapisano {found_count} {form} do warstwy <b>{self.ui.text_edit_layer_name.text()}</b>"
            ))
        if self.not_found_count > 0:
            self.ui.button_save_not_found.setEnabled(True)

        self.__cleanup_after_search()

    def __handle_interrupted(self):
        self.__collect_received_features()
        self.__cleanup_after_search()

    def __collect_received_features(self):
        if self.features_found:
            self.result_collector.update_with_features(self.features_found)

    def _export_table_errors_to_csv(self):
        count = self.ui.table_errors.rowCount()
        path, _ = QFileDialog.getSaveFileName(filter='*.csv')
        if path:
            with open(path, 'w') as f:
                writer = csv.writer(f, delimiter=',')
                writer.writerow([
                    self.ui.table_errors.horizontalHeaderItem(0).text(),
                    self.ui.table_errors.horizontalHeaderItem(1).text()
                ])
                for row in range(0, count):
                    teryt = self.ui.table_errors.item(row, 0).text()
                    error = self.ui.table_errors.item(row, 1).text()
                    writer.writerow([teryt, error])
                iface.messageBar().pushWidget(
                    QgsMessageBarItem(
                        "Wtyczka ULDK",
                        "Pomyślnie wyeksportowano nieznalezione działki."))

    def _add_table_errors_row(self, teryt, exception_message):
        row = self.ui.table_errors.rowCount()
        self.ui.table_errors.insertRow(row)
        self.ui.table_errors.setItem(row, 0, QTableWidgetItem(teryt))
        self.ui.table_errors.setItem(row, 1,
                                     QTableWidgetItem(exception_message))

    def __cleanup_after_search(self):
        self.__set_controls_enabled(True)
        self.ui.button_cancel.setText("Anuluj")
        self.ui.button_cancel.setEnabled(False)
        self.ui.progress_bar.setValue(0)

    def __cleanup_before_search(self):
        self.__set_controls_enabled(False)
        self.ui.button_cancel.setEnabled(True)
        self.ui.button_save_not_found.setEnabled(False)
        self.ui.table_errors.setRowCount(0)
        self.ui.label_status.setText("")
        self.ui.label_found_count.setText("")
        self.ui.label_not_found_count.setText("")

        self.found_count = 0
        self.not_found_count = 0

    def __set_controls_enabled(self, enabled):
        self.ui.text_edit_layer_name.setEnabled(enabled)
        self.ui.button_start.setEnabled(enabled)
        self.ui.file_select.setEnabled(enabled)
        self.ui.combobox_teryt_column.setEnabled(enabled)

    def __stop(self):
        self.thread.requestInterruption()
        self.ui.button_cancel.setEnabled(False)
        self.ui.button_cancel.setText("Przerywanie...")
class GeneratorDialog(QDialog):
    """Generator Dialog"""
    def __init__(self, version, iconPath, addonDir, mediaDir):

        super().__init__()
        self.mediaDir = mediaDir
        self.iconPath = iconPath

        # Paths
        self.ankiCsvPath = join(addonDir, Constant.ANKI_DECK)

        # Create Generator GUI
        self.ui = UiGenerator()
        self.ui.setupUi(self, version, iconPath)
        self.ui.cancelBtn.setDisabled(True)

        # Create Importer Instance
        self.importer = ImporterDialog(version, iconPath, addonDir, mediaDir)
        self.importer.keyPressed.connect(self.importer.on_key)

        # Set Total Input Word count
        self.ui.inputTxt.textChanged.connect(self.input_text_changed)

        # Set Completed Output Card count
        self.ui.outputTxt.textChanged.connect(self.output_text_changed)

        # Set Failures Word count
        self.ui.failureTxt.textChanged.connect(self.failure_text_changed)

        # Handle clicks on Generate button
        self.ui.generateBtn.clicked.connect(self.btn_generate_clicked)

        # Handle clicks on Progress bar
        self.ui.importBtn.clicked.connect(self.btn_importer_clicked)

        self.get_supported_languages()
        # Handle user clicks on translation
        self.ui.source1.clicked.connect(self.get_supported_languages)
        self.ui.source2.clicked.connect(self.get_supported_languages)
        self.ui.source3.clicked.connect(self.get_supported_languages)
        self.ui.source4.clicked.connect(self.get_supported_languages)

    def close_event(self, event):
        logging.shutdown()

    def input_text_changed(self):

        words = self.ui.inputTxt.toPlainText().split("\n")
        # Filter words list, only get non-empty words
        self.words = list(filter(None, words))
        self.ui.totalLbl.setText("Total: {}".format(len(self.words)))

    def output_text_changed(self):

        cards = self.ui.outputTxt.toPlainText().split("\n")
        # Filter cards list, only get non-empty cards
        self.cards = list(filter(None, cards))
        self.cardCount = len(self.cards)
        self.ui.completedLbl.setText("Completed: {}".format(len(self.cards)))

    def failure_text_changed(self):

        failures = self.ui.failureTxt.toPlainText().split("\n")
        # Filter failures list, only get non-empty failures
        self.failures = list(filter(None, failures))
        self.failureCount = len(self.failures)
        self.ui.failureLbl.setText("Failure: {}".format(len(self.failures)))

    def btn_generate_clicked(self):

        # Validate if input text empty?
        inputText = self.ui.inputTxt.toPlainText()

        if not inputText:
            AnkiHelper.message_box("Info",
                                   "No input words available for generating.",
                                   "Please check your input words!",
                                   self.iconPath)
            return

        # Increase to 2% as a processing signal to user
        self.ui.generateProgressBar.setValue(2)
        self.ui.generateBtn.setDisabled(True)

        # Clean up output before generating cards
        self.ui.outputTxt.setPlainText("")
        self.ui.failureTxt.setPlainText("")

        # Get translation options
        source = self.selected_radio(self.ui.sourceBox)
        target = self.selected_radio(self.ui.translatedToBox)
        self.translation = Translation(source, target)

        # Get generating options
        self.allWordTypes = self.ui.allWordTypes.isChecked()
        self.isOnline = self.ui.isOnline.isChecked()

        # Initialize Generator based on translation
        self.generator = Worker.initialize_generator(self.translation)

        # Step 2: Create a QThread object
        self.bgThread = QThread(self)
        self.bgThread.setTerminationEnabled(True)

        # Step 3: Create a worker object
        self.worker = Worker(self.generator, self.words, self.translation,
                             self.mediaDir, self.isOnline, self.allWordTypes,
                             self.ankiCsvPath)

        # Step 4: Move worker to the thread
        self.worker.moveToThread(self.bgThread)

        # Step 5: Connect signals and slots
        self.bgThread.started.connect(self.worker.generate_cards_background)

        self.worker.finished.connect(self.bgThread.quit)
        self.bgThread.finished.connect(self.bgThread.deleteLater)

        self.worker.finished.connect(self.worker.deleteLater)
        self.worker.progress.connect(self.report_progress)

        self.worker.cardStr.connect(self.report_card)
        self.worker.failureStr.connect(self.report_failure)

        # Step 6: Start the thread
        self.bgThread.start()
        self.ui.cancelBtn.setEnabled(True)
        self.ui.generateBtn.setDisabled(True)

        # Handle cancel background task
        self.isCancelled = False

        receiversCount = self.ui.cancelBtn.receivers(self.ui.cancelBtn.clicked)
        if receiversCount > 0:
            logging.info(
                "Already connected before...{}".format(receiversCount))
            self.ui.cancelBtn.clicked.disconnect()

        self.ui.cancelBtn.clicked.connect(self.cancel_background_task)

        # Final resets
        self.bgThread.finished.connect(self.finished_generation_progress)

    def cancel_background_task(self):

        logging.info("Canceling background task...")
        self.bgThread.requestInterruption()
        self.bgThread.quit()

        self.ui.outputTxt.setPlainText("")
        self.isCancelled = True

        AnkiHelper.message_box("Info",
                               "Flashcards generation process stopped!",
                               "Restart by clicking Generate button.",
                               self.iconPath)

    def finished_generation_progress(self):

        self.ui.cancelBtn.setDisabled(True)
        self.ui.generateBtn.setEnabled(True)

        # Return if thread is cancelled
        if self.isCancelled:
            self.ui.outputTxt.setPlainText("")
            return

        if self.ui.outputTxt.toPlainText():
            btnSelected = AnkiHelper.message_box_buttons(
                "Info",
                "Finished generating flashcards.\nThanks for using AnkiFlash!",
                "Do you want to import generated flashcards now?\n\nProgress completed 100%\n- Input: {}\n- Output: {}\n- Failure: {}"
                .format(len(self.words), self.cardCount,
                        self.failureCount), QMessageBox.No | QMessageBox.Yes,
                QMessageBox.Yes, self.iconPath)
            if btnSelected == QMessageBox.Yes:
                self.btn_importer_clicked()
        else:
            AnkiHelper.message_box_buttons(
                "Info",
                "Finished generating flashcards.\nThanks for using AnkiFlash!",
                "No output flashcards available for importing.\n\nProgress completed 100%\n- Input: {}\n- Output: {}\n- Failure: {}"
                .format(len(self.words), self.cardCount, self.failureCount),
                QMessageBox.Close, QMessageBox.Close, self.iconPath)

    def report_progress(self, percent):

        # Return if thread is interrupted
        if self.bgThread.isInterruptionRequested():
            return

        self.ui.generateProgressBar.setValue(percent)

    def report_card(self, cardStr):

        # Return if thread is interrupted
        if self.bgThread.isInterruptionRequested():
            return

        currentText = self.ui.outputTxt.toPlainText()
        if currentText:
            currentText += "\n"
        self.ui.outputTxt.setPlainText("{}{}".format(currentText, cardStr))

    def report_failure(self, failureStr):

        # Return if thread is interrupted
        if self.bgThread.isInterruptionRequested():
            return

        currentText = self.ui.failureTxt.toPlainText()
        if currentText:
            currentText += "\n"
        self.ui.failureTxt.setPlainText("{}{}".format(currentText, failureStr))

    def btn_importer_clicked(self):
        if self.ui.outputTxt.toPlainText():
            self.importer.ui.importProgressBar.setValue(0)
            self.importer.show()
        else:
            AnkiHelper.message_box(
                "Info", "No output flashcards available for importing.",
                "Please check your input words!", self.iconPath)

    def get_supported_languages(self):
        source = self.selected_radio(self.ui.sourceBox)
        supportTranslations = {
            Constant.ENGLISH: [
                Constant.ENGLISH, Constant.VIETNAMESE, Constant.CHINESE_TD,
                Constant.CHINESE_SP, Constant.FRENCH, Constant.JAPANESE
            ],
            Constant.VIETNAMESE: [
                Constant.ENGLISH, Constant.FRENCH, Constant.JAPANESE,
                Constant.VIETNAMESE
            ],
            Constant.FRENCH: [Constant.ENGLISH, Constant.VIETNAMESE],
            Constant.JAPANESE: [Constant.ENGLISH, Constant.VIETNAMESE]
        }

        targetLanguages = supportTranslations.get(source)
        logging.info("targetLanguages {}".format(targetLanguages))

        radioBtns = [
            radio for radio in self.ui.translatedToBox.children()
            if isinstance(radio, QRadioButton)
        ]

        for radio in radioBtns:
            if radio.text() == Constant.ENGLISH:
                radio.click()

            if radio.text() in targetLanguages:
                radio.setEnabled(True)
                self.change_radio_color(radio, True)
            else:
                radio.setEnabled(False)
                self.change_radio_color(radio, False)

    def change_radio_color(self, radio: QRadioButton, isEnabled: bool):
        if isEnabled:
            radio.setStyleSheet(self.ui.source1.styleSheet())
        else:
            radio.setStyleSheet("color:gray")

    def selected_radio(self, groupBox: QGroupBox) -> str:
        # Get all radio buttons
        radioBtns = [
            radio for radio in groupBox.children()
            if isinstance(radio, QRadioButton)
        ]
        # Find choosen radio and return text
        for radio in radioBtns:
            if radio.isChecked():
                return radio.text()
Exemple #9
0
class _POSIXUserscriptRunner(_BaseUserscriptRunner):

    """Userscript runner to be used on POSIX. Uses _BlockingFIFOReader.

    The OS must have support for named pipes and select(). Commands are
    executed immediately when they arrive in the FIFO.

    Attributes:
        _reader: The _BlockingFIFOReader instance.
        _thread: The QThread where reader runs.
    """

    def __init__(self, parent=None):
        super().__init__(parent)
        self._reader = None
        self._thread = None

    def run(self, cmd, *args, env=None):
        rundir = utils.get_standard_dir(QStandardPaths.RuntimeLocation)
        # tempfile.mktemp is deprecated and discouraged, but we use it here to
        # create a FIFO since the only other alternative would be to create a
        # directory and place the FIFO there, which sucks. Since os.kfifo will
        # raise an exception anyways when the path doesn't exist, it shouldn't
        # be a big issue.
        self._filepath = tempfile.mktemp(prefix='userscript-', dir=rundir)
        os.mkfifo(self._filepath)  # pylint: disable=no-member

        self._reader = _BlockingFIFOReader(self._filepath)
        self._thread = QThread(self)
        self._reader.moveToThread(self._thread)
        self._reader.got_line.connect(self.got_cmd)
        self._thread.started.connect(self._reader.read)
        self._reader.finished.connect(self.on_reader_finished)
        self._thread.finished.connect(self.on_thread_finished)

        self._run_process(cmd, *args, env=env)
        self._thread.start()

    def on_proc_finished(self):
        """Interrupt the reader when the process finished."""
        log.procs.debug("proc finished")
        self._thread.requestInterruption()

    def on_proc_error(self, error):
        """Interrupt the reader when the process had an error."""
        super().on_proc_error(error)
        self._thread.requestInterruption()

    def on_reader_finished(self):
        """Quit the thread and clean up when the reader finished."""
        log.procs.debug("reader finished")
        self._thread.quit()
        self._reader.fifo.close()
        self._reader.deleteLater()
        super()._cleanup()
        self.finished.emit()

    def on_thread_finished(self):
        """Clean up the QThread object when the thread finished."""
        log.procs.debug("thread finished")
        self._thread.deleteLater()
Exemple #10
0
class _POSIXUserscriptRunner(_BaseUserscriptRunner):

    """Userscript runner to be used on POSIX. Uses _BlockingFIFOReader.

    The OS must have support for named pipes and select(). Commands are
    executed immediately when they arrive in the FIFO.

    Attributes:
        _reader: The _BlockingFIFOReader instance.
        _thread: The QThread where reader runs.
    """

    def __init__(self, win_id, parent=None):
        super().__init__(win_id, parent)
        self._reader = None
        self._thread = None

    def run(self, cmd, *args, env=None):
        rundir = standarddir.get(QStandardPaths.RuntimeLocation)
        try:
            # tempfile.mktemp is deprecated and discouraged, but we use it here
            # to create a FIFO since the only other alternative would be to
            # create a directory and place the FIFO there, which sucks. Since
            # os.kfifo will raise an exception anyways when the path doesn't
            # exist, it shouldn't be a big issue.
            self._filepath = tempfile.mktemp(prefix='userscript-', dir=rundir)
            os.mkfifo(self._filepath)  # pylint: disable=no-member
        except OSError as e:
            message.error(self._win_id, "Error while creating FIFO: {}".format(
                e))
            return

        self._reader = _BlockingFIFOReader(self._filepath)
        self._thread = QThread(self)
        self._reader.moveToThread(self._thread)
        self._reader.got_line.connect(self.got_cmd)
        self._thread.started.connect(self._reader.read)
        self._reader.finished.connect(self.on_reader_finished)
        self._thread.finished.connect(self.on_thread_finished)

        self._run_process(cmd, *args, env=env)
        self._thread.start()

    def on_proc_finished(self):
        """Interrupt the reader when the process finished."""
        log.procs.debug("proc finished")
        self._thread.requestInterruption()

    def on_proc_error(self, error):
        """Interrupt the reader when the process had an error."""
        super().on_proc_error(error)
        self._thread.requestInterruption()

    def on_reader_finished(self):
        """Quit the thread and clean up when the reader finished."""
        log.procs.debug("reader finished")
        self._thread.quit()
        self._reader.fifo.close()
        self._reader.deleteLater()
        super()._cleanup()
        self.finished.emit()

    def on_thread_finished(self):
        """Clean up the QThread object when the thread finished."""
        log.procs.debug("thread finished")
        self._thread.deleteLater()