Merge branch 'mizaki-talker_settings_generator' into develop

This commit is contained in:
Timmy Welch 2023-02-27 22:20:53 -08:00
commit fca5818874
No known key found for this signature in database
8 changed files with 393 additions and 313 deletions

View File

@ -46,6 +46,43 @@ def identifier(parser: settngs.Manager) -> None:
action=AppendAction,
help="When enabled filters the listed publishers from all search results",
)
parser.add_setting("--series-match-search-thresh", default=90, type=int)
parser.add_setting(
"--clear-metadata",
default=True,
help="Clears all existing metadata during import, default is to merges metadata.\nMay be used in conjunction with -o, -f and -m.\n\n",
dest="clear_metadata_on_import",
action=argparse.BooleanOptionalAction,
)
parser.add_setting(
"-a",
"--auto-imprint",
action=argparse.BooleanOptionalAction,
default=False,
help="Enables the auto imprint functionality.\ne.g. if the publisher is set to 'vertigo' it\nwill be updated to 'DC Comics' and the imprint\nproperty will be set to 'Vertigo'.\n\n",
)
parser.add_setting(
"--sort-series-by-year", default=True, action=argparse.BooleanOptionalAction, help="Sorts series by year"
)
parser.add_setting(
"--exact-series-matches-first",
default=True,
action=argparse.BooleanOptionalAction,
help="Puts series that are an exact match at the top of the list",
)
parser.add_setting(
"--always-use-publisher-filter",
default=False,
action=argparse.BooleanOptionalAction,
help="Enables the publisher filter",
)
parser.add_setting(
"--clear-form-before-populating",
default=False,
action=argparse.BooleanOptionalAction,
help="Clears all existing metadata when applying metadata from comic source",
)
def dialog(parser: settngs.Manager) -> None:
@ -86,43 +123,6 @@ def filename(parser: settngs.Manager) -> None:
def talker(parser: settngs.Manager) -> None:
# General settings for talkers
parser.add_setting("--source", default="comicvine", help="Use a specified source by source ID")
parser.add_setting("--series-match-search-thresh", default=90, type=int)
parser.add_setting(
"--clear-metadata",
default=True,
help="Clears all existing metadata during import, default is to merges metadata.\nMay be used in conjunction with -o, -f and -m.\n\n",
dest="clear_metadata_on_import",
action=argparse.BooleanOptionalAction,
)
parser.add_setting(
"-a",
"--auto-imprint",
action=argparse.BooleanOptionalAction,
default=False,
help="Enables the auto imprint functionality.\ne.g. if the publisher is set to 'vertigo' it\nwill be updated to 'DC Comics' and the imprint\nproperty will be set to 'Vertigo'.\n\n",
)
parser.add_setting(
"--sort-series-by-year", default=True, action=argparse.BooleanOptionalAction, help="Sorts series by year"
)
parser.add_setting(
"--exact-series-matches-first",
default=True,
action=argparse.BooleanOptionalAction,
help="Puts series that are an exact match at the top of the list",
)
parser.add_setting(
"--always-use-publisher-filter",
default=False,
action=argparse.BooleanOptionalAction,
help="Enables the publisher filter",
)
parser.add_setting(
"--clear-form-before-populating",
default=False,
action=argparse.BooleanOptionalAction,
help="Clears all existing metadata when applying metadata from comic source",
)
def cbl(parser: settngs.Manager) -> None:

View File

@ -39,7 +39,7 @@ class RenameWindow(QtWidgets.QDialog):
comic_archive_list: list[ComicArchive],
data_style: int,
config: settngs.Config[settngs.Namespace],
talker: ComicTalker,
talkers: dict[str, ComicTalker],
) -> None:
super().__init__(parent)
@ -55,7 +55,7 @@ class RenameWindow(QtWidgets.QDialog):
)
self.config = config
self.talker = talker
self.talkers = talkers
self.comic_archive_list = comic_archive_list
self.data_style = data_style
self.rename_list: list[str] = []
@ -160,7 +160,7 @@ class RenameWindow(QtWidgets.QDialog):
self.twList.setSortingEnabled(True)
def modify_settings(self) -> None:
settingswin = SettingsWindow(self, self.config, self.talker)
settingswin = SettingsWindow(self, self.config, self.talkers)
settingswin.setModal(True)
settingswin.show_rename_tab()
settingswin.exec()

View File

@ -25,6 +25,7 @@ from typing import Any
import settngs
from PyQt5 import QtCore, QtGui, QtWidgets, uic
import comictaggerlib.ui.talkeruigenerator
from comicapi import utils
from comicapi.genericmetadata import md_test
from comictaggerlib.ctversion import version
@ -131,7 +132,7 @@ Spider-Geddon #1 - New Players; Check In
class SettingsWindow(QtWidgets.QDialog):
def __init__(
self, parent: QtWidgets.QWidget, config: settngs.Config[settngs.Namespace], talker: ComicTalker
self, parent: QtWidgets.QWidget, config: settngs.Config[settngs.Namespace], talkers: dict[str, ComicTalker]
) -> None:
super().__init__(parent)
@ -142,7 +143,7 @@ class SettingsWindow(QtWidgets.QDialog):
)
self.config = config
self.talker = talker
self.talkers = talkers
self.name = "Settings"
if platform.system() == "Windows":
@ -185,16 +186,21 @@ class SettingsWindow(QtWidgets.QDialog):
self.leRenameTemplate.setToolTip(f"<pre>{html.escape(template_tooltip)}</pre>")
self.rename_error: Exception | None = None
self.sources: dict = comictaggerlib.ui.talkeruigenerator.generate_source_option_tabs(
self.tComicTalkers, self.config, self.talkers
)
self.connect_signals()
self.settings_to_form()
self.rename_test()
self.dir_test()
# Set General as start tab
self.tabWidget.setCurrentIndex(0)
def connect_signals(self) -> None:
self.btnBrowseRar.clicked.connect(self.select_rar)
self.btnClearCache.clicked.connect(self.clear_cache)
self.btnResetSettings.clicked.connect(self.reset_settings)
self.btnTestKey.clicked.connect(self.test_api_key)
self.btnTemplateHelp.clicked.connect(self.show_template_help)
self.cbxMoveFiles.clicked.connect(self.dir_test)
self.leDirectory.textEdited.connect(self.dir_test)
@ -223,7 +229,6 @@ class SettingsWindow(QtWidgets.QDialog):
self.btnRemoveValueReplacement.clicked.disconnect()
self.btnResetSettings.clicked.disconnect()
self.btnTemplateHelp.clicked.disconnect()
self.btnTestKey.clicked.disconnect()
self.cbxChangeExtension.clicked.disconnect()
self.cbxComplicatedParser.clicked.disconnect()
self.cbxMoveFiles.clicked.disconnect()
@ -301,7 +306,7 @@ class SettingsWindow(QtWidgets.QDialog):
else:
self.leRarExePath.setEnabled(False)
self.sbNameMatchIdentifyThresh.setValue(self.config[0].identifier_series_match_identify_thresh)
self.sbNameMatchSearchThresh.setValue(self.config[0].talker_series_match_search_thresh)
self.sbNameMatchSearchThresh.setValue(self.config[0].identifier_series_match_search_thresh)
self.tePublisherFilter.setPlainText("\n".join(self.config[0].identifier_publisher_filter))
self.cbxCheckForNewVersion.setChecked(self.config[0].general_check_for_new_version)
@ -312,16 +317,10 @@ class SettingsWindow(QtWidgets.QDialog):
self.cbxRemovePublisher.setChecked(self.config[0].filename_remove_publisher)
self.switch_parser()
self.cbxUseSeriesStartAsVolume.setChecked(self.config[0].talker_comicvine_cv_use_series_start_as_volume)
self.cbxClearFormBeforePopulating.setChecked(self.config[0].talker_clear_form_before_populating)
self.cbxRemoveHtmlTables.setChecked(self.config[0].talker_comicvine_cv_remove_html_tables)
self.cbxUseFilter.setChecked(self.config[0].talker_always_use_publisher_filter)
self.cbxSortByYear.setChecked(self.config[0].talker_sort_series_by_year)
self.cbxExactMatches.setChecked(self.config[0].talker_exact_series_matches_first)
self.leKey.setText(self.config[0].talker_comicvine_comicvine_key)
self.leURL.setText(self.config[0].talker_comicvine_comicvine_url)
self.cbxClearFormBeforePopulating.setChecked(self.config[0].identifier_clear_form_before_populating)
self.cbxUseFilter.setChecked(self.config[0].identifier_always_use_publisher_filter)
self.cbxSortByYear.setChecked(self.config[0].identifier_sort_series_by_year)
self.cbxExactMatches.setChecked(self.config[0].identifier_exact_series_matches_first)
self.cbxAssumeLoneCreditIsPrimary.setChecked(self.config[0].cbl_assume_lone_credit_is_primary)
self.cbxCopyCharactersToTags.setChecked(self.config[0].cbl_copy_characters_to_tags)
@ -349,6 +348,10 @@ class SettingsWindow(QtWidgets.QDialog):
table.removeRow(i)
for row, replacement in enumerate(replacments):
self.insertRow(table, row, replacement)
# Set talker values
comictaggerlib.ui.talkeruigenerator.settings_to_talker_form(self.sources, self.config)
self.connect_signals()
def get_replacements(self) -> Replacements:
@ -418,7 +421,7 @@ class SettingsWindow(QtWidgets.QDialog):
self.config[0].general_check_for_new_version = self.cbxCheckForNewVersion.isChecked()
self.config[0].identifier_series_match_identify_thresh = self.sbNameMatchIdentifyThresh.value()
self.config[0].talker_series_match_search_thresh = self.sbNameMatchSearchThresh.value()
self.config[0].identifier_series_match_search_thresh = self.sbNameMatchSearchThresh.value()
self.config[0].identifier_publisher_filter = [
x.strip() for x in str(self.tePublisherFilter.toPlainText()).splitlines() if x.strip()
]
@ -428,16 +431,10 @@ class SettingsWindow(QtWidgets.QDialog):
self.config[0].filename_remove_fcbd = self.cbxRemoveFCBD.isChecked()
self.config[0].filename_remove_publisher = self.cbxRemovePublisher.isChecked()
self.config[0].talker_comicvine_cv_use_series_start_as_volume = self.cbxUseSeriesStartAsVolume.isChecked()
self.config[0].talker_clear_form_before_populating = self.cbxClearFormBeforePopulating.isChecked()
self.config[0].talker_comicvine_cv_remove_html_tables = self.cbxRemoveHtmlTables.isChecked()
self.config[0].talker_always_use_publisher_filter = self.cbxUseFilter.isChecked()
self.config[0].talker_sort_series_by_year = self.cbxSortByYear.isChecked()
self.config[0].talker_exact_series_matches_first = self.cbxExactMatches.isChecked()
self.config[0].talker_comicvine_comicvine_key = self.leKey.text().strip()
self.config[0].talker_comicvine_comicvine_url = self.leURL.text().strip()
self.config[0].identifier_clear_form_before_populating = self.cbxClearFormBeforePopulating.isChecked()
self.config[0].identifier_always_use_publisher_filter = self.cbxUseFilter.isChecked()
self.config[0].identifier_sort_series_by_year = self.cbxSortByYear.isChecked()
self.config[0].identifier_exact_series_matches_first = self.cbxExactMatches.isChecked()
self.config[0].cbl_assume_lone_credit_is_primary = self.cbxAssumeLoneCreditIsPrimary.isChecked()
self.config[0].cbl_copy_characters_to_tags = self.cbxCopyCharactersToTags.isChecked()
@ -459,6 +456,9 @@ class SettingsWindow(QtWidgets.QDialog):
self.config[0].rename_strict = self.cbxRenameStrict.isChecked()
self.config[0].rename_replacements = self.get_replacements()
# Read settings from talker tabs
comictaggerlib.ui.talkeruigenerator.form_settings_to_config(self.sources, self.config)
self.update_talkers_config()
settngs.save_file(self.config, self.config[0].runtime_config.user_config_dir / "settings.json")
@ -467,8 +467,8 @@ class SettingsWindow(QtWidgets.QDialog):
def update_talkers_config(self) -> None:
cfg = settngs.normalize_config(self.config, True, True)
if f"talker_{self.talker.id}" in cfg[0]:
self.talker.parse_settings(cfg[0][f"talker_{self.talker.id}"])
for talker, talker_obj in self.talkers.items():
talker_obj.parse_settings(cfg[0][f"talker_{talker}"])
def select_rar(self) -> None:
self.select_file(self.leRarExePath, "RAR")
@ -478,12 +478,6 @@ class SettingsWindow(QtWidgets.QDialog):
ComicCacher(self.config[0].runtime_config.user_cache_dir, version).clear_cache()
QtWidgets.QMessageBox.information(self, self.name, "Cache has been cleared.")
def test_api_key(self) -> None:
if self.talker.check_api_key(self.leKey.text().strip(), self.leURL.text().strip()):
QtWidgets.QMessageBox.information(self, "API Key Test", "Key is valid!")
else:
QtWidgets.QMessageBox.warning(self, "API Key Test", "Key is NOT valid.")
def reset_settings(self) -> None:
self.config = settngs.get_namespace(settngs.defaults(self.config[1]))
self.settings_to_form()

View File

@ -1354,7 +1354,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
QtWidgets.QMessageBox.warning(self, self.tr("Web Link"), self.tr("Web Link is invalid."))
def show_settings(self) -> None:
settingswin = SettingsWindow(self, self.config, self.current_talker())
settingswin = SettingsWindow(self, self.config, self.talkers)
settingswin.setModal(True)
settingswin.exec()
settingswin.result()
@ -2047,7 +2047,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
if self.dirty_flag_verification(
"File Rename", "If you rename files now, unsaved data in the form will be lost. Are you sure?"
):
dlg = RenameWindow(self, ca_list, self.load_data_style, self.config, self.current_talker())
dlg = RenameWindow(self, ca_list, self.load_data_style, self.config, self.talkers)
dlg.setModal(True)
if dlg.exec() and self.comic_archive is not None:
self.fileSelectionList.update_selected_rows()

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>702</width>
<height>513</height>
<height>559</height>
</rect>
</property>
<property name="windowTitle">
@ -28,7 +28,7 @@
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
<number>3</number>
</property>
<widget class="QWidget" name="tGeneral">
<attribute name="title">
@ -136,24 +136,14 @@
<string>Searching</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;These settings are for the automatic issue identifier which searches online for matches. &lt;/p&gt;&lt;p&gt;Hover the mouse over an entry field for more info.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="Line" name="line_2">
<item row="3" column="0">
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="6" column="0">
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
@ -252,6 +242,44 @@
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;These settings are for the automatic issue identifier which searches online for matches. &lt;/p&gt;&lt;p&gt;Hover the mouse over an entry field for more info.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="cbxExactMatches">
<property name="text">
<string>Initially show Series Name exact matches first</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="cbxSortByYear">
<property name="text">
<string>Initially sort Series search results by Starting Year instead of No. Issues</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="cbxClearFormBeforePopulating">
<property name="text">
<string>Clear Form Before Importing Comic Vine data</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tFilenameParser">
@ -308,197 +336,11 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tComicVine">
<widget class="QWidget" name="tComicTalkers">
<attribute name="title">
<string>Comic Vine</string>
<string>Metadata Sources</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QGroupBox" name="grpBoxCVTop">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="cbxUseSeriesStartAsVolume">
<property name="text">
<string>Use Series Start Date as Volume</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbxClearFormBeforePopulating">
<property name="text">
<string>Clear Form Before Importing Comic Vine data</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbxRemoveHtmlTables">
<property name="text">
<string>Remove HTML tables from CV summary field</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbxSortByYear">
<property name="text">
<string>Initially sort Series search results by Starting Year instead of No. Issues</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbxExactMatches">
<property name="text">
<string>Initially show Series Name exact matches first</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="grpBoxKey">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="lblKeyHelp">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;A personal API key from &lt;a href=&quot;http://www.comicvine.com/api/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Comic Vine&lt;/span&gt;&lt;/a&gt; is recommended in order to search for tag data. Login (or create a new account) there to get your key, and enter it below.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="btnTestKey">
<property name="text">
<string>Test Key</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="leKey">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="lblKey">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Comic Vine API Key</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lblURL">
<property name="text">
<string>Comic Vine URL</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="leURL"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
<layout class="QVBoxLayout" name="verticalLayout_4"/>
</widget>
<widget class="QWidget" name="tCBL">
<attribute name="title">
@ -774,25 +616,16 @@
<property name="text">
<string>Find</string>
</property>
<property name="textAlignment">
<set>AlignCenter</set>
</property>
</column>
<column>
<property name="text">
<string>Replacement</string>
</property>
<property name="textAlignment">
<set>AlignCenter</set>
</property>
</column>
<column>
<property name="text">
<string>Strict Only</string>
</property>
<property name="textAlignment">
<set>AlignCenter</set>
</property>
</column>
</widget>
</item>
@ -819,25 +652,16 @@
<property name="text">
<string>Find</string>
</property>
<property name="textAlignment">
<set>AlignCenter</set>
</property>
</column>
<column>
<property name="text">
<string>Replacement</string>
</property>
<property name="textAlignment">
<set>AlignCenter</set>
</property>
</column>
<column>
<property name="text">
<string>Strict Only</string>
</property>
<property name="textAlignment">
<set>AlignCenter</set>
</property>
</column>
</widget>
</item>

View File

@ -0,0 +1,238 @@
from __future__ import annotations
import argparse
import logging
from functools import partial
from typing import NamedTuple
import settngs
from PyQt5 import QtCore, QtWidgets
from comictalker.comictalker import ComicTalker
logger = logging.getLogger(__name__)
class TalkerTab(NamedTuple):
tab: QtWidgets.QWidget
widgets: dict[str, QtWidgets.QWidget]
def generate_api_widgets(
talker_id: str,
sources: dict[str, QtWidgets.QWidget],
config: settngs.Config[settngs.Namespace],
layout: QtWidgets.QGridLayout,
talkers: dict[str, ComicTalker],
):
# *args enforces keyword arguments and allows position arguments to be ignored
def call_check_api(*args, le_url: QtWidgets.QLineEdit, le_key: QtWidgets.QLineEdit, talker_id: str):
url = ""
key = ""
if le_key is not None:
key = le_key.text().strip()
if le_url is not None:
url = le_url.text().strip()
check_text, check_bool = talkers[talker_id].check_api_key(url, key)
if check_bool:
QtWidgets.QMessageBox.information(None, "API Test Success", check_text)
else:
QtWidgets.QMessageBox.warning(None, "API Test Failed", check_text)
# get the actual config objects in case they have overwritten the default
talker_key = config[1][f"talker_{talker_id}"][1][f"{talker_id}_key"]
talker_url = config[1][f"talker_{talker_id}"][1][f"{talker_id}_url"]
btn_test_row = None
le_key = None
le_url = None
# only file settings are saved
if talker_key.file:
# record the current row so we know where to add the button
btn_test_row = layout.rowCount()
le_key = generate_textbox(talker_key, layout)
# To enable setting and getting
sources["tabs"][talker_id].widgets[f"talker_{talker_id}_{talker_id}_key"] = le_key
# only file settings are saved
if talker_url.file:
# record the current row so we know where to add the button
# We overwrite so that the default will be next to the url text box
btn_test_row = layout.rowCount()
le_url = generate_textbox(talker_url, layout)
# To enable setting and getting
sources["tabs"][talker_id].widgets[f"talker_{talker_id}_{talker_id}_url"] = le_url
# The button row was recorded so we add it
if btn_test_row is not None:
btn = QtWidgets.QPushButton("Test API")
layout.addWidget(btn, btn_test_row, 2)
# partial is used as connect will pass in event information
btn.clicked.connect(partial(call_check_api, le_url=le_url, le_key=le_key, talker_id=talker_id))
def generate_checkbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QCheckBox:
widget = QtWidgets.QCheckBox(option.display_name)
widget.setToolTip(option.help)
layout.addWidget(widget, layout.rowCount(), 0, 1, -1)
return widget
def generate_spinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QSpinBox:
row = layout.rowCount()
lbl = QtWidgets.QLabel(option.display_name)
lbl.setToolTip(option.help)
layout.addWidget(lbl, row, 0)
widget = QtWidgets.QSpinBox()
widget.setRange(0, 9999)
widget.setToolTip(option.help)
layout.addWidget(widget, row, 1, alignment=QtCore.Qt.AlignLeft)
return widget
def generate_doublespinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QDoubleSpinBox:
row = layout.rowCount()
lbl = QtWidgets.QLabel(option.display_name)
lbl.setToolTip(option.help)
layout.addWidget(lbl, row, 0)
widget = QtWidgets.QDoubleSpinBox()
widget.setRange(0, 9999.99)
widget.setToolTip(option.help)
layout.addWidget(widget, row, 1, alignment=QtCore.Qt.AlignLeft)
return widget
def generate_textbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QLineEdit:
row = layout.rowCount()
lbl = QtWidgets.QLabel(option.display_name)
lbl.setToolTip(option.help)
layout.addWidget(lbl, row, 0)
widget = QtWidgets.QLineEdit()
widget.setToolTip(option.help)
layout.addWidget(widget, row, 1)
return widget
def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None:
# Set the active talker via id in sources combo box
sources["cbx_select_talker"].setCurrentIndex(sources["cbx_select_talker"].findData(config[0].talker_source))
for talker in sources["tabs"].items():
for name, widget in talker[1].widgets.items():
value = getattr(config[0], name)
value_type = type(value)
try:
if value_type is str:
widget.setText(value)
if value_type is int or value_type is float:
widget.setValue(value)
if value_type is bool:
widget.setChecked(value)
except Exception:
logger.debug("Failed to set value of %s", name)
def form_settings_to_config(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None:
# Source combo box value
config[0].talker_source = sources["cbx_select_talker"].currentData()
for tab in sources["tabs"].items():
for name, widget in tab[1].widgets.items():
widget_value = None
if isinstance(widget, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox)):
widget_value = widget.value()
elif isinstance(widget, QtWidgets.QLineEdit):
widget_value = widget.text().strip()
elif isinstance(widget, QtWidgets.QCheckBox):
widget_value = widget.isChecked()
setattr(config[0], name, widget_value)
def generate_source_option_tabs(
comic_talker_tab: QtWidgets.QWidget,
config: settngs.Config[settngs.Namespace],
talkers: dict[str, ComicTalker],
) -> dict[str, QtWidgets.QWidget]:
"""
Generate GUI tabs and settings for talkers
"""
# Store all widgets as to allow easier access to their values vs. using findChildren etc. on the tab widget
sources: dict = {"tabs": {}}
# Tab comes with a QVBoxLayout
comic_talker_tab_layout = comic_talker_tab.layout()
talker_layout = QtWidgets.QGridLayout()
lbl_select_talker = QtWidgets.QLabel("Metadata Source:")
cbx_select_talker = QtWidgets.QComboBox()
line = QtWidgets.QFrame()
line.setFrameShape(QtWidgets.QFrame.HLine)
line.setFrameShadow(QtWidgets.QFrame.Sunken)
talker_tabs = QtWidgets.QTabWidget()
talker_layout.addWidget(lbl_select_talker, 0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum)
talker_layout.addWidget(cbx_select_talker, 0, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum)
talker_layout.addWidget(line, 1, 0, 1, -1)
talker_layout.addWidget(talker_tabs, 2, 0, 1, -1)
comic_talker_tab_layout.addLayout(talker_layout)
# Add combobox to sources for getting and setting talker
sources["cbx_select_talker"] = cbx_select_talker
# Add source sub tabs to Comic Sources tab
for talker_id, talker_obj in talkers.items():
# Add source to general tab dropdown list
cbx_select_talker.addItem(talker_obj.name, talker_id)
tab_name = talker_id
sources["tabs"][tab_name] = TalkerTab(tab=QtWidgets.QWidget(), widgets={})
layout_grid = QtWidgets.QGridLayout()
for option in config[1][f"talker_{talker_id}"][1].values():
if not option.file:
continue
if option.dest in (f"{talker_id}_url", f"{talker_id}_key"):
continue
current_widget = None
if option.action is not None and (
option.action is argparse.BooleanOptionalAction
or option.type is bool
or option.action == "store_true"
or option.action == "store_false"
):
current_widget = generate_checkbox(option, layout_grid)
sources["tabs"][tab_name].widgets[option.internal_name] = current_widget
elif option.type is int:
current_widget = generate_spinbox(option, layout_grid)
sources["tabs"][tab_name].widgets[option.internal_name] = current_widget
elif option.type is float:
current_widget = generate_doublespinbox(option, layout_grid)
sources["tabs"][tab_name].widgets[option.internal_name] = current_widget
# option.type of None should be string
elif (option.type is None and option.action is None) or option.type is str:
current_widget = generate_textbox(option, layout_grid)
sources["tabs"][tab_name]("widget")[option.internal_name] = current_widget
else:
logger.debug(f"Unsupported talker option found. Name: {option.internal_name} Type: {option.type}")
# Add talker URL and API key fields
generate_api_widgets(talker_id, sources, config, layout_grid, talkers)
# Add vertical spacer
vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
layout_grid.addItem(vspacer, layout_grid.rowCount() + 1, 0)
# Display the new widgets
sources["tabs"][tab_name].tab.setLayout(layout_grid)
# Add new sub tab to Comic Source tab
talker_tabs.addTab(sources["tabs"][tab_name].tab, talker_obj.name)
return sources

View File

@ -119,7 +119,10 @@ class ComicTalker:
self.api_url = self.default_api_url = ""
def register_settings(self, parser: settngs.Manager) -> None:
"""Allows registering settings using the settngs package with an argparse like interface"""
"""
Allows registering settings using the settngs package with an argparse like interface
NOTE: The order used will be reflected within the settings menu
"""
return None
def parse_settings(self, settings: dict[str, Any]) -> dict[str, Any]:
@ -138,10 +141,15 @@ class ComicTalker:
self.api_url = self.default_api_url
return settings
def check_api_key(self, key: str, url: str) -> bool:
def check_api_key(self, url: str, key: str) -> tuple[str, bool]:
"""
This function should return true if the given api key and url are valid.
If the Talker does not use an api key it should validate that the url works.
This function should return a string with the test outcome for display to user and a bool.
"""
raise NotImplementedError
def check_api_url(self, url: str) -> bool:
"""
This function should return true if the given url is valid.
"""
raise NotImplementedError

View File

@ -173,6 +173,27 @@ class ComicVineTalker(ComicTalker):
self.wait_on_ratelimit_time: int = 20
def register_settings(self, parser: settngs.Manager) -> None:
parser.add_setting(
"--cv-use-series-start-as-volume",
default=False,
action=argparse.BooleanOptionalAction,
display_name="Use series start as volume",
help="Use the series start year as the volume number",
)
parser.add_setting(
"--cv-wait-on-ratelimit",
default=False,
action=argparse.BooleanOptionalAction,
display_name="Wait on ratelimit",
help="Wait when the rate limit is hit",
)
parser.add_setting(
"--cv-remove-html-tables",
default=False,
action=argparse.BooleanOptionalAction,
display_name="Remove HTML tables",
help="Removes html tables instead of converting them to text",
)
# The empty string being the default allows this setting to be unset, allowing the default to change
parser.add_setting(
f"--{self.id}-key",
@ -186,14 +207,6 @@ class ComicVineTalker(ComicTalker):
display_name="API URL",
help=f"Use the given Comic Vine URL. (default: {self.default_api_url})",
)
parser.add_setting("--cv-use-series-start-as-volume", default=False, action=argparse.BooleanOptionalAction)
parser.add_setting("--cv-wait-on-ratelimit", default=False, action=argparse.BooleanOptionalAction)
parser.add_setting(
"--cv-remove-html-tables",
default=False,
action=argparse.BooleanOptionalAction,
help="Removes html tables instead of converting them to text.",
)
def parse_settings(self, settings: dict[str, Any]) -> dict[str, Any]:
settings = super().parse_settings(settings)
@ -203,7 +216,7 @@ class ComicVineTalker(ComicTalker):
self.remove_html_tables = settings["cv_remove_html_tables"]
return settings
def check_api_key(self, key: str, url: str) -> bool:
def check_api_key(self, url: str, key: str) -> tuple[str, bool]:
url = talker_utils.fix_url(url)
if not url:
url = self.default_api_url
@ -217,9 +230,12 @@ class ComicVineTalker(ComicTalker):
).json()
# Bogus request, but if the key is wrong, you get error 100: "Invalid API Key"
return cv_response["status_code"] != 100
if cv_response["status_code"] != 100:
return "The API key is valid", True
else:
return "The API key is INVALID!", False
except Exception:
return False
return "Failed to connect to the URL!", False
def search_for_series(
self,