# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__   = 'GPL v3'
__copyright__ = '2014,2015,2016, DaltonST <DaltonShiTzu@outlook.com>'
__my_version__ = "3.6.98"
from PyQt5.Qt import QMenu, QDialog, QIcon, QAction, QVBoxLayout, QPushButton, QMessageBox, QLabel, QWidget, QRegularExpression
import os, sys, sqlite3
import apsw
from calibre.library.database import LibraryDatabase
from calibre.constants import filesystem_encoding, preferred_encoding, iswindows
from calibre import isbytestring, force_unicode, prints
from calibre.db.backend import DB
from calibre.gui2 import info_dialog, question_dialog, error_dialog, Dispatcher
from calibre.gui2.actions import InterfaceAction
from calibre.gui2.dialogs import message_box
from calibre.ebooks.metadata.meta import set_metadata
from calibre.ebooks.metadata.book.base import Metadata
from functools import partial
from Queue import Queue
from calibre.utils.logging import Log as mylog
import unicodedata
from calibre_plugins.quarantine_and_scrub.config import prefs
from calibre_plugins.quarantine_and_scrub.config import ConfigWidget
from calibre_plugins.quarantine_and_scrub.configdialog import ConfigDialog
from calibre_plugins.quarantine_and_scrub.tag_rules_list_editor import TagRulesListEditor
from calibre_plugins.quarantine_and_scrub.tag_rules_list_editor_ui import Ui_TagRulesListEditor
from calibre_plugins.quarantine_and_scrub.tag_rules_list_editor_2 import TagRulesListEditor2
from calibre_plugins.quarantine_and_scrub.tag_rules_list_editor_ui_2 import Ui_TagRulesListEditor2
from calibre_plugins.quarantine_and_scrub.ui_toastdialog import UIToastDialog
from calibre_plugins.quarantine_and_scrub.workauthorchangedialog import WorkAuthorChangeDialog
from calibre_plugins.quarantine_and_scrub.workseriesadddialog import WorkSeriesAddDialog
from calibre_plugins.quarantine_and_scrub.workseriesdestroydialog import WorkSeriesDestroyDialog
from calibre_plugins.quarantine_and_scrub.workseriesreplacedialog import WorkSeriesReplaceDialog
from calibre_plugins.quarantine_and_scrub.workseriesindexchangedialog import WorkSeriesIndexChangeDialog
from calibre_plugins.quarantine_and_scrub.worktagadddialog import WorkTagAddDialog
from calibre_plugins.quarantine_and_scrub.worktagdestroydialog import WorkTagDestroyDialog
from calibre_plugins.quarantine_and_scrub.worktagreplacedialog import WorkTagReplaceDialog
from calibre_plugins.quarantine_and_scrub.worktitlechangedialog import WorkTitleChangeDialog
from calibre_plugins.quarantine_and_scrub.autopopchoicesdialog import AutoPopChoicesDialog
from calibre_plugins.quarantine_and_scrub.seriesconsolchoicesdialog import SeriesConsolChoicesDialog
from calibre_plugins.quarantine_and_scrub.common_utils import (set_plugin_icon_resources, get_icon,
                                                                                                            create_menu_action_unique,
                                                                                                            choose_qs_service_pack_database)
from calibre_plugins.quarantine_and_scrub.jobs import  (start_threaded_book_level, start_threaded_util_copy_original_metadata,
                                                start_threaded_author_level, start_threaded_util_copy_pristine_metadata, start_threaded_series_level,
                                                start_threaded_miscellany, start_threaded_derive_genres,
                                                start_threaded_purge_work_data, start_threaded_create_sqlite_objects,
                                                start_threaded_import_from_qs_service_pack)
from calibre_plugins.quarantine_and_scrub.classify_web_service_api import  oclc_classify_api
import zipfile
import subprocess
from copy import deepcopy
from calibre.db.cache import Cache as dbcache
from time import sleep
import webbrowser
PLUGIN_ICONS = ['images/quarantineicon.png','images/view_refresh.png','images/idea.png','images/good.png','images/import.png',\
                            'images/readinstructionsicon.png','images/copy.png','images/qs_config.png','images/tags.png','images/ddc.png',\
                            'images/genre.png','images/ddclccicon.png','images/plus.png','images/search_and_replace.png','images/change.png',\
                            'images/tag_rules_menu.png','images/copy_circle.png','images/series.png','images/swap.png','images/wrench-hammer.png',\
                            'images/spraybottleicon.png','images/greenquarantineicon.png','images/sqldbcustomize.png','images/minus.png',\
                            'images/download.png','images/rename.png', 'images/update_cc.png','images/consolidate.png','images/minimize.png',
                            'images/propagate.png']
class QuarantineAndScrub(InterfaceAction):
    name = 'QuarantineAndScrub'
    action_spec = (_('Q+S'), None, _('Scrub Metadata for Books in the QuarantineAndScrub Special Library'), ())
    action_type = 'current'
    accepts_drops = False
    auto_repeat = False
    priority = 9
    popup_type = 1
    my_plugin_path = "unknown"
    documentation_path = "unknown"
    library_is_quarantine_db = False
    guidb = "unknown"
    work_book_ids_list = []
    book_ids = []
    scrubbed_books_final = []
    selected_book_list = []
    def genesis(self):
        global my_plugin_path
        my_plugin_path = self.plugin_path
        self.is_library_selected = True
        self.menu = QMenu(self.gui)
        icon_resources = self.load_resources(PLUGIN_ICONS)
        set_plugin_icon_resources(self.name, icon_resources )
        gui = self.gui
        self.rebuild_menus(gui)
        self.qaction.setMenu(self.menu)
        self.qaction.setIcon(get_icon(PLUGIN_ICONS[0]))
        self.qaction.triggered.connect(self.create_ui_toast_dialog_for_main_icon)
        global work_book_ids_list
    def initialization_complete(self):
        global work_book_ids_list
        self.my_jobs_dialog_object = "unknown"
        try:
            sre = QRegularExpression(".+")
            answer  = self.gui.findChildren(QWidget,sre)
            if answer:
                if isinstance(answer,list):
                    for item in answer:
                        if item:
                            s = str(item)
                            if "JobsDialog" in s:
                                self.my_jobs_dialog_object = item
                                return
        except:
            self.my_jobs_dialog_object = "unknown"
    def ensure_correct_library(self, guidb):
        global library_is_quarantine_db
        db = guidb
        path = db.library_path
        s = str(path)
        n = s.find("QuarantineAndScrub")
        if n < 0:
            library_is_quarantine_db = False
            self.create_ui_toast_dialog(0)
            sleep(3.0)
        else:
            library_is_quarantine_db = True
        try:
            self.ui_toast_dialog.close()
        except:
            pass
    def library_changed(self, guidb):
        self.reset_guidb()
        global work_book_ids_list
        work_book_ids_list = []
        gui = self.gui
        self.rebuild_menus(gui)
        self.qaction.setMenu(self.menu)
        self.qaction.setIcon(get_icon(PLUGIN_ICONS[0]))
        try:
            self.worktagdestroy_dialog.close()
        except:
            pass
        try:
            self.worktagadd_dialog.close()
        except:
            pass
        try:
            self.worktagreplace_dialog.close()
        except:
            pass
        try:
            self.workseriesreplace_dialog.close()
        except:
            pass
        try:
            self.workseriesdestroy_dialog.close()
        except:
            pass
        try:
            self.workseriesadd_dialog.close()
        except:
            pass
        try:
            self.workseriesindexchange_dialog.close()
        except:
            pass
        try:
            self.worktitlechange_dialog.close()
        except:
            pass
        try:
            self.workauthorchange_dialog.close()
        except:
            pass
        try:
            self.ui_toast_dialog.close()
        except:
            pass
    def reset_guidb(self):
        global guidb
        guidb = self.gui.library_view.model().db
    def rebuild_menus(self, gui):
        m = self.menu
        m.clear()
        m.setTearOffEnabled(True)
        m.setWindowTitle('Quarantine And Scrub')
        m.addSeparator()
        create_menu_action_unique(self, m, 'Read Q&&S Instructions/User Guide', 'images/readinstructionsicon.png',
                      triggered=partial(self.view_user_instructions))
        m.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        create_menu_action_unique(self, m, 'Configure Q&&S [Path to Pristine Library; Maximum for Tag Minimizer; More]', 'images/qs_config.png',
                              triggered=partial(self.config))
        m.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        create_menu_action_unique(self, m, 'After Version Upgrade: Create New SQLite Database Objects [Important]', 'images/sqldbcustomize.png',
                              triggered=partial(self.create_sqlite_objects_begin_dialogs_with_user))
        m.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        self.m5 = QMenu(_('[Menu] Q&&S Pristine Validation Data Maintenance'))
        self.m5_action = m.addMenu(self.m5)
        self.m5.setIcon(get_icon('images/copy_circle.png'))
        self.m5.setTearOffEnabled(True)
        self.m5.setWindowTitle('Quarantine And Scrub: Pristine Validation Data Maintenance')
        self.m5.addSeparator()
        create_menu_action_unique(self, self.m5, 'Purge Pristine Authors/Series Validation Tables in Q&&S [If Not Really 100% Clean]', 'images/minus.png',
                              triggered=partial(self.purge_pristine_tables))
        self.m5.addSeparator()
        create_menu_action_unique(self, self.m5, ' ', ' ',
                              triggered=None)
        self.m5.addSeparator()
        create_menu_action_unique(self, self.m5, 'Copy Pristine Library Author/Series Tables to Q&&S as Validation Data [Only If 100% Clean]', 'images/copy_circle.png',
                              triggered=partial(self.copy_pristine_level_begin_dialogs_with_user))
        self.m5.addSeparator()
        create_menu_action_unique(self, self.m5, ' ', ' ',
                              triggered=None)
        self.m5.addSeparator()
        create_menu_action_unique(self, self.m5, 'Copy Valid Q&&S Real Authors to Q&&S Pristine Author Validation Table [Selected Books]', 'images/good.png',
                              triggered=partial(self.copy_real_author_to_pristine_author_table))
        self.m5.addSeparator()
        create_menu_action_unique(self, self.m5, ' ', ' ',
                              triggered=None)
        self.m5.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        self.m4 = QMenu(_('[Menu] Q&&S Purge Work Data'))
        self.m4_action = m.addMenu(self.m4)
        self.m4.setIcon(get_icon('images/minus.png'))
        self.m4.setTearOffEnabled(True)
        self.m4.setWindowTitle('Quarantine And Scrub: Purge Work Data')
        self.m4.addSeparator()
        create_menu_action_unique(self, self.m4, 'Purge All Work Data [All Books] [Routine Use]', 'images/minus.png',
                              triggered=partial(self.purge_work_data_begin_dialogs_with_user))
        self.m4.addSeparator()
        create_menu_action_unique(self, self.m4, ' ', ' ',
                              triggered=None)
        self.m4.addSeparator()
        create_menu_action_unique(self, self.m4, 'Purge All Work Data [Selected Books (Max=100)] [Spot Cleaning]', 'images/minus.png',
                              triggered=partial(self.purge_work_data_selected_books))
        self.m4.addSeparator()
        create_menu_action_unique(self, self.m4, ' ', ' ',
                              triggered=None)
        self.m4.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        self.m3 = QMenu(_('[Menu] Q&&S Copy Real to Work'))
        self.m3_action = m.addMenu(self.m3)
        self.m3.setIcon(get_icon('images/view_refresh.png'))
        self.m3.setTearOffEnabled(True)
        self.m3.setWindowTitle('Quarantine And Scrub: Copy Real to Work')
        self.m3.addSeparator()
        create_menu_action_unique(self, self.m3, 'Copy Q&&S Only Dirty Tags to Work Tags [Selected Books][For Standalone Tag Scrubber]', 'images/tags.png',
                              triggered=partial(self.copy_selected_tags_only_begin_dialogs_with_user))
        self.m3.addSeparator()
        create_menu_action_unique(self, self.m3, ' ', ' ',
                              triggered=None)
        self.m3.addSeparator()
        create_menu_action_unique(self, self.m3, 'Copy Q&&S All Dirty Data to Work Data [All Books] [Routine Use]', 'images/view_refresh.png',
                              triggered=partial(self.copy_work_level_begin_dialogs_with_user))
        self.m3.addSeparator()
        create_menu_action_unique(self, self.m3, 'Copy Q&&S All Dirty Data to Work Data [Selected Books (Max=100)] [Spot Cleaning]', 'images/view_refresh.png',
                              triggered=partial(self.copy_selected_work_level))
        self.m3.addSeparator()
        create_menu_action_unique(self, self.m3, ' ', ' ',
                              triggered=None)
        self.m3.addSeparator()
        create_menu_action_unique(self, self.m3, 'Copy Q&&S Only Dirty Series to Work Series [Selected Books][Work Author && Work Title Required]', 'images/series.png',
                              triggered=partial(self.copy_selected_series_only_begin_dialogs_with_user))
        self.m3.addSeparator()
        create_menu_action_unique(self, self.m3, ' ', ' ',
                              triggered=None)
        self.m3.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        create_menu_action_unique(self, m, 'Scrub Work Data at Author Level [All Books]', 'images/quarantineicon.png',
                              triggered=partial(self.scrub_begin_dialog_author_level))
        m.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        create_menu_action_unique(self, m, 'Scrub Work Data at Book Level [Selected Books]  [Confirmation]', 'images/quarantineicon.png',
                              triggered=partial(self.scrub_begin_dialog_book_level))
        m.addSeparator()
        create_menu_action_unique(self, m, 'Scrub Work Data at Book Level [Selected Books]  [Immediate]', 'images/quarantineicon.png',
                              triggered=partial(self.scrub_selected_book_level))
        m.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        self.m2 = QMenu(_('[Menu] Q&&S Scrub at Series Level'))
        self.m2_action = m.addMenu(self.m2)
        self.m2.setIcon(get_icon('images/quarantineicon.png'))
        self.m2.setTearOffEnabled(True)
        self.m2.setWindowTitle('Quarantine And Scrub: Scrub at Series Level')
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Rename Fiction Work Series Names Using Existing Historical Web Series Names [All]', 'images/rename.png',
                              triggered=partial(self._start_miscellany_option_5_standalone))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, ' ', ' ',
                              triggered=None)
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Consolidate Work Series at Series Level [All Books]  [What-If Only]', 'images/consolidate.png',
                              triggered=partial(self.scrub_begin_dialog_series_level_whatif))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Consolidate Work Series at Series Level [All Books]  [Actual]', 'images/consolidate.png',
                              triggered=partial(self.scrub_begin_dialog_series_level))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, ' ', ' ',
                              triggered=None)
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Validate Work Series/Titles/Indexes Via Web Source [Fiction Series of Selected Books]', 'images/download.png',
                              triggered=partial(self._start_miscellany_option_4_standalone))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, ' ', ' ',
                              triggered=None)
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Rename Work Series Name to Web Series Name [Previously Validated Fiction Series Only]', 'images/rename.png',
                              triggered=partial(self._start_miscellany_option_5_standalone))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, ' ', ' ',
                              triggered=None)
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Download Web Series/Titles/Indexes from Web Source for All Fiction Series for a Single Real Author [Selected Real Author]', 'images/download.png',
                              triggered=partial(self._start_miscellany_option_14_standalone))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, ' ', ' ',
                              triggered=None)
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Add a Specific Work Series Name && Index [Selected Books]', 'images/plus.png',
                              triggered=partial(self.work_series_add_dialog))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, ' ', ' ',
                              triggered=None)
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Change a Specific Work Series Index [Single Book]', 'images/change.png',
                              triggered=partial(self.work_series_index_change_dialog))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, ' ', ' ',
                              triggered=None)
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Search && Replace Specific Work Series Name [Selected Books]', 'images/search_and_replace.png',
                              triggered=partial(self.work_series_replace_dialog))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, ' ', ' ',
                              triggered=None)
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Search && Destroy Specific Work Series Name [Selected Books]', 'images/minus.png',
                              triggered=partial(self.work_series_destroy_dialog))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, ' ', ' ',
                              triggered=None)
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, 'Swap Work Title and Work Series  (Must Have Both)  [Selected Books]', 'images/swap.png',
                              triggered=partial(self._swap_work_title_and_work_series))
        self.m2.addSeparator()
        create_menu_action_unique(self, self.m2, ' ', ' ',
                              triggered=None)
        self.m2.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        self.m0 = QMenu(_('[Menu] Q&&S Scrub Work Tags Using Power Tools'))
        self.m0_action = m.addMenu(self.m0)
        self.m0.setIcon(get_icon('images/quarantineicon.png'))
        self.m0.setTearOffEnabled(True)
        self.m0.setWindowTitle('Quarantine And Scrub: Scrub Work Tags Using Power Tools')
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, 'Propagate Tags from/to Books with the Identical Series', 'images/propagate.png',
                              triggered=partial(self._start_miscellany_option_1_standalone))
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, ' ', ' ',
                              triggered=None)
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, "Change Selected Work Tags to Default Tag of 'Real' Author [Selected Books]", 'images/change.png',
                              triggered=partial(self._start_miscellany_option_11_standalone))
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, ' ', ' ',
                              triggered=None)
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, 'Book Level Tag Scrubber [Selected Books]', 'images/quarantineicon.png',
                              triggered=partial(self._start_miscellany_option_3_standalone))
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, ' ', ' ',
                              triggered=None)
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, 'Minimize Work Tags Per Book Using Tag Priorities [Selected Books]', 'images/minimize.png',
                              triggered=partial(self._start_miscellany_option_9_standalone))
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, ' ', ' ',
                              triggered=None)
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, 'Add a Single Work Tag [Selected Books]', 'images/plus.png',
                              triggered=partial(self.work_tag_add_dialog))
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, ' ', ' ',
                              triggered=None)
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, 'Search && Replace Specific Work Tag [Selected Books]', 'images/search_and_replace.png',
                              triggered=partial(self.work_tag_replace_dialog))
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, ' ', ' ',
                              triggered=None)
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, 'Search && Destroy Specific Work Tag [Selected Books]', 'images/minus.png',
                              triggered=partial(self.work_tag_destroy_dialog))
        self.m0.addSeparator()
        create_menu_action_unique(self, self.m0, ' ', ' ',
                              triggered=None)
        self.m0.addSeparator()
        m.addSeparator()
        self.m6 = QMenu(_('[Menu] Q&&S Tag Rules Tables Maintenance'))
        self.m6_action = m.addMenu(self.m6)
        self.m6.setIcon(get_icon('images/tag_rules_menu.png'))
        self.m6.setTearOffEnabled(True)
        self.m6.setWindowTitle('Quarantine And Scrub: Tag Rules Tables Maintenance')
        self.m6.addSeparator()
        create_menu_action_unique(self, self.m6, 'Auto-Populate Tags-by-Comment Table Using Tag Rules and Comments', 'images/tags.png',
                              triggered=partial(self.autopopulate_tags_by_comment))
        self.m6.addSeparator()
        create_menu_action_unique(self, self.m6, ' ', ' ',
                              triggered=None)
        self.m6.addSeparator()
        create_menu_action_unique(self, self.m6, "Tag Scrubber: 'Easy-Add' New 'Real' Tags to Purge to Tag Rules Table", 'images/tags.png',
                              triggered=partial(self._start_miscellany_option_6_standalone))
        self.m6.addSeparator()
        create_menu_action_unique(self, self.m6, ' ', ' ',
                              triggered=None)
        self.m6.addSeparator()
        create_menu_action_unique(self, self.m6, "Tag Scrubber: 'Easy-Add' Work Tags-to-NEWTAG Mappings to Tag Rules Table", 'images/tags.png',
                              triggered=partial(self._start_miscellany_option_7_standalone))
        self.m6.addSeparator()
        create_menu_action_unique(self, self.m6, ' ', ' ',
                              triggered=None)
        self.m6.addSeparator()
        create_menu_action_unique(self, self.m6, "Change Default Tag of 'Real' Author to Selected Work Tag [Selected Books]", 'images/change.png',
                              triggered=partial(self._start_miscellany_option_12_standalone))
        self.m6.addSeparator()
        create_menu_action_unique(self, self.m6, ' ', ' ',
                              triggered=None)
        self.m6.addSeparator()
        m.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        self.m7 = QMenu(_('[Menu] Q&S Special Tools'))
        self.m7_action = m.addMenu(self.m7)
        self.m7.setIcon(get_icon('images/wrench-hammer.png'))
        self.m7.setTearOffEnabled(True)
        self.m7.setWindowTitle('Quarantine And Scrub: Special Tools')
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, 'Change Work Author (Green Only) [Single Book Only] [Spot Cleaning]', 'images/change.png',
                              triggered=partial(self.work_author_change_dialog))
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, ' ', ' ',
                              triggered=None)
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, 'Change Work Title [Single Book Only] [Spot Cleaning]', 'images/change.png',
                              triggered=partial(self.work_title_change_dialog))
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, ' ', ' ',
                              triggered=None)
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, 'Update Pseudonym Custom Column Using Real Authors [Selected Books]', 'images/update_cc.png',
                              triggered=partial(self._start_miscellany_option_16_standalone))
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, ' ', ' ',
                              triggered=None)
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, 'Update Custom Column(s) per Table "Tags CC Mapping Control" [Selected Books]', 'images/update_cc.png',
                              triggered=partial(self._start_miscellany_option_15_standalone))
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, ' ', ' ',
                              triggered=None)
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, 'Delete Non-ISBN/ISSN/OCLC Identifiers [Selected Books]', 'images/minus.png',
                              triggered=partial(self._start_miscellany_option_2_standalone))
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, ' ', ' ',
                              triggered=None)
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, 'Copy Tag && Title && Other Rules plus WSSVD From/To Previously Configured Q&&S Libraries', 'images/copy_circle.png',
                              triggered=partial(self._start_miscellany_option_10_standalone))
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, ' ', ' ',
                              triggered=None)
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, 'Pristine Library: Reset Last-Modified Date to (Now - 24 hours) [All Books Modified in Last 24 Hours]', 'images/spraybottleicon.png',
                              triggered=partial(self._start_miscellany_option_8_standalone))
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, ' ', ' ',
                              triggered=None)
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, 'Import Q&&S Reference Data from Service Pack .db', 'images/import.png',
                              triggered=partial(self._import_validation_data_from_qs_service_pack_db))
        self.m7.addSeparator()
        create_menu_action_unique(self, self.m7, ' ', ' ',
                              triggered=None)
        self.m7.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        self.m1 = QMenu(_('[Menu] Q&&S Copy Work to Real'))
        self.m1_action = m.addMenu(self.m1)
        self.m1.setIcon(get_icon('images/good.png'))
        self.m1.setTearOffEnabled(True)
        self.m1.setWindowTitle('Quarantine And Scrub: Copy Work to Real')
        self.m1.addSeparator()
        create_menu_action_unique(self, self.m1, 'Copy WorkAuthor to Author                     [Selected Books]', 'images/good.png',
                              triggered=partial(self.copy_work_author_to_author))
        self.m1.addSeparator()
        create_menu_action_unique(self, self.m1, 'Copy WorkTitle to Title                              [Selected Books]', 'images/good.png',
                              triggered=partial(self.copy_work_title_to_title))
        self.m1.addSeparator()
        create_menu_action_unique(self, self.m1, 'Copy WorkSeries to Series                         [Selected Books]', 'images/good.png',
                              triggered=partial(self.copy_work_series_to_series))
        self.m1.addSeparator()
        create_menu_action_unique(self, self.m1, 'Copy WorkSeries Index to Series Index    [Selected Books]', 'images/good.png',
                              triggered=partial(self.copy_work_series_number_to_series_index))
        self.m1.addSeparator()
        create_menu_action_unique(self, self.m1, 'Copy WorkTags to Tags                             [Selected Books]', 'images/good.png',
                              triggered=partial(self.copy_work_tags_to_tags))
        self.m1.addSeparator()
        create_menu_action_unique(self, self.m1, ' ', ' ',
                              triggered=None)
        self.m1.addSeparator()
        create_menu_action_unique(self, self.m1, 'Copy All Work to Real Metadata              [Single Book Only]', 'images/good.png',
                              triggered=partial(self.copy_all_work_to_all_real))
        self.m1.addSeparator()
        create_menu_action_unique(self, self.m1, ' ', ' ',
                              triggered=None)
        self.m1.addSeparator()
        create_menu_action_unique(self, self.m1, 'Copy Valid Q&&S Real Authors to Q&&S Pristine Author Validation Table [Selected Books]', 'images/good.png',
                              triggered=partial(self.copy_real_author_to_pristine_author_table))
        self.m1.addSeparator()
        create_menu_action_unique(self, self.m1, ' ', ' ',
                              triggered=None)
        self.m1.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        create_menu_action_unique(self, m, 'Derive Genres [Q&&S] [Selected Books]', 'images/genre.png',
                              triggered=partial(self.scrub_begin_dialog_derive_genres))
        m.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        create_menu_action_unique(self, m, 'Classify: DDC && LCC  [Selected Books]', 'images/ddclccicon.png',
                              triggered=partial(self.populate_ddc_lcc_using_classify_api))
        m.addSeparator()
        create_menu_action_unique(self, m, ' ', ' ',
                              triggered=None)
        m.addSeparator()
        self.gui.keyboard.finalize()
        qs_stylesheet = "QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"  # the '#' in the stylesheet will cause sed.exe in the .bat to strip what follows it during the build...
        m.setStyleSheet(qs_stylesheet)
        m.setToolTip("<p style='white-space:wrap'>The menu items in the Q&S Main Menu are meant to be generally used in a 'top-to-bottom' sequence. \
                                                                                <br><br>[1] Purge All Work Data [Routine Use].\
                                                                                <br><br>[2] Copy Real Data to Work Data [Routine Use].\
                                                                                <br><br>[3] Scrub Work Data at Author-Level.\
                                                                                <br><br>[4] Scrub Work Data at Book-Level.\
                                                                                <br><br>[5] Scrub Work Data at Series-Level.\
                                                                                <br><br>[6] Perform Spot-Cleaning as Desired.\
                                                                                <br><br>[7] Copy Work Data to Real Data.\
                                                                                <br><br>[8] Purge All Work Data [Routine Use].\
                                                                                <br><br>[9] Derive Genres.\
                                                                                <br><br>[10] Derive Library Codes.\
                                                                                <br><br>[11] Move your Cleaned Books from Q&S to your 'Real' Calibre Pristine Library.\
                                                                                <br><br>Refer to the User Guide for details and examples.\
                                                                                <br><br>Use Q&S in small batches of ~150 or fewer Books.")
        self.m0.setStyleSheet(qs_stylesheet)
        self.m0.setToolTip("<p style='white-space:wrap'>This submenu is used to scrub Work Tags using power tools.")
        self.m1.setStyleSheet(qs_stylesheet)
        self.m1.setToolTip("<p style='white-space:wrap'>This submenu is used to copy Work Data to Real Data.  Copy all, some, or none for all, some, or no Books.  Pick and choose as you wish.")
        self.m2.setStyleSheet(qs_stylesheet)
        self.m2.setToolTip("<p style='white-space:wrap'>This submenu is used to scrub Work Data at the Series-Level.")
        self.m3.setStyleSheet(qs_stylesheet)
        self.m3.setToolTip("<p style='white-space:wrap'>This submenu is used to copy Real Data to Work Data.")
        self.m4.setStyleSheet(qs_stylesheet)
        self.m4.setToolTip("<p style='white-space:wrap'>This submenu is used to Purge Work Data.")
        self.m5.setStyleSheet(qs_stylesheet)
        self.m5.setToolTip("<p style='white-space:wrap'>This submenu is used to copy Pristine Authors and Pristine Series into Q&S for use as validation data.")
        self.m6.setStyleSheet(qs_stylesheet)
        self.m6.setToolTip("<p style='white-space:wrap'>This submenu is used to maintain the Tag Rules Tables.")
        self.m7.setStyleSheet(qs_stylesheet)
        self.m7.setToolTip("<p style='white-space:wrap'>This submenu is used to perform spot-cleaning and other specialized functions.")
    def scrub_begin_dialog_author_level(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            book_ids_list = []
            self._question_then_go_author_level(book_ids_list)
    def scrub_begin_dialog_book_level(self):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            n = len(book_ids_list)
            if  n <= 0:
                return error_dialog(self.gui, _('Cannot Scrub Metadata'),
                                                          _('You must select one or more books to perform this action.'), show=True)
            else:
                work_book_ids_list = deepcopy(book_ids_list)
                self._question_then_go_book_level(book_ids_list)
    def _question_then_go_book_level(self, book_ids_list):
        global scrubbed_books_final
        global guidb
        global library_is_quarantine_db
        global book_ids
        global my_plugin_path
        scrubbed_books_final = {}
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            tmp_list = []
            for item in book_ids_list:
                s = str(item['calibre_id'])
                tmp_list.append(s)
            del book_ids_list
            tmp_set = set(tmp_list)
            book_ids = list(tmp_set)
            del tmp_list
            del tmp_set
            payload = book_ids
            n = len(book_ids)
            if not n == 0:
                if question_dialog(self.gui, "QuarantineAndScrub", (str(n) + "  Book(s) Have Been Selected for Book-Level Scrubbing \
                                                                        <br><br><font color='#0404B4'>[Author-Level Must Always Be Run Prior to Initial Book-Level] \
                                                                        <br><br>[Author-Level Must Never Be Run After a Previous Author-Level Without First Refreshing All Work Data]</font>\
                                                                        <br><br>GUI Will Be Locked While Job Runs <br><br>Continue with Book-Level? ")):
                    start_threaded_book_level(self, self.gui, book_ids, scrubbed_books_final, my_plugin_path, Dispatcher(self._scrub_book_level_no_restart))
                    self.create_ui_toast_dialog_for_jobs()
                else:
                     info_dialog(self.gui, 'Canceled',
                         'QuarantineAndScrub is Canceled').show()
        if self.my_jobs_dialog_object:
            if self.my_jobs_dialog_object <> "unknown":
                try:
                    self.my_jobs_dialog_object.show()
                except:
                    pass
    def _question_then_go_author_level(self, book_ids_list):
        global scrubbed_books_final
        global guidb
        global  library_is_quarantine_db
        scrubbed_books_final = []
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            tmp_list = []
            for item in book_ids_list:
                s = str(item['calibre_id'])
                tmp_list.append(s)
            del book_ids_list
            tmp_set = set(tmp_list)
            book_ids = list(tmp_set)
            del tmp_list
            del tmp_set
            payload = book_ids
            if question_dialog(self.gui, "QuarantineAndScrub", ("  Author-Level Scrubbing Is for All Books\
                                                    <br><br>[Author-Level Must Be Run Prior to Initial Book-Level]\
                                                    <br><br><font color='#0404B4'><b>[Never Rerun This Job Once It Has Already Been Run Without First Purging All Work Data] \
                                                    <br><br><font color='#FF0000'>[No Book Statuses of 'auth_ok' Should Exist At This Moment Or Job Will Terminate Immediately]</b> </font>\
                                                    <br><br>GUI Will Be Locked While Job Runs <br><br>Continue?")):
                start_threaded_author_level(self, self.gui, book_ids, scrubbed_books_final, Dispatcher(self._scrub_author_level_no_restart))
                self.create_ui_toast_dialog_for_jobs()
            else:
                 info_dialog(self.gui, 'Canceled',
                     'Author-Level Scrubbing is Canceled').show()
        if self.my_jobs_dialog_object:
            if self.my_jobs_dialog_object <> "unknown":
                try:
                    self.my_jobs_dialog_object.show()
                except:
                    pass
    def scrub_begin_dialog_series_level(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            book_ids_list = []
            self._question_then_go_series_level("1")
    def _question_then_go_series_level(self, run_type):
        global scrubbed_books_final
        global guidb
        global library_is_quarantine_db
        scrubbed_books_final = []
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        if run_type == "0":
            s_msg = "Series-Level Consolidation/Renaming:  What-if Only.  No Updates."
        else:
            s_msg = "Series-Level Consolidation/Renaming:  Actual Updates"
        self.series_choices_dialog = SeriesConsolChoicesDialog(self.gui,self.qaction.icon(),guidb,run_type,s_msg,self._question_then_go_series_level_part2)
        self.series_choices_dialog.show()
    def _question_then_go_series_level_part2(self, guidb,chosen_option,run_type):
        global scrubbed_books_final
        global library_is_quarantine_db
        scrubbed_books_final = []
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            my_dummy = []
            payload = my_dummy
            series_source_priority_1 = "Pristine,Global,Work"
            series_source_priority_2 = "Global,Work"
            series_source_priority_3 = "Work"
            series_source_priority = str(series_source_priority_3)
            if chosen_option == "W":
                series_source_priority = str(series_source_priority_3)
            if chosen_option == "GW":
                series_source_priority = str(series_source_priority_2)
            if chosen_option == "PGW":
                series_source_priority = str(series_source_priority_1)
            start_threaded_series_level(self, self.gui, series_source_priority, run_type, Dispatcher(self._scrub_series_level_no_restart))
            self.series_choices_dialog.close()
            self.create_ui_toast_dialog_for_jobs()
            if self.my_jobs_dialog_object:
                if self.my_jobs_dialog_object <> "unknown":
                    try:
                        self.my_jobs_dialog_object.show()
                    except:
                        pass
    def scrub_begin_dialog_series_level_whatif(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            self._question_then_go_series_level("0")
    def _start_miscellaneous_events(self, miscellany_option, run_type, guidb):
        global selected_book_list
        job_option = False
        s_option = str(miscellany_option)
        if s_option == "1":
            s_option = str("Propagate Tags from/to Books With The Identical Series")
            job_option = True
        if s_option == "3":
            s_option = str("Book Level Tag Scrubber [Selected Books]")
            job_option = True
        if s_option == "4":
            s_option = str("Validate Work Series Book Titles & Indexes[Selected Fiction Series]")
            job_option = True
        if s_option == "5":
            s_option = str("Rename Work Series Name to Web Series Name <br>[Previously Verified Fiction Series Only]<br><font color='#FF0000'>[This Job Actually Performs Updates]")
            job_option = True
        if s_option == "9":
            s_option = str("Minimize Work Tags Using Tag Priorities [Selected Books]")
            job_option = True
        if s_option == "10":
            s_option = str("Copy Tag & Title Rules From/To Previously Configured Q&S Libraries")
            job_option = True
        selected_book_list = []
        if miscellany_option == "3" or miscellany_option == "4" or miscellany_option == "9" :
            guidb = self.gui.library_view.model().db
            book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            n = len(book_ids_list0)
            if  n <= 0:
                return
            else:
                for row in book_ids_list0:
                    s = row["calibre_id"]
                    selected_book_list.append(str(s))
        if job_option:
            if question_dialog(self.gui, "QuarantineAndScrub", ("  <big>Miscellaneous Scrubbing <br><br>GUI Will Be Locked While Job Runs </big>\
                                                                            <br><br><big><font color='#0404B4'><B>" + s_option + "  </B></font></big>\
                                                                            <br><br><big>Continue? <font color='#800000'><B> To Cancel: Click NO </B></font></big> ") ):
                start_threaded_miscellany(self, self.gui, miscellany_option, run_type, selected_book_list, Dispatcher(self._generic_finish_with_refresh))
                self.create_ui_toast_dialog_for_jobs()
                if self.my_jobs_dialog_object:
                    if self.my_jobs_dialog_object <> "unknown":
                        try:
                            self.my_jobs_dialog_object.show()
                        except:
                            pass
            else:
                pass
        else:
            pass
        if s_option == "2":
            self.delete_all_identifiers_except_isbn(guidb)
        if s_option == "6":
            self._tagruleseditor_dialog(guidb, self._actually_insert_tag_rules_rows)
        if s_option == "7":
            self._tagruleseditor_2_dialog(guidb, self._actually_insert_tag_rules_rows)
        if s_option == "8":
            self.reset_last_modified_date(guidb)
        if s_option == "11":
            self.default_work_tags_by_author(guidb)
        if s_option == "12":
            self.reset_default_work_tag_by_author(guidb)
        if s_option == "13":
            self.autopopulate_tags_by_comment(guidb)
    def _start_miscellany_option_1_standalone(self):
        global selected_book_list
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        miscellany_option = "1"
        run_type = "X"
        s_option = str("Propagate Tags from/to Books With The Identical Series")
        selected_book_list = []
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n <= 0:
            return
        else:
            for row in book_ids_list0:
                s = row["calibre_id"]
                selected_book_list.append(str(s))
        if question_dialog(self.gui, "QuarantineAndScrub", ("  <big>Miscellaneous Scrubbing <br><br>GUI Will Be Locked While Job Runs </big>\
                                                                    <br><br><big><font color='#0404B4'><B>" + s_option + "  </B></font></big>\
                                                                    <br><br><big>Continue? <font color='#800000'><B> To Cancel: Click NO </B></font></big> ") ):
            start_threaded_miscellany(self, self.gui, miscellany_option, run_type, selected_book_list, Dispatcher(self._generic_finish_with_refresh))
            self.create_ui_toast_dialog_for_jobs()
            if self.my_jobs_dialog_object:
                if self.my_jobs_dialog_object <> "unknown":
                    try:
                        self.my_jobs_dialog_object.show()
                    except:
                        pass
        else:
            pass
    def _start_miscellany_option_2_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.delete_all_identifiers_except_isbn(guidb)
    def _start_miscellany_option_3_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        global selected_book_list
        miscellany_option = "3"
        run_type = "X"
        s_option = str("Book Level Tag Scrubber [Selected Books]")
        selected_book_list = []
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n <= 0:
            return
        else:
            for row in book_ids_list0:
                s = row["calibre_id"]
                selected_book_list.append(str(s))
        if question_dialog(self.gui, "QuarantineAndScrub", ("  <big>Miscellaneous Scrubbing <br><br>GUI Will Be Locked While Job Runs </big>\
                                                                    <br><br><big><font color='#0404B4'><B>" + s_option + "  </B></font></big>\
                                                                    <br><br><big>Continue? <font color='#800000'><B> To Cancel: Click NO </B></font></big> ") ):
            start_threaded_miscellany(self, self.gui, miscellany_option, run_type, selected_book_list, Dispatcher(self._generic_finish_with_refresh))
            self.create_ui_toast_dialog_for_jobs()
            if self.my_jobs_dialog_object:
                if self.my_jobs_dialog_object <> "unknown":
                    try:
                        self.my_jobs_dialog_object.show()
                    except:
                        pass
        else:
            pass
    def _start_miscellany_option_4_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        global selected_book_list
        miscellany_option = "4"
        run_type = "0"
        s_option = str("Validate Work Series Book Titles & Indexes[Selected Fiction Series]")
        selected_book_list = []
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n <= 0:
            return
        else:
            for row in book_ids_list0:
                s = row["calibre_id"]
                selected_book_list.append(str(s))
        if question_dialog(self.gui, "QuarantineAndScrub", ("  <big>Miscellaneous Scrubbing <br><br>GUI Will Be Locked While Job Runs </big>\
                                                                    <br><br><big><font color='#0404B4'><B>" + s_option + "  </B></font></big>\
                                                                    <br><br><big>Continue? <font color='#800000'><B> To Cancel: Click NO </B></font></big> ") ):
            start_threaded_miscellany(self, self.gui, miscellany_option, run_type, selected_book_list, Dispatcher(self._generic_finish_with_refresh))
            self.create_ui_toast_dialog_for_jobs()
            if self.my_jobs_dialog_object:
                if self.my_jobs_dialog_object <> "unknown":
                    try:
                        self.my_jobs_dialog_object.show()
                    except:
                        pass
        else:
            pass
    def _start_miscellany_option_5_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        global selected_book_list
        miscellany_option = "5"
        run_type = "1"
        s_option = str("Rename Work Series Name to Web Series Name <br>[Previously Verified Fiction Series Only]<br><font color='#FF0000'>[This Job Actually Performs Updates]")
        selected_book_list = []
        if question_dialog(self.gui, "QuarantineAndScrub", ("  <big>Miscellaneous Scrubbing <br><br>GUI Will Be Locked While Job Runs </big>\
                                                                    <br><br><big><font color='#0404B4'><B>" + s_option + "  </B></font></big>\
                                                                    <br><br><big>Continue? <font color='#800000'><B> To Cancel: Click NO </B></font></big> ") ):
            start_threaded_miscellany(self, self.gui, miscellany_option, run_type, selected_book_list, Dispatcher(self._generic_finish_with_refresh))
            self.create_ui_toast_dialog_for_jobs()
            if self.my_jobs_dialog_object:
                if self.my_jobs_dialog_object <> "unknown":
                    try:
                        self.my_jobs_dialog_object.show()
                    except:
                        pass
        else:
            pass
    def _start_miscellany_option_6_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self._tagruleseditor_dialog(guidb, self._actually_insert_tag_rules_rows)
    def _start_miscellany_option_7_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self._tagruleseditor_2_dialog(guidb, self._actually_insert_tag_rules_rows)
    def _start_miscellany_option_8_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.reset_last_modified_date(guidb)
    def _start_miscellany_option_9_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        global selected_book_list
        miscellany_option = "9"
        run_type = "1"
        s_option = str("Minimize Work Tags Using Tag Priorities [Selected Books]")
        selected_book_list = []
        guidb = self.gui.library_view.model().db
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n <= 0:
            return
        else:
            for row in book_ids_list0:
                s = row["calibre_id"]
                selected_book_list.append(str(s))
        if question_dialog(self.gui, "QuarantineAndScrub", ("  <big>Miscellaneous Scrubbing <br><br>GUI Will Be Locked While Job Runs </big>\
                                                                    <br><br><big><font color='#0404B4'><B>" + s_option + "  </B></font></big>\
                                                                    <br><br><big>Continue? <font color='#800000'><B> To Cancel: Click NO </B></font></big> ") ):
            start_threaded_miscellany(self, self.gui, miscellany_option, run_type, selected_book_list, Dispatcher(self._generic_finish_with_refresh))
            self.create_ui_toast_dialog_for_jobs()
            if self.my_jobs_dialog_object:
                if self.my_jobs_dialog_object <> "unknown":
                    try:
                        self.my_jobs_dialog_object.show()
                    except:
                        pass
        else:
            pass
    def _start_miscellany_option_10_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        global selected_book_list
        miscellany_option = "10"
        run_type = "1"
        s_option = str("Copy Tag & Title Rules From/To Previously Configured Q&S Libraries")
        selected_book_list = []
        if question_dialog(self.gui, "QuarantineAndScrub", ("  <big>Miscellaneous Scrubbing <br><br>GUI Will Be Locked While Job Runs </big>\
                                                                    <br><br><big><font color='#0404B4'><B>" + s_option + "  </B></font></big>\
                                                                    <br><br><big>Continue? <font color='#800000'><B> To Cancel: Click NO </B></font></big> ") ):
            start_threaded_miscellany(self, self.gui, miscellany_option, run_type, selected_book_list, Dispatcher(self._generic_finish_with_refresh))
            self.create_ui_toast_dialog_for_jobs()
            if self.my_jobs_dialog_object:
                if self.my_jobs_dialog_object <> "unknown":
                    try:
                        self.my_jobs_dialog_object.show()
                    except:
                        pass
        else:
            pass
    def _start_miscellany_option_11_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.default_work_tags_by_author(guidb)
    def _start_miscellany_option_12_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.reset_default_work_tag_by_author(guidb)
    def _start_miscellany_option_13_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.autopopulate_tags_by_comment(guidb)
    def _start_miscellany_option_14_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        miscellany_option = "14"
        run_type = "1"
        s_option = str("Download Work Series/Titles/Indexes from Web Source for All Series for a Single Author [Single Author Only]")
        selected_book_list = []
        guidb = self.gui.library_view.model().db
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n <> 1:
            return
        else:
            for row in book_ids_list0:
                s = row["calibre_id"]
                selected_book_list.append(str(s))
        start_threaded_miscellany(self, self.gui, miscellany_option, run_type, selected_book_list, Dispatcher(self._generic_finish_with_refresh))
        self.create_ui_toast_dialog_for_jobs()
        if self.my_jobs_dialog_object:
            if self.my_jobs_dialog_object <> "unknown":
                try:
                    self.my_jobs_dialog_object.show()
                except:
                    pass
    def _start_miscellany_option_15_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        miscellany_option = "15"
        run_type = "1"
        s_option = str('Copy Work Tags to Custom Columns(s) Per Table "Tags CC Mapping Control" [Selected Books]')
        selected_book_list = []
        guidb = self.gui.library_view.model().db
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n == 0:
            return
        else:
            for row in book_ids_list0:
                s = row["calibre_id"]
                selected_book_list.append(str(s))
        start_threaded_miscellany(self, self.gui, miscellany_option, run_type, selected_book_list, Dispatcher(self._generic_finish_with_refresh))
        self.create_ui_toast_dialog_for_jobs()
        if self.my_jobs_dialog_object:
            if self.my_jobs_dialog_object <> "unknown":
                try:
                    self.my_jobs_dialog_object.show()
                except:
                    pass
    def _start_miscellany_option_16_standalone(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        miscellany_option = "16"
        run_type = "1"
        s_option = str('Update Pseudonym Custom Column [Selected Books]')
        selected_book_list = []
        guidb = self.gui.library_view.model().db
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n == 0:
            return
        else:
            for row in book_ids_list0:
                s = row["calibre_id"]
                selected_book_list.append(str(s))
        start_threaded_miscellany(self, self.gui, miscellany_option, run_type, selected_book_list, Dispatcher(self._generic_finish_with_refresh))
        self.create_ui_toast_dialog_for_jobs()
        if self.my_jobs_dialog_object:
            if self.my_jobs_dialog_object <> "unknown":
                try:
                    self.my_jobs_dialog_object.show()
                except:
                    pass
    def _tagruleseditor_dialog(self, guidb, _actually_insert_tag_rules_rows):
        tags_list_of_lists = self._get_all_tags(guidb)
        tagruleslisteditor_dialog = TagRulesListEditor(self.gui, 'Quarantine And Scrub:  Tag Rules for Tag Scrubbing', 'None',\
                                                                                    tags_list_of_lists, None, self._actually_insert_tag_rules_rows, guidb)
        tagruleslisteditor_dialog.show()
    def _tagruleseditor_2_dialog(self, guidb, _actually_insert_tag_rules_rows):
        tags_list_of_lists = self._get_all_tags_2(guidb)
        tagruleslisteditor_2_dialog = TagRulesListEditor2(self.gui, 'Quarantine And Scrub:  Tag Rules for Tag Scrubbing', 'None',\
                                                                                    tags_list_of_lists, None, self._actually_insert_tag_rules_rows_2, guidb)
        tagruleslisteditor_2_dialog.show()
    def _finish_displaying_results(self, payload):
        global guidb
        books_updated = payload
        self.gui.library_view.model().refresh_ids(list(books_updated))
        self.gui.tags_view.recount()
        marked_ids = {}
        for line in books_updated:
            marked_ids[line] = 'metadata_scrubbed'
        self.gui.current_db.set_marked_ids(marked_ids)
        self.gui.search.set_search_string('marked:metadata_scrubbed')
        self.gui.status_bar.show_message(_('Metadata Was Scrubbed'), 5000)
    def copy_selected_work_level_begin_dialogs_with_user(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            copy_selected_work_level()
    def copy_work_level_begin_dialogs_with_user(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            if question_dialog(self.gui, "Copy Real Book Metadata to Work Columns","\
                                                            <br><br>GUI Will Be Locked While Job Runs \
                                                           <br><br>This Is For All Books. <br><br> Continue?"):
                start_threaded_util_copy_original_metadata(self, guidb, Dispatcher(self._copy_real_to_work_no_restart))
                self.create_ui_toast_dialog_for_jobs()
                if self.my_jobs_dialog_object:
                    if self.my_jobs_dialog_object <> "unknown":
                        try:
                            self.my_jobs_dialog_object.show()
                        except:
                            pass
            else:
                 info_dialog(self.gui, 'Canceled',
                     'Copy is Canceled').show()
    def copy_selected_tags_only_begin_dialogs_with_user(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            self.copy_selected_tags_only()
    def copy_selected_series_only_begin_dialogs_with_user(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            self.copy_selected_series_only()
    def purge_work_data_begin_dialogs_with_user(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            if question_dialog(self.gui, "Purge All Work Data","\
                                                            <br><br>GUI Will Be Locked While Job Runs \
                                                           <br><br>This Is For All Books. <br><br> Continue?"):
                start_threaded_purge_work_data(self, guidb, Dispatcher(self._purge_work_data_no_restart))
                self.create_ui_toast_dialog_for_jobs()
                if self.my_jobs_dialog_object:
                    if self.my_jobs_dialog_object <> "unknown":
                        try:
                            self.my_jobs_dialog_object.show()
                        except:
                            pass
            else:
                 info_dialog(self.gui, 'Canceled', 'Purge is Canceled').show()
    def create_sqlite_objects_begin_dialogs_with_user(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            if question_dialog(self.gui, "Create New SQLite Objects After Plugin Upgrade","\
                                                            <br><br>GUI Will Be Locked While Job Runs \
                                                           <br><br> Continue?"):
                start_threaded_create_sqlite_objects(self, guidb, Dispatcher(self._create_sqlite_objects_no_restart))
                self.create_ui_toast_dialog_for_jobs()
                if self.my_jobs_dialog_object:
                    if self.my_jobs_dialog_object <> "unknown":
                        try:
                            self.my_jobs_dialog_object.show()
                        except:
                            pass
            else:
                 info_dialog(self.gui, 'Canceled', 'Creation of SQLite Objects is Canceled').show()
    def copy_pristine_level_begin_dialogs_with_user(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            if question_dialog(self.gui, "Copy Calibre Pristine Data to Q+S Work Database?","<br> This Is For All Authors/Series. <br><br> Continue?"):
                start_threaded_util_copy_pristine_metadata(self, guidb, Dispatcher(self._copy_pristine_no_restart))
                self.create_ui_toast_dialog_for_jobs()
            if self.my_jobs_dialog_object:
                if self.my_jobs_dialog_object <> "unknown":
                    try:
                        self.my_jobs_dialog_object.show()
                    except:
                        pass
            else:
                 info_dialog(self.gui, 'Canceled',
                     'Copy is Canceled').show()
    def _generic_finish_with_refresh(self, job):
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Q+S Job Failed...'))
            pass
        else:
            pass
        db = self.gui.current_db.new_api
        work_book_ids_frozenset = db.all_book_ids()
        books_to_refresh = []
        for row in work_book_ids_frozenset:
            books_to_refresh.append(row)
        self.gui.status_bar.show_message(_('Q+S Job Finished'), 5000)
        self.force_refresh_of_cache(books_to_refresh)
    def _copy_real_to_work_no_restart(self, job):
        global work_book_ids_list
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed to Copy Work Data...'))
            pass
        else:
            pass
        db = self.gui.current_db.new_api
        work_book_ids_frozenset = db.all_book_ids()
        books_to_refresh = []
        for row in work_book_ids_frozenset:
            books_to_refresh.append(row)
        n = len(books_to_refresh)
        try:
            self.gui.status_bar.show_message(_('Copying of Calibre Real Metadata to the Work Q+S Database Has Completed'), 5000)
            info_dialog(self.gui, 'Job Successful','Copying of Calibre Real Metadata to the Work Q+S Database Has Completed.\
                                            <br><br> A Calibre Restart Is Recommended If Any DB Locking Issues Are Experienced.').show()
            self.force_refresh_of_cache(books_to_refresh)
        except:
            self.gui.quit(restart=True)
    def _purge_work_data_no_restart(self, job):
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed to Purge Work Data...'))
            pass
        else:
            pass
        db = self.gui.current_db.new_api
        work_book_ids_frozenset = db.all_book_ids()
        books_to_refresh = []
        for row in work_book_ids_frozenset:
            books_to_refresh.append(row)
        n = len(books_to_refresh)
        try:
            self.gui.status_bar.show_message(_('Purging All Work Data Has Completed'), 5000)
            self.force_refresh_of_cache(books_to_refresh)
        except:
            self.gui.quit(restart=True)
    def _create_sqlite_objects_no_restart(self, job):
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed to Create SQLite Objects...'))
            return
        else:
            self.gui.status_bar.show_message(_('Creation of SQLite Objects Has Completed'), 15000)
    def _copy_pristine_no_restart(self, job):
        global work_book_ids_list
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed to Copy Pristine Data...'))
            return
        else:
            pass
        self.gui.status_bar.show_message(_('Copying of Calibre Pristine Author/Series Data to the Work Q+S Database Has Completed'), 5000)
        info_dialog(self.gui, 'Job Successful','Copying of Calibre Pristine Author/Series Data to the Q+S Database Has Completed').show()
    def _scrub_book_level_no_restart(self, job):
        global book_ids
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed to Scrub at Book Level...'))
            pass
        else:
            pass
        self.gui.status_bar.show_message(_('Scrubbing at the Book Level Has Completed'), 5000)
        self.force_refresh_of_cache(book_ids)
    def _scrub_author_level_no_restart(self, job):
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed to Scrub at Author Level...'))
            pass
        else:
            pass
        db = self.gui.current_db.new_api
        work_book_ids_frozenset = db.all_book_ids()
        books_to_refresh = []
        for row in work_book_ids_frozenset:
            books_to_refresh.append(row)
        self.gui.status_bar.show_message(_('Scrubbing at the Author Level Has Completed'), 5000)
        self.force_refresh_of_cache( books_to_refresh)
    def _scrub_series_level_no_restart(self, job):
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed to Scrub at Series Level...'))
            pass
        else:
            pass
        db = self.gui.current_db.new_api
        work_book_ids_frozenset = db.all_book_ids()
        books_to_refresh = []
        for row in work_book_ids_frozenset:
            books_to_refresh.append(row)
        self.gui.status_bar.show_message(_('Scrubbing at the Series Level Has Completed'), 5000)
        self.force_refresh_of_cache( books_to_refresh)
    def _cancel_scrub(self):
        return
    def _populate_book_from_calibre_id(self, book, db=None):
        mi = db.get_metadata(book['calibre_id'], index_is_id=True)
        book['calibre_id'] = mi.id
    def _convert_id_to_book(self, idval):
        book = {}
        book['calibre_id'] = idval
        return book
    def manually_restart(self):
        self.gui.quit(restart=True)
    def _swap_work_title_and_work_series(self):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        mynothing = ""
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        work_book_ids_list = []
        del work_book_ids_list
        work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(work_book_ids_list)
        if  n <= 0:
            return error_dialog(self.gui, _('Cannot Swap Work Title and Work Series Metadata'),
                                                      _('You must select one or more books to perform this action.'), show=True)
        self.create_ui_toast_dialog(1)
        work_dict_title = {}
        del work_dict_title
        work_dict_series = {}
        del work_dict_series
        work_dict_title = self.__get_work_titles(work_book_ids_list)
        work_dict_series = self.__get_work_series(work_book_ids_list)
        db = self.gui.current_db.new_api
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        my_book_list = []
        for row in work_book_ids_list:
            book = row['calibre_id']
            my_book_list.append(str(book))
            try:
                old_title = work_dict_title[book]
                old_series = work_dict_series[book]
            except:
                continue
            if not old_title or not old_series:
                continue
            try:
                book = str(book)
                mysql = str('DELETE FROM custom_column_8 WHERE id = ? ')
                my_db.execute(mysql,([book]))
                mysql = str('DELETE FROM books_custom_column_8_link WHERE book =  ? ')
                my_db.execute(mysql,([book]))
                mysql = str('DELETE FROM custom_column_10 WHERE id = ? ')
                my_db.execute(mysql,([book]))
                mysql = str('DELETE FROM books_custom_column_10_link WHERE book =  ? ')
                my_db.execute(mysql,([book]))
                my_db.commit()
                sleep(.05)
                mysql = str("INSERT OR REPLACE into custom_column_8 (id,value) VALUES (?,?) ")
                my_db.execute(mysql,(book,old_series))
                my_db.commit()
                sleep(.05)
                mysql = str("INSERT OR REPLACE into custom_column_10 (id,value) VALUES (?,?) ")
                my_db.execute(mysql,(book,old_title))
                my_db.commit()
                sleep(.05)
                mysql = str("INSERT OR REPLACE into books_custom_column_8_link (id,book,value) VALUES (?,?,?) ")
                my_db.execute(mysql,(book,book,book))
                mysql = str("INSERT OR REPLACE into books_custom_column_10_link (id,book,value) VALUES (?,?,?) ")
                my_db.execute(mysql,(book,book,book))
                my_db.commit()
                sleep(.05)
                mysql = str("INSERT OR REPLACE INTO custom_column_15 (id,book,value) SELECT book,book,seriesfull FROM __series_work_full WHERE book = ? ")
                my_db.execute(mysql,([book]))
                my_db.commit()
                sleep(.05)
                mysql =  str("UPDATE custom_column_15 SET value=REPLACE(value,'.0','') WHERE book = ?  ; ")
                my_db.execute(mysql,([book]))
                my_db.commit()
                sleep(.05)
                mysql =  str("UPDATE custom_column_15 SET value=REPLACE(value,'.5]','.50]') WHERE book = ? ; " )
                my_db.execute(mysql,([book]))
                my_db.commit()
                sleep(.05)
            except Exception as e:
                pass
        sleep(0.1)
        my_db.close()
        self.ui_toast_dialog.close()
        self.force_refresh_of_cache(my_book_list)
    def copy_work_author_to_author(self, s_callback):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        mynothing = ""
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        if not  s_callback == mynothing:
            work_book_ids_list = []
            work_book_ids_list[:] = []
        try:
            n = len(work_book_ids_list)
        except:
            work_book_ids_list = []
            n = 0
        if not n > 0:
            work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            n = len(work_book_ids_list)
            if  n <= 0:
                return
        payload = work_book_ids_list
        work_dict_auth = self.__get_work_authors(work_book_ids_list)
        id_map = {}
        for row in work_book_ids_list:
            mi = Metadata(_('Unknown'))
            book = row['calibre_id']
            authors = work_dict_auth[book]
            if not authors:
                continue
            s = str(authors)
            auth_list = []
            auth_list.append(authors)
            mi.authors = auth_list
            id_map[book] = mi
        edit_metadata_action = self.gui.iactions['Edit Metadata']
        if s_callback == mynothing:
            edit_metadata_action.apply_metadata_changes(id_map, callback=self._finish_displaying_copy_work_to_real_results)
            work_book_ids_list[:] = []
        else:
            edit_metadata_action.apply_metadata_changes(id_map, callback=None)
    def copy_work_title_to_title(self, s_callback):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        mynothing = ""
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        if not  s_callback == mynothing:
            work_book_ids_list = []
            work_book_ids_list[:] = []
        try:
            n = len(work_book_ids_list)
        except:
            work_book_ids_list = []
            work_book_ids_list[:] = []
            n = 0
        if not n > 0:
            work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            n = len(work_book_ids_list)
            if  n <= 0:
                return
                return error_dialog(self.gui, _('Cannot Copy Work Title to Real Title Metadata'),
                                                          _('You must select one or more books to perform this action.'), show=True)
        work_dict_title = {}
        del work_dict_title
        payload = work_book_ids_list
        work_dict_title = self.__get_work_titles(work_book_ids_list)
        id_map = {}
        for row in work_book_ids_list:
            mi = Metadata(_('Unknown'))
            book = row['calibre_id']
            title = work_dict_title[book]
            if (not title):
                continue
            if str(title) == "?":
                continue
            mi.title = title
            id_map[book] = mi
        edit_metadata_action = self.gui.iactions['Edit Metadata']
        if s_callback == mynothing:
            edit_metadata_action.apply_metadata_changes(id_map, callback=self._finish_displaying_copy_work_to_real_results)
            work_book_ids_list[:] = []
        else:
            edit_metadata_action.apply_metadata_changes(id_map, callback=None)
    def copy_work_series_to_series(self, s_callback):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        mynothing = ""
        guidb = self.gui.library_view.model().db
        db = self.gui.current_db.new_api
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        if not  s_callback == mynothing:
            work_book_ids_list = []
            work_book_ids_list[:] = []
        try:
            n = len(work_book_ids_list)
        except:
            work_book_ids_list = []
            n = 0
        if not n > 0:
            work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            n = len(work_book_ids_list)
            if  n <= 0:
                return
                return error_dialog(self.gui, _('Cannot Copy Work Title to Real Title Metadata'),
                                                          _('You must select one or more books to perform this action.'), show=True)
        payload = work_book_ids_list
        work_dict_series = self.__get_work_series(work_book_ids_list)
        id_map = {}
        for row in work_book_ids_list:
            mi = Metadata(_('Unknown'))
            book = row['calibre_id']
            try:
                series = work_dict_series[book]
                if not series:
                    continue
            except:
                continue
            mi.series = series
            id_map[book] = mi
        edit_metadata_action = self.gui.iactions['Edit Metadata']
        if s_callback == mynothing:
            edit_metadata_action.apply_metadata_changes(id_map, callback=self._finish_displaying_copy_work_to_real_results)
            work_book_ids_list[:] = []
        else:
            edit_metadata_action.apply_metadata_changes(id_map, callback=None)
    def copy_work_series_number_to_series_index(self, s_callback):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        mynothing = ""
        guidb = self.gui.library_view.model().db
        db = self.gui.current_db.new_api
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        if not  s_callback == mynothing:
            work_book_ids_list = []
            work_book_ids_list[:] = []
        try:
            n = len(work_book_ids_list)
        except:
            work_book_ids_list = []
            n = 0
        if not n > 0:
            work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            n = len(work_book_ids_list)
            if  n <= 0:
                return
                return error_dialog(self.gui, _('Cannot Copy WorkSeries Number to Real Series Index Metadata'),
                                                          _('You must select one or more books to perform this action.'), show=True)
        payload = work_book_ids_list
        work_dict_series_index = self.__get_work_series_number(work_book_ids_list)
        id_map = {}
        for row in work_book_ids_list:
            mi = Metadata(_('Unknown'))
            book = row['calibre_id']
            series_index = str(work_dict_series_index[book])
            if not series_index:
                continue
            try:
                series_index = series_index.replace("u'", mynothing, 1)
                series_index = series_index.replace("'", mynothing, 1)
                series_index = float(series_index)
            except:
                continue
            old_series = db.field_for('series', book, default_value=None)
            if not old_series or old_series == mynothing:
                continue
            mi.series = old_series
            mi.series_index = series_index
            id_map[book] = mi
        edit_metadata_action = self.gui.iactions['Edit Metadata']
        if s_callback == mynothing:
            edit_metadata_action.apply_metadata_changes(id_map, callback=self._finish_displaying_copy_work_to_real_results)
            work_book_ids_list[:] = []
        else:
            edit_metadata_action.apply_metadata_changes(id_map, callback=None)
    def copy_work_tags_to_tags(self, s_callback):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        mynothing = ""
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        if not  s_callback == mynothing:
            work_book_ids_list = []
            work_book_ids_list[:] = []
        try:
            n = len(work_book_ids_list)
        except:
            work_book_ids_list = []
            n = 0
        if not n > 0:
            work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            n = len(work_book_ids_list)
            if  n <= 0:
                return
        payload = work_book_ids_list
        work_dict_tags = self.__get_work_tags(work_book_ids_list)
        id_map = {}
        for row in work_book_ids_list:
            mi = Metadata(_('Unknown'))
            book = row['calibre_id']
            tags = work_dict_tags[book]
            if not tags:
                tags = " "
            if tags == "None" or tags == "NONE" or tags == "none":
                tags = " "
            mi.tags = tags
            id_map[book] = mi
        edit_metadata_action = self.gui.iactions['Edit Metadata']
        if s_callback == mynothing:
            edit_metadata_action.apply_metadata_changes(id_map, callback=self._finish_displaying_copy_work_to_real_results, merge_tags=False)
            try:
                work_book_ids_list[:] = []
            except:
                work_book_ids_list = []
        else:
            try:
                del work_book_ids_list
            except:
                pass
            edit_metadata_action.apply_metadata_changes(id_map, callback=None, merge_tags=False)
    def copy_all_work_to_all_real(self):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        mynothing = ""
        guidb = self.gui.library_view.model().db
        try:
            work_book_ids_list[:] = []
        except:
            pass
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        else:
            work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            work_book_ids_list_save = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            n = len(work_book_ids_list)
            if n == 0:
                return
            if  n > 1:
                return error_dialog(self.gui, _('More Than One (1) Book Selected'),_('You must select only one (1) book to perform this action.'), show=True)
            else:
                pass
        s_callback = mynothing
        self.copy_work_author_to_author(s_callback)
        work_book_ids_list = work_book_ids_list_save
        s_callback = mynothing
        self.copy_work_title_to_title(s_callback)
        work_book_ids_list = work_book_ids_list_save
        s_callback = mynothing
        self.copy_work_series_to_series(s_callback)
        work_book_ids_list = work_book_ids_list_save
        s_callback = mynothing
        self.copy_work_series_number_to_series_index(s_callback)
        work_book_ids_list = work_book_ids_list_save
        s_callback = mynothing
        self.copy_work_tags_to_tags(s_callback)
        work_book_ids_list = work_book_ids_list_save
        payload = work_book_ids_list
        self._finish_displaying_copy_work_to_real_results(payload)
    def _finish_displaying_copy_work_to_real_results(self, payload):
        global guidb
        work_book_ids_list = payload
        self.gui.library_view.model().refresh_ids(list(work_book_ids_list))
        self.gui.tags_view.recount()
        return
    def view_user_instructions(self):
        global guidb
        global library_is_quarantine_db
        global documentation_path
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        self.extract_documentation_from_zip()
        try:
            p_pid = subprocess.Popen(documentation_path, shell=True)
        except:
            return error_dialog(self.gui, _('Documentation .pdf Not Found. Try reinstalling this plugin, and restarting Calibre.'),
                                       _('It is supposed to be: ' + documentation_path), show=True)
    def extract_documentation_from_zip(self):
        global my_plugin_path
        global documentation_path
        zipfile_path = my_plugin_path
        destination_path = my_plugin_path
        destination_path = destination_path.replace("\QuarantineAndScrub.zip", "", 1)
        destination_path = destination_path.replace("/QuarantineAndScrub.zip", "", 1)
        zfile = zipfile.ZipFile(zipfile_path)
        dir_name = "quarantine_documentation"
        dir_name = dir_name.encode("ascii", "strict")
        file_name = 'quarantine_instructions.pdf'
        file_name = file_name.encode("ascii", "strict")
        file_name = os.path.join(dir_name, file_name )
        for name in zfile.namelist():
            n = name.find(dir_name)
            if n >= 0:
                zfile.extract(name, destination_path)
                documentation_path = os.path.join(destination_path, file_name)
    def config(self):
        global library_is_quarantine_db
        global guidb
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        base_plugin_object = self.interface_action_base_plugin
        do_user_config = base_plugin_object.do_user_config
        d = ConfigDialog(self.gui, self.qaction.icon(), do_user_config)
        d.close()
    def apply_settings(self):
        from calibre_plugins.quarantine_and_scrub.config import prefs
        prefs
    def __get_work_authors(self, book_list):
        db = self.gui.current_db.new_api
        work_dict_auth = {}
        name = "#work_author"
        for row in book_list:
            book = row['calibre_id']
            work_author = db.field_for(name, book, default_value=None)
            if not work_author:
                continue
            if not isinstance(work_author, unicode):
                work_author = "u'" + work_author + "'"
            work_dict_auth[book] = work_author
        return work_dict_auth
    def __get_work_titles(self, book_list):
        db = self.gui.current_db.new_api
        work_dict_title = {}
        del work_dict_title
        work_dict_title = {}
        name = "#work_title"
        for row in book_list:
            book = row['calibre_id']
            work_title = db.field_for(name, book, default_value=None)
            if (not work_title):
                work_title = "?"
            if work_title == "":
                work_title = "?"
            if not isinstance(work_title, unicode):
                work_title = "u'" + work_title + "'"
            work_dict_title[book] = work_title
        return work_dict_title
    def __get_work_series(self, book_list):
        db = self.gui.current_db.new_api
        work_dict_series = {}
        name = "#work_series"
        for row in book_list:
            book = row['calibre_id']
            work_series = db.field_for(name, book, default_value=None)
            if not work_series:
                continue
            if not isinstance(work_series, unicode):
                work_series = "u'" + work_series + "'"
            work_dict_series[book] = work_series
        return work_dict_series
    def __get_work_series_number(self, book_list):
        mynothing = ""
        db = self.gui.current_db.new_api
        work_dict_series_index = {}
        name = "#work_series_number"
        for row in book_list:
            book = row['calibre_id']
            work_series_number = str(db.field_for(name, book, default_value=None))
            if not work_series_number:
                continue
            if not isinstance(work_series_number, unicode):
                work_series_number = str("u'" + work_series_number + "'")
            work_dict_series_index[book] = work_series_number
        return work_dict_series_index
    def __get_work_tags(self, book_list):
        mynothing = ""
        db = self.gui.current_db.new_api
        work_dict_tags = {}
        name = "#work_tags"
        for row in book_list:
            book = row['calibre_id']
            work_tags= str(db.field_for(name, book, default_value="None"))
            if not work_tags:
                continue
            work_tags = work_tags.replace(",", ", ", 50)
            if not isinstance(work_tags, unicode):
                work_tags = str("u'" + work_tags + "'")
            work_dict_tags[book] = work_tags
        return work_dict_tags
    def copy_selected_tags_only(self):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        mynothing = ""
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        try:
            work_book_ids_list[:] = []
        except:
            pass
        work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(work_book_ids_list)
        if  n < 1:
            return error_dialog(self.gui, _('Cannot Copy Selected Real Tags to Work Tags'),
                                                      _('You must select books to perform this action.'), show=True)
        else:
            pass
        db = self.gui.current_db.new_api
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        my_book_list = []
        for row in work_book_ids_list:
            book = row['calibre_id']
            my_book_list.append(book)
            s_book = str(book)
            try:
                my_db.execute('DELETE FROM custom_column_13 WHERE id = "' + s_book + '" ;')
                my_db.execute('DELETE FROM books_custom_column_13_link WHERE book = "' + s_book + '" ;')
                my_db.execute('DELETE FROM _book_awards_mapping WHERE book = "' + s_book + '" ;')
                my_db.commit()
                sleep(.5)
            except Exception as e:
                pass
        try:
            mysql = "DELETE FROM books_custom_column_13_link WHERE value NOT IN (SELECT id FROM custom_column_13 WHERE id > '0')"
            my_db.execute(mysql)
            my_db.commit()
            sleep(0.5)
            mysql = "DELETE FROM custom_column_13 WHERE id NOT IN (SELECT value FROM books_custom_column_13_link WHERE value > '0')"
            my_db.execute(mysql)
            my_db.commit()
            sleep(0.5)
            mysql = "DELETE FROM _book_awards_mapping WHERE book NOT IN (SELECT id FROM custom_column_13 WHERE id > '0')"
            my_db.execute(mysql)
            my_db.commit()
            sleep(0.5)
        except Exception as e:
            pass
        sleep(1.0)
        my_db.close()
        self.force_refresh_of_cache(my_book_list)
        real_dict_tags = {}
        name_tags = "tags"
        name = name_tags
        for row in work_book_ids_list:
            book = row['calibre_id']
            tags = db.field_for(name, book, default_value=None)
            if not tags:
                continue
            s = ""
            for item in tags:
                s = s + item + ","
            s = s.strip()
            s = s[0:-1]
            s = s.replace(",", ", ", 50)
            tags = s
            if not isinstance(tags, unicode):
                tags = "u'" + tags + "'"
            real_dict_tags[book] = tags
        custom_columns = self.gui.current_db.field_metadata.custom_field_metadata()
        id_map = {}
        for row in work_book_ids_list:
            book = row['calibre_id']
            mi = Metadata(_('Unknown'))
            try:
                custcol6 = custom_columns["#work_tags"]
                custcol6['#value#'] = real_dict_tags[book]
                mi.set_user_metadata('#work_tags', custcol6)
            except:
                custcol6 = custom_columns["#work_tags"]
                custcol6['#value#'] = ""
                mi.set_user_metadata('#work_tags', custcol6)
            id_map[book] = mi
        edit_metadata_action = self.gui.iactions['Edit Metadata']
        edit_metadata_action.apply_metadata_changes(id_map, callback=None)
        del real_dict_tags
        work_book_ids_list[:] = []
    def copy_selected_series_only(self):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        mynothing = ""
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        try:
            work_book_ids_list[:] = []
        except:
            pass
        work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(work_book_ids_list)
        if  n < 1:
            return error_dialog(self.gui, _('Cannot Copy Selected Real Series to Work Series'),
                                                      _('You must select books to perform this action.'), show=True)
        else:
            pass
        db = self.gui.current_db.new_api
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        real_dict_series = {}
        real_dict_series_index = {}
        name_series = "series"
        name_series_index = "series_index"
        my_book_list = []
        for row in work_book_ids_list:
            book = row['calibre_id']
            my_book_list.append(book)
            s_book = str(book)
            try:
                my_db.execute('DELETE FROM custom_column_10 WHERE id = "' + s_book + '" ;')
                my_db.execute('DELETE FROM books_custom_column_10_link WHERE book = "' + s_book + '" ;')
                my_db.execute('DELETE FROM custom_column_12 WHERE book ="' + s_book + '" ;')
                my_db.execute('DELETE FROM custom_column_15 WHERE book = "' + s_book + '" ;')
                my_db.commit()
                sleep(.5)
            except Exception as e:
                pass
        try:
            mysql = "DELETE FROM books_custom_column_10_link WHERE value NOT IN (SELECT id FROM custom_column_10 WHERE id > '0')"
            my_db.execute(mysql)
            my_db.commit()
            sleep(0.5)
            mysql = "DELETE FROM custom_column_10 WHERE id NOT IN (SELECT value FROM books_custom_column_10_link WHERE value > '0')"
            my_db.execute(mysql)
            my_db.commit()
            sleep(0.5)
        except Exception as e:
            pass
        sleep(0.5)
        my_db.close()
        self.force_refresh_of_cache(my_book_list)
        name = name_series
        for row in work_book_ids_list:
            book = row['calibre_id']
            series = db.field_for(name, book, default_value=None)
            if not series:
                continue
            if not isinstance(series, unicode):
                series = "u'" + series + "'"
            real_dict_series[book] = series
        name = name_series_index
        for row in work_book_ids_list:
            book = row['calibre_id']
            series_index = str(db.field_for(name, book, default_value=None))
            series_index = str(series_index.replace(".0", "", 1))
            series_index = str(series_index.replace(".50", ".5", 1))
            if not series_index:
                continue
            if not isinstance(series_index, unicode):
                series_index = "u'" + series_index + "'"
            real_dict_series_index[book] = series_index
        custom_columns = self.gui.current_db.field_metadata.custom_field_metadata()
        id_map = {}
        for row in work_book_ids_list:
            book = row['calibre_id']
            mi = Metadata(_('Unknown'))
            try:
                custcol3 = custom_columns["#work_series"]
                custcol3['#value#'] = real_dict_series[book]
                mi.set_user_metadata('#work_series', custcol3)
            except:
                custcol3 = custom_columns["#work_series"]
                custcol3['#value#'] = ""
                mi.set_user_metadata('#work_series', custcol3)
            try:
                custcol4 = custom_columns["#work_series_number"]
                s = real_dict_series_index[book]
                s = s.replace("u'","",1)
                s = s.replace("'","", 1)
                try:
                    n = float(s)
                except:
                    try:
                        n = int(s)
                    except:
                        n = 0
                custcol4['#value#'] = n
                mi.set_user_metadata('#work_series_number', custcol4)
            except:
                custcol4 = custom_columns["#work_series_number"]
                custcol4['#value#'] = ""
                mi.set_user_metadata('#work_series_number', custcol4)
            try:
                custcol5 = custom_columns["#work_series_full"]
                w_series = real_dict_series[book]
                w_seriesindex = real_dict_series_index[book]
                s = str(n)
                w_seriesfull = str(w_series + " [" + s + "]")
                w_seriesfull = str(w_seriesfull.replace(".0", "", 1))
                w_seriesfull = str(w_seriesfull.replace(".50", ".5", 1))
                w_seriesfull = unicode(w_seriesfull)
                custcol5['#value#'] = w_seriesfull
                mi.set_user_metadata('#work_series_full', custcol5)
            except:
                custcol5 = custom_columns["#work_series_full"]
                custcol5['#value#'] = ""
                mi.set_user_metadata('#work_series_full', custcol5)
            id_map[book] = mi
        del real_dict_series
        del real_dict_series_index
        work_book_ids_list[:] = []
        payload = my_book_list
        edit_metadata_action = self.gui.iactions['Edit Metadata']
        edit_metadata_action.apply_metadata_changes(id_map, callback=self.__explode_custom_column_10_if_needed)
    def copy_selected_work_level(self):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        mynothing = ""
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        self.create_ui_toast_dialog(1)
        try:
            work_book_ids_list[:] = []
        except:
            pass
        work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(work_book_ids_list)
        if  n == 0 or n > 100:
            return error_dialog(self.gui, _('Cannot Copy Selected Real to Work Metadata'),
                                                      _('You may select up to 100 books (only) to perform this action.'), show=True)
        else:
            pass
        db = self.gui.current_db.new_api
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        real_dict_authors = {}
        real_dict_title = {}
        real_dict_series = {}
        real_dict_series_index = {}
        real_dict_tags = {}
        name_auth = "authors"
        name_title = "title"
        name_series = "series"
        name_series_index = "series_index"
        name_tags = "tags"
        my_book_list = []
        name = name_auth
        for row in work_book_ids_list:
            book = row['calibre_id']
            my_book_list.append(book)
            s_book = str(book)
            try:
                my_db.execute('DELETE FROM custom_column_4 WHERE id = "' + s_book + '" ;')
                my_db.execute('DELETE FROM books_custom_column_4_link WHERE book = "' + s_book + '" ;')
                my_db.execute('DELETE FROM custom_column_8 WHERE id = "' + s_book + '" ;')
                my_db.execute('DELETE FROM books_custom_column_8_link WHERE book = "' + s_book + '" ;')
                my_db.execute('DELETE FROM custom_column_10 WHERE id = "' + s_book + '" ;')
                my_db.execute('DELETE FROM books_custom_column_10_link WHERE book = "' + s_book + '" ;')
                my_db.execute('DELETE FROM custom_column_12 WHERE book ="' + s_book + '" ;')
                my_db.execute('DELETE FROM custom_column_13 WHERE id = "' + s_book + '" ;')
                my_db.execute('DELETE FROM books_custom_column_13_link WHERE book = "' + s_book + '" ;')
                my_db.execute('DELETE FROM custom_column_15 WHERE book = "' + s_book + '" ;')
                my_db.execute('DELETE FROM books_custom_column_17_link WHERE book = "' + s_book + '" ;')
                my_db.execute('DELETE FROM books_custom_column_18_link WHERE book = "' + s_book + '" ;')
                my_db.execute('DELETE FROM _book_awards_mapping WHERE book = "' + s_book + '" ;')
                my_db.commit()
                sleep(.5)
            except Exception as e:
                pass
            authors = db.field_for(name, book, default_value=None)
            if not authors:
                continue
            author_is_bad = False
            for item in authors:
                if any(char.isdigit() for char in item):
                    author_is_bad = True
                if ":" in item or "{" in item:
                    author_is_bad = True
            if author_is_bad:
                author = " "
                for item in authors:
                    author = author + " & " + item
                author = author.strip()
                if author.startswith("&"):
                    author = author[1: ]
                author = author.strip()
            else:
                for item in authors:
                    author = item
                    break
            if not isinstance(author, unicode):
                author = "u'" + author + "'"
            real_dict_authors[book] = author
        try:
            mysql = "DELETE FROM custom_column_4 WHERE value = null "
            my_db.execute(mysql)
            mysql = "DELETE FROM books_custom_column_4_link WHERE value NOT IN (SELECT id FROM custom_column_4 WHERE id > '0')"
            my_db.execute(mysql)
            mysql = "DELETE FROM books_custom_column_8_link WHERE value NOT IN (SELECT id FROM custom_column_8 WHERE id > '0')"
            my_db.execute(mysql)
            mysql = "DELETE FROM books_custom_column_10_link WHERE value NOT IN (SELECT id FROM custom_column_10 WHERE id > '0')"
            my_db.execute(mysql)
            mysql = "DELETE FROM books_custom_column_13_link WHERE value NOT IN (SELECT id FROM custom_column_13 WHERE id > '0')"
            my_db.execute(mysql)
            my_db.commit()
            sleep(0.5)
            mysql = "DELETE FROM custom_column_4 WHERE id NOT IN (SELECT value FROM books_custom_column_4_link WHERE value > '0')"
            my_db.execute(mysql)
            mysql = "DELETE FROM custom_column_8 WHERE id NOT IN (SELECT value FROM books_custom_column_8_link WHERE value > '0')"
            my_db.execute(mysql)
            mysql = "DELETE FROM custom_column_10 WHERE id NOT IN (SELECT value FROM books_custom_column_10_link WHERE value > '0')"
            my_db.execute(mysql)
            mysql = "DELETE FROM custom_column_13 WHERE id NOT IN (SELECT value FROM books_custom_column_13_link WHERE value > '0')"
            my_db.execute(mysql)
            my_db.commit()
            sleep(0.5)
        except Exception as e:
            pass
        sleep(1.0)
        my_db.close()
        self.force_refresh_of_cache(my_book_list)
        name = name_title
        for row in work_book_ids_list:
            book = row['calibre_id']
            title = db.field_for(name, book, default_value=None)
            if not title:
                continue
            if not isinstance(title, unicode):
                title = "u'" + title + "'"
            real_dict_title[book] = title
        name = name_series
        for row in work_book_ids_list:
            book = row['calibre_id']
            series = db.field_for(name, book, default_value=None)
            if not series:
                continue
            if not isinstance(series, unicode):
                series = "u'" + series + "'"
            real_dict_series[book] = series
        name = name_series_index
        for row in work_book_ids_list:
            book = row['calibre_id']
            series_index = str(db.field_for(name, book, default_value=None))
            series_index = str(series_index.replace(".0", "", 1))
            series_index = str(series_index.replace(".50", ".5", 1))
            if not series_index:
                continue
            if not isinstance(series_index, unicode):
                series_index = "u'" + series_index + "'"
            real_dict_series_index[book] = series_index
        name = name_tags
        for row in work_book_ids_list:
            book = row['calibre_id']
            tags = db.field_for(name, book, default_value=None)
            if not tags:
                continue
            s = ""
            for item in tags:
                s = s + item + ","
            s = s.strip()
            s = s[0:-1]
            s = s.replace(",", ", ", 50)
            tags = s
            if not isinstance(tags, unicode):
                tags = "u'" + tags + "'"
            real_dict_tags[book] = tags
        name_auth = "authors"
        name_title = "title"
        name_series = "series"
        name_series_index = "series_index"
        name_tags = "tags"
        custom_columns = self.gui.current_db.field_metadata.custom_field_metadata()
        id_map = {}
        for row in work_book_ids_list:
            book = row['calibre_id']
            mi = Metadata(_('Unknown'))
            try:
                custcol1 = custom_columns["#work_author"]
                custcol1['#value#'] = real_dict_authors[book]
                mi.set_user_metadata('#work_author', custcol1)
            except:
                pass
            try:
                custcol2 = custom_columns["#work_title"]
                custcol2['#value#'] = real_dict_title[book]
                mi.set_user_metadata('#work_title', custcol2)
            except:
                pass
            try:
                custcol3 = custom_columns["#work_series"]
                custcol3['#value#'] = real_dict_series[book]
                mi.set_user_metadata('#work_series', custcol3)
            except:
                custcol3 = custom_columns["#work_series"]
                custcol3['#value#'] = ""
                mi.set_user_metadata('#work_series', custcol3)
            try:
                custcol4 = custom_columns["#work_series_number"]
                s = real_dict_series_index[book]
                s = s.replace("u'","",1)
                s = s.replace("'","", 1)
                try:
                    n = float(s)
                except:
                    try:
                        n = int(s)
                    except:
                        n = 0
                custcol4['#value#'] = n
                mi.set_user_metadata('#work_series_number', custcol4)
            except:
                custcol4 = custom_columns["#work_series_number"]
                custcol4['#value#'] = ""
                mi.set_user_metadata('#work_series_number', custcol4)
            try:
                custcol5 = custom_columns["#work_series_full"]
                w_series = real_dict_series[book]
                w_seriesindex = real_dict_series_index[book]
                s = str(n)
                w_seriesfull = str(w_series + " [" + s + "]")
                w_seriesfull = str(w_seriesfull.replace(".0", "", 1))
                w_seriesfull = str(w_seriesfull.replace(".50", ".5", 1))
                w_seriesfull = unicode(w_seriesfull)
                custcol5['#value#'] = w_seriesfull
                mi.set_user_metadata('#work_series_full', custcol5)
            except:
                custcol5 = custom_columns["#work_series_full"]
                custcol5['#value#'] = ""
                mi.set_user_metadata('#work_series_full', custcol5)
            try:
                custcol6 = custom_columns["#work_tags"]
                custcol6['#value#'] = real_dict_tags[book]
                mi.set_user_metadata('#work_tags', custcol6)
            except:
                custcol6 = custom_columns["#work_tags"]
                custcol6['#value#'] = ""
                mi.set_user_metadata('#work_tags', custcol6)
            custcol7 = custom_columns["#work_freeze"]
            custcol7['#value#'] = "0"
            mi.set_user_metadata('#work_freeze', custcol7)
            custcol8 = custom_columns["#status"]
            custcol8['#value#'] = "dirty"
            mi.set_user_metadata('#status', custcol8)
            id_map[book] = mi
        del real_dict_authors
        del real_dict_title
        del real_dict_series
        del real_dict_series_index
        del real_dict_tags
        work_book_ids_list[:] = []
        payload = my_book_list
        try:
            self.ui_toast_dialog.close()
        except:
            pass
        edit_metadata_action = self.gui.iactions['Edit Metadata']
        edit_metadata_action.apply_metadata_changes(id_map, callback=self.__explode_custom_column_4_if_needed)
        try:
            self.ui_toast_dialog.close()
        except:
            pass
    def scrub_selected_book_level(self):
        global guidb
        global library_is_quarantine_db
        global work_book_ids_list
        global scrubbed_books_final
        work_book_ids_list = []
        scrubbed_books_final = []
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            n = len(book_ids_list0)
            if  n <= 0:
                return error_dialog(self.gui, _('Cannot Scrub Metadata'),
                                                          _('You must select one or more books to perform this action.'), show=True)
            else:
                book_ids_list = []
                for row in book_ids_list0:
                    s = row["calibre_id"]
                    book_ids_list.append(str(s))
                n = len(book_ids_list)
                if n == 0:
                    return error_dialog(self.gui, _('Cannot Scrub Metadata'),
                                                          _('You must select one or more books to perform this action.'), show=True)
                else:
                    start_threaded_book_level(self, self.gui, book_ids_list, scrubbed_books_final, my_plugin_path, Dispatcher(self._scrub_selected_book_level_foreground))
                    self.create_ui_toast_dialog_for_jobs()
        if self.my_jobs_dialog_object:
            if self.my_jobs_dialog_object <> "unknown":
                try:
                    self.my_jobs_dialog_object.show()
                except:
                    pass
    def _scrub_selected_book_level_foreground(self, job):
        global guidb
        global work_book_ids_list
        global scrubbed_books_final
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed to Scrub Selected Books in Foreground/GUI'))
            pass
        scrubbed_books_final = job.result
        if not scrubbed_books_final:
            scrubbed_books_final = []
        books_to_refresh = []
        for row in scrubbed_books_final:
            book, authname, booktitle, seriesname, seriesindex, seriesfull,tagsall = row
            work_dict_book = book
            book = work_dict_book['calibre_id']
            books_to_refresh.append(book)
        self.force_refresh_of_cache(books_to_refresh)
        return
    def _finish_displaying_foreground_scrub(self, payload):
        global guidb
        global work_book_ids_list
        books_to_refresh = payload
        self.gui.library_view.model().refresh_ids(list(books_to_refresh))
        self.gui.tags_view.recount()
        try:
            work_book_ids_list[:] = []
        except:
            work_book_ids_list = []
        marked_ids = {}
        for line in books_to_refresh:
            marked_ids[line] = 'book_scrubbed'
        self.gui.current_db.set_marked_ids(marked_ids)
        self.gui.search.set_search_string('marked:book_scrubbed')
    def force_refresh_of_cache(self, books_to_refresh):
        backend = self.gui.library_view.model().db.backend
        mydbcache = dbcache(self.gui.library_view.model().db.backend)
        mydbcache.init()
        self.gui.library_view.model().refresh_ids(list(books_to_refresh))
        self.gui.tags_view.recount()
    def send_suggestions(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            return
        pm_url = "http://www.mobileread.com/forums/private.php?do=newpm&u=219888"
        webbrowser.open(pm_url)
    def purge_pristine_tables(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        if question_dialog(self.gui, "QuarantineAndScrub","Purge The Pristine Authors/Series Tables in Q&S? [Advisable if Not Really Pristine]"):
            pass
        else:
            return
        self.create_ui_toast_dialog(1)
        guidb = self.gui.library_view.model().db
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        mysql = "DELETE FROM _pristine_authors WHERE id NOT NULL"
        my_db.execute(mysql)
        my_db.commit()
        sleep(0.02)
        mysql = "DELETE FROM _pristine_author_series_link WHERE id NOT NULL"
        my_db.execute(mysql)
        my_db.commit()
        sleep(0.02)
        mysql = "DELETE FROM _pristine_series WHERE id NOT NULL"
        my_db.execute(mysql)
        my_db.commit()
        sleep(0.02)
        sleep(2.0)
        self.ui_toast_dialog.close()
    def copy_real_author_to_pristine_author_table(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        mynothing = ""
        guidb = self.gui.library_view.model().db
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n <= 0:
            return
        else:
            book_ids_list = []
            for row in book_ids_list0:
                s = row["calibre_id"]
                book_ids_list.append(str(s))
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        tmp_auth_rows = []
        del tmp_auth_rows
        for row in book_ids_list:
            book = str(row)
            try:
                mysql = str("SELECT name FROM __book_author_name_sort WHERE book = '" + book + "' ;")
                my_cursor.execute(mysql)
                tmp_auth_rows = my_cursor.fetchall()
                if tmp_auth_rows:
                    for row in tmp_auth_rows:
                        s = str(row)
                        s_string = str(s)
                        s_string = s_string.replace('(u"', mynothing, 100)
                        s_string = s_string.replace("(u'", mynothing, 100)
                        s_string = s_string.replace("',))", mynothing, 100)
                        s_string = s_string.replace("',)", mynothing, 100)
                        s_string = s_string.replace('",)', mynothing, 100)
                        s_string = s_string.replace('u"', mynothing, 100)
                        s_string = s_string.replace("u'", mynothing, 100)
                        s_string = s_string.replace("'(", "'", 100)
                        s_string = s_string.replace(")'", "'", 100)
                        s_string = s_string.replace('u"', mynothing, 100)
                        s_string = s_string.replace('")', mynothing, 100)
                        s_string = s_string.replace("|", ",", 2)
                        s_author = str(s_string)
                        n1 = s_author.count(",")
                        if n1 == 0:
                            s_list = s_author.split(" ")
                            s_lastname = str(s_list[1])
                            s_firstname = str(s_list[0])
                            s_sort = str(str(s_lastname + ", " + s_firstname))
                        else:
                            s_sort = str(s_author)
                            s_list = s_author.split(",")
                            s_lastname = str(s_list[0])
                            s_firstname = str(s_list[1])
                            s_author = str(str(s_firstname + " " + s_lastname))
                        s_author = str(s_author.strip())
                        s_sort = str(s_sort.strip())
                        break
                else:
                    continue
                n1 = s_author.count("-")
                n2 = s_author.count(":")
                if n1 == 0 and n2 == 0:
                    mysql = str("INSERT OR IGNORE INTO _pristine_authors (id,name,sort) VALUES (null, '" + s_author + "','" + s_sort + "') ;")
                    my_db.execute(mysql)
                    my_db.commit()
                    sleep(0.02)
                else:
                    pass
            except Exception as e:
                pass
        my_db.close()
        info_dialog(self.gui, _('Copy Selected Real Author to Pristine Author Table'),
                                        _('The Selected Real Authors Were Copied to the Pristine Author Table'), show=True)
    def delete_all_identifiers_except_isbn(self, guidb):
        self.create_ui_toast_dialog(1)
        mynothing = ""
        guidb = self.gui.library_view.model().db
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n <= 0:
            return
        else:
            book_ids_list = []
            for row in book_ids_list0:
                s = row["calibre_id"]
                book_ids_list.append(str(s))
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in book_ids_list:
            book = str(row)
            try:
                mysql = str("DELETE FROM identifiers WHERE book = '" + book + "' AND (type <> 'isbn') AND (type <> 'oclc-owi') AND (type <> 'issn');")
                my_cursor.execute(mysql)
            except Exception as e:
                pass
        try:
            my_db.commit()
            sleep(0.5)
        except:
            pass
        my_db.close()
        self.force_refresh_of_cache(book_ids_list)
        try:
            self.ui_toast_dialog.close()
        except:
            pass
        info_dialog(self.gui, _('Non-ISBN/ISSN/OCLC Identifiers Deleted for Selected Book(s)'),
                                        _('The Non-ISBN/ISSN/OCLC Identifiers Were Deleted for the Selected Book(s)'), show=True )
    def reset_last_modified_date(self, guidb):
        if question_dialog(self.gui, "QuarantineAndScrub", "Reset Pristine Library Books' Last Modified Date Per the 24 Hour Rule? "):
            pass
        else:
            return
        mynothing = ""
        guidb = self.gui.library_view.model().db
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        from datetime import datetime, date, time, timedelta
        today = datetime.utcnow()
        h = -24
        t = timedelta(hours=h)
        yesterday = str(today + t)
        yesterday = str(yesterday + "+00:00")
        from calibre_plugins.quarantine_and_scrub.config import prefs
        pristine_path = prefs['pristine_library']
        try:
            s1 = "ATTACH DATABASE '"
            s2 =  "'  As 'Calibre';"
            mysql = s1 + pristine_path + s2
            if isbytestring(mysql):
                mysql = mysql.decode(filesystem_encoding)
            my_cursor.execute(mysql)
        except Exception as e:
            s = str(e)
            my_db.close()
            return
        try:
            mysql = str("DROP TRIGGER IF EXISTS Calibre.books_update_trg")
            my_cursor.execute(mysql)
            my_db.commit()
            sleep(0.02)
            mysql = str("UPDATE Calibre.books SET last_modified = ? WHERE last_modified >= ? ")
            my_cursor.execute(mysql, (yesterday, yesterday))
            my_db.commit()
            sleep(0.02)
        except:
            pass
        try:
            mysql = str("CREATE TRIGGER IF NOT EXISTS Calibre.books_update_trg AFTER UPDATE ON books \
             BEGIN UPDATE books SET sort=title_sort(NEW.title) WHERE id=NEW.id AND OLD.title <> NEW.title; END ")
            my_cursor.execute(mysql)
            my_db.commit()
        except:
            pass
        mysql = "DETACH DATABASE 'Calibre' "
        my_cursor.execute(mysql)
        my_db.close()
        info_dialog(self.gui, _('Last Modified Date Changed in Pristine Library Per the 24 Hour Rule'),
                                        _('Last Modified Date Changed in Pristine Library Per the 24 Hour Rule'), show=True)
    def default_work_tags_by_author(self, guidb):
        mynothing = ""
        guidb = self.gui.library_view.model().db
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n <= 0:
            return
        else:
            book_ids_list = []
            for row in book_ids_list0:
                s = row["calibre_id"]
                book_ids_list.append(str(s))
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        from calibre_plugins.quarantine_and_scrub.config import prefs
        best_path = prefs['qs_library_best_rules']
        best_path = str(best_path.replace(os.sep, '/'))
        nq = best_path.count("QuarantineAndScrub")
        if str(best_path) == str(path):
            do_freshen = True
        else:
            if nq == 0:
                do_freshen = True
            else:
                do_freshen = False
        if do_freshen:
            mysql = "DELETE FROM _tags_by_author "
            my_cursor.execute(mysql)
            my_db.commit()
            sleep(0.1)
            mysql = "INSERT OR REPLACE INTO _tags_by_author SELECT authname,tagname,book_count FROM __tags_by_author_with_count;   "
            my_cursor.execute(mysql)
            my_db.commit()
            sleep(0.1)
            mysql = "INSERT OR IGNORE INTO _tags_by_author_default SELECT name,tag  FROM _tags_by_author \
                                WHERE name NOT LIKE '%9%' AND name NOT LIKE '%(%' AND name NOT LIKE '%#%' AND name NOT LIKE '%*%' \
                                    AND name NOT LIKE '%0%' AND name NOT LIKE '%[%'  ;"
            my_cursor.execute(mysql)
            my_db.commit()
            sleep(0.1)
            list_of_tags = []
            mysql = str("SELECT name FROM tags WHERE name IN(SELECT oldtag FROM _tag_rules WHERE newtag NOT NULL AND purgetag = 0 )")
            my_cursor.execute(mysql)
            tmp_rows = my_cursor.fetchall()
            if not tmp_rows:
                pass
            else:
                for item in tmp_rows:
                    for col in item:
                        tag = col
                        list_of_tags.append(tag)
                del tmp_rows
            sleep(0.1)
            for row in list_of_tags:
                tag = row
                if tag == None or tag == mynothing:
                    continue
                try:
                    mysql = str("UPDATE  _tags_by_author_default SET tag =(SELECT newtag FROM _tag_rules WHERE  _tag_rules.oldtag = _tags_by_author_default.tag \
                                            AND  _tag_rules.newtag NOT NULL AND  _tag_rules.purgetag = 0 AND _tags_by_author_default.tag NOT NULL) WHERE tag = ? \
                                            AND tag NOT NULL")
                    my_cursor.execute(mysql,(tag,))
                except Exception as e:
                    continue
            try:
                my_db.commit()
            except:
                pass
        else:
            pass
        mysql = "INSERT or REPLACE INTO custom_column_13 (id,value) SELECT book, tagsall FROM __books_work_populate WHERE tagsall not null  ; "
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.1)
        mysql = "UPDATE books_custom_column_13_link  SET value = books_custom_column_13_link.book "
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.1)
        mysql = "DELETE FROM custom_column_13 WHERE id NOT IN (SELECT value FROM books_custom_column_13_link)"
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.1)
        n = len(book_ids_list)
        book_tag_dict = {}
        for row in book_ids_list:
            book = str(row)
            book = str(self.__strip_bytestring(book))
            tmp_rows = []
            del tmp_rows
            try:
                mysql = str("SELECT authname FROM __books_work_populate WHERE book = ?")
                my_cursor.execute(mysql,(book,))
                tmp_rows = my_cursor.fetchall()
                if not tmp_rows:
                    continue
                else:
                    for item in tmp_rows:
                        for col in item:
                            authname = col
                del tmp_rows
                mysql = str("SELECT tag FROM _tags_by_author_default WHERE name = ? ")
                my_cursor.execute(mysql,(authname,))
                tmp_rows = my_cursor.fetchall()
                if not tmp_rows:
                    continue
                else:
                    for item in tmp_rows:
                        for col in item:
                            tag = col
                            break
                        break
                    book_tag_dict[book] = tag
            except Exception as e:
                continue
        for book,tag in book_tag_dict.iteritems():
            mysql = "DELETE FROM custom_column_13 WHERE id IN(SELECT value FROM books_custom_column_13_link WHERE book = ? )"
            my_cursor.execute(mysql,(book,))
            mysql = "DELETE FROM books_custom_column_13_link WHERE book = ? "
            my_cursor.execute(mysql,(book,))
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        for book,tag in book_tag_dict.iteritems():
            mysql = "INSERT OR REPLACE INTO custom_column_13 (id,value) VALUES(?,?)"
            my_cursor.execute(mysql,(book,tag))
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        for book,tag in book_tag_dict.iteritems():
            mysql = "INSERT OR REPLACE INTO books_custom_column_13_link (id,book,value) VALUES(?,?,?)"
            my_cursor.execute(mysql,(book,book,book))
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        mysql = "DELETE FROM custom_column_13 WHERE id NOT IN(SELECT value FROM books_custom_column_13_link)"
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.1)
        my_db.close()
        self.force_refresh_of_cache(book_ids_list)
    def reset_default_work_tag_by_author(self,guidb):
        mynothing = ""
        guidb = self.gui.library_view.model().db
        book_ids_list0 = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list0)
        if  n <= 0:
            return
        else:
            book_ids_list = []
            for row in book_ids_list0:
                s = row["calibre_id"]
                book_ids_list.append(str(s))
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        n = len(book_ids_list)
        if not n > 0:
            return
        updates_made = False
        for row in book_ids_list:
            book = str(row)
            book = str(self.__strip_bytestring(book))
            try:
                mysql = str("SELECT authname,tagsall FROM __books_work_populate WHERE book = ? AND tagsall NOT NULL AND authname NOT NULL")
                my_cursor.execute(mysql,(book,))
                tmp_rows = my_cursor.fetchall()
                if not tmp_rows:
                    continue
                else:
                    for item in tmp_rows:
                        authname,tagsall = item
                    if ( "," in tagsall) or (tagsall == mynothing) or (tagsall == None) or (not tagsall > " ") :
                        continue
                    else:
                        mysql = "INSERT OR REPLACE INTO _tags_by_author_default (name,tag) VALUES (?,?)"
                        my_cursor.execute(mysql,(authname,tagsall))
                        updates_made = True
            except:
                continue
        if updates_made :
            try:
                my_db.commit()
                sleep(0.1)
            except:
                pass
        my_db.close()
    def _get_all_tags(self, guidb):
        tags_list_of_lists = []
        tags = []
        guidb = self.gui.library_view.model().db
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        mysql = "INSERT OR IGNORE INTO _tags_copied_from_other_libraries SELECT null, name FROM tags WHERE tags.name NOT LIKE '%978%' "
        my_cursor.execute(mysql)
        my_db.commit()
        mysql = str("SELECT name FROM _tags_copied_from_other_libraries WHERE ('/'||name||'/'  NOT IN(SELECT oldtag FROM _tag_rules \
                                                WHERE oldtag NOT NULL) ) AND (name NOT IN(SELECT oldtag FROM _tag_rules \
                                                WHERE oldtag NOT NULL) ) AND (name NOT IN(SELECT newtag \
                                                FROM _tag_rules WHERE newtag NOT NULL) ) ")
        my_cursor.execute(mysql)
        tmp_rows = my_cursor.fetchall()
        my_db.close()
        if tmp_rows:
            r = []
            for row in tmp_rows:
                del r
                r = []
                n_col = 0
                for col in row:
                    s1 = col
                    r.append(s1)
                    tags.append(r)
        else:
            return tags_list_of_lists
        tagset = set(map(tuple,tags))
        tags = map(list,tagset)
        tags.sort()
        for row in tags:
            tags_list_of_lists.append(row)
        n = len(tags_list_of_lists)
        return tags_list_of_lists
    def _get_all_tags_2(self, guidb):
        guidb = self.gui.library_view.model().db
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = apsw.Connection(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_cursor.execute(mysql)
        mysql = str("SELECT tag, subject FROM __tags_work_single_subject WHERE tag NOT NULL \
                            AND tag NOT IN(SELECT oldtag FROM _tag_rules) \
                            AND tag NOT IN(SELECT oldtag FROM _tag_rules WHERE oldtag LIKE '%'||'/'||tag||'/'||'%'  ) \
                            AND tag NOT IN(SELECT oldtag FROM _tag_rules WHERE oldtag = '/'||tag||'/'   )  ")
        my_cursor.execute(mysql)
        tmp_rows = my_cursor.fetchall()
        my_db.close()
        tags_list_of_lists = []
        tags = []
        if tmp_rows:
            if len(tmp_rows) == 0:
                info_dialog(self.gui, _('Nothing to Show You'),
                    _('Nothing to Show You.  Perhaps: (1) nothing needs to be done; or, (2) a Tag Scrubber job for all books (to identify tags with no rules) needs to be run.'), show=True)
                return tags_list_of_lists
            else:
                pass
            r = []
            for row in tmp_rows:
                del r
                r = []
                n_col = 0
                for col in row:
                    if (not col) or (col is None):
                        col = "No BISAC Match"
                    if n_col == 0:
                        s1 = col
                        r.append(s1)
                        n_col = 1
                    else:
                        s2 = col
                        if s2.endswith("-*"):
                            s2 = s2[0:-2]
                        if s2.endswith("-") or s2.endswith("*"):
                            s2 = s2[0:-1]
                        r.append(s2)
                        tags.append(r)
                        break
        else:
            info_dialog(self.gui, _('Nothing to Show You'),
                _('Nothing to Show You.  Perhaps: (1) nothing needs to be done; or, (2) a Tag Scrubber job for all books (to identify tags with no rules) needs to be run.'), show=True)
            return tags_list_of_lists
        tagset = set(map(tuple,tags))
        tags = map(list,tagset)
        tags.sort()
        for row in tags:
            tags_list_of_lists.append(row)
        n = len(tags_list_of_lists)
        return tags_list_of_lists
    def __strip_bytestring(self,s):
        mynothing = ""
        s_string = str(s)
        s_string = s_string.replace('(u"', mynothing, 100)
        s_string = s_string.replace("(u'", mynothing, 100)
        s_string = s_string.replace("',))", mynothing, 100)
        s_string = s_string.replace("',)", mynothing, 100)
        s_string = s_string.replace('",)', mynothing, 100)
        s_string = s_string.replace('u"', mynothing, 100)
        s_string = s_string.replace("u'", mynothing, 100)
        s_string = s_string.replace("'(", "'", 100)
        s_string = s_string.replace(")'", "'", 100)
        s_string = s_string.replace('u"', mynothing, 100)
        s_string = s_string.replace('")', mynothing, 100)
        s_string = s_string.replace(",", mynothing, 100)
        s = str(s_string)
        return str(s)
    def _actually_insert_tag_rules_rows(self, guidb, list):
        mynothing = ""
        if list:
            n = len(list)
            if n == 0:
                return
        else:
            return
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in list:
            s = row.split("|||")
            a = s[0]
            b = s[1]
            a = a.strip()
            b = b.strip()
            if (not a > " "):
                continue
            if isinstance(a, str):
                try:
                    a = unicode(a, errors='ignore')
                except:
                    pass
            else:
                pass
            if str(b) == '1':
                s1 = "/" + a + "/"
            else:
                s1 = a
            s2 = '1'
            if str(a) <> str(s1):
                mysql = str('INSERT OR REPLACE INTO _tag_rules (id,oldtag,newtag,purgetag) VALUES (null, ?, null, ?)   ')
                my_cursor.execute(mysql,(s1,s2))
            mysql = str('INSERT OR REPLACE INTO _tag_rules (id,oldtag,newtag,purgetag) VALUES (null, ?, null, ?)   ')
            my_cursor.execute(mysql,(a,s2))
        my_db.commit()
        sleep(0.5)
        my_db.close()
    def _actually_insert_tag_rules_rows_2(self, guidb, list):
        mynothing = ""
        if list:
            n_rows = len(list)
            if n_rows == 0:
                return
        else:
            return
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in list:
            s = row.split("|||")
            a = s[0]
            b = s[1]
            c = s[2]
            a = a.strip()
            b = b.strip()
            if (not a > " ")  and (not b > " ") :
                continue
            if a == b:
                pass
            if isinstance(a, str):
                try:
                    a = unicode(a, errors='ignore')
                except:
                    pass
            else:
                pass
            if str(c) == '1':
                s1 = "/" + a + "/"
            else:
                s1 = a
            s2 = b
            if s2 == "No BISAC Match":
                continue
            if b == "DELETE" or b == "delete" or b == "" or b == '""' or b == "''":
                s2 = None
                mysql = str('INSERT OR REPLACE INTO _tag_rules (id,oldtag,newtag,purgetag) VALUES (null, ?, ?, 1 )   ')
                my_cursor.execute(mysql,(s1,s2))
                if s1 <> a:
                    mysql = str('INSERT OR REPLACE INTO _tag_rules (id,oldtag,newtag,purgetag) VALUES (null, ?, ?, 1 )   ')
                    my_cursor.execute(mysql,(a,s2))
            else:
                mysql = str('INSERT OR REPLACE INTO _tag_rules (id,oldtag,newtag,purgetag) VALUES (null, ?, ?, 0 )   ')
                my_cursor.execute(mysql,(s1,s2))
                if s1 <> a:
                    mysql = str('INSERT OR REPLACE INTO _tag_rules (id,oldtag,newtag,purgetag) VALUES (null, ?, ?, 0 )   ')
                    my_cursor.execute(mysql,(a,s2))
            my_db.commit()
            sleep(0.2)
        sleep(0.5)
        mysql = str("UPDATE _tag_rules SET purgetag = 1 WHERE newtag = 'delete' OR newtag = 'DELETE' ")
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.5)
        mysql = str("UPDATE _tag_rules SET newtag = NULL WHERE purgetag = 1 ")
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.5)
        mysql = str("DELETE FROM _tag_rules WHERE newtag = 'No BISAC Match' ")
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.5)
        mysql = str("INSERT or IGNORE INTO _tag_rules SELECT null,newtag,newtag,'0'  FROM _tag_rules \
                                                    WHERE newtag NOT NULL AND newtag NOT IN (SELECT oldtag FROM _tag_rules) ")
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.5)
        my_db.close()
    def purge_work_data_selected_books(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(work_book_ids_list)
        if  n == 0 or n > 100:
            return error_dialog(self.gui, _('Cannot Purge Workd Data for Selected Books'),
                                                      _('You may select a maximum of 100 books to perform this action.'), show=True)
        else:
            pass
        self.create_ui_toast_dialog(1)
        db = self.gui.current_db.new_api
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        book_ids_list = []
        del book_ids_list
        book_ids_list = []
        for row in work_book_ids_list:
            book = str(row['calibre_id'])
            book = str(self.__strip_bytestring(book))
            book_ids_list.append(str(book))
            try:
                mysql = str("DELETE FROM books_custom_column_4_link WHERE book = [BOOK] ")
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_db.execute(mysql)
                mysql = str("DELETE FROM books_custom_column_8_link WHERE book = [BOOK] ")
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_db.execute(mysql)
                mysql = str("DELETE FROM books_custom_column_10_link WHERE book = [BOOK] ")
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_db.execute(mysql)
                mysql = str("DELETE FROM custom_column_12 WHERE book = [BOOK] ")
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_db.execute(mysql)
                mysql = str("DELETE FROM books_custom_column_13_link WHERE book = [BOOK] ")
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_db.execute(mysql)
                mysql = str("DELETE FROM custom_column_15 WHERE book = [BOOK] ")
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_db.execute(mysql)
                mysql = str("DELETE FROM custom_column_16 WHERE book = [BOOK] ")
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_db.execute(mysql)
                mysql = str("DELETE FROM books_custom_column_17_link WHERE book = [BOOK] ")
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_db.execute(mysql)
                mysql = str("DELETE FROM books_custom_column_18_link WHERE book = [BOOK] ")
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_db.execute(mysql)
                mysql = "DELETE FROM _books_work WHERE book = [BOOK] "
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_cursor.execute (mysql)
                mysql = "DELETE FROM _tags_work WHERE book = [BOOK] "
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_cursor.execute (mysql)
                mysql = "DELETE FROM _instr_title_author WHERE book = [BOOK] "
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_cursor.execute (mysql)
                mysql = "DELETE FROM _book_awards_mapping WHERE book = [BOOK] "
                mysql = str(mysql.replace("[BOOK]", str(book), 1))
                my_cursor.execute (mysql)
            except Exception as e:
                pass
        try:
            my_db.commit()
        except:
            pass
        sleep(0.5)
        mysql = "DELETE FROM custom_column_4 WHERE id NOT IN (SELECT value FROM books_custom_column_4_link WHERE value > '0')"
        my_db.execute(mysql)
        mysql = "DELETE FROM custom_column_8 WHERE id NOT IN (SELECT value FROM books_custom_column_8_link WHERE value > '0')"
        my_db.execute(mysql)
        mysql = "DELETE FROM custom_column_10 WHERE id NOT IN (SELECT value FROM books_custom_column_10_link WHERE value > '0')"
        my_db.execute(mysql)
        mysql = "DELETE FROM custom_column_13 WHERE id NOT IN (SELECT value FROM books_custom_column_13_link WHERE value > '0')"
        my_db.execute(mysql)
        mysql = "DELETE FROM custom_column_17 WHERE id NOT IN (SELECT value FROM books_custom_column_17_link WHERE value > '0')"
        my_db.execute(mysql)
        mysql = "DELETE FROM custom_column_18 WHERE id NOT IN (SELECT value FROM books_custom_column_18_link WHERE value > '0')"
        my_db.execute(mysql)
        my_db.commit()
        sleep(1.0)
        my_db.close()
        sleep(0.1)
        self.force_refresh_of_cache(book_ids_list)
        try:
            self.ui_toast_dialog.close()
        except:
            pass
    def __explode_custom_column_4_if_needed(self, payload):
        try:
            self.ui_toast_dialog.close()
        except:
            pass
        try:
            self.create_ui_toast_dialog(2)
        except:
            pass
        global guidb
        my_book_list = payload
        guidb = self.gui.library_view.model().db
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in my_book_list:
            my_book = str(row)
            try:
                tmp_rows = []
                del tmp_rows
                mysql = str("SELECT value FROM  custom_column_4 WHERE id IN(SELECT value FROM books_custom_column_4_link WHERE book = [BOOK]  )")
                mysql = str(mysql.replace("[BOOK]",my_book,1))
                my_cursor.execute(mysql)
                tmp_rows = my_cursor.fetchall()
                if tmp_rows:
                    for item in tmp_rows:
                        for col in item:
                            correct_value = col
                    my_cursor.execute ("BEGIN TRANSACTION")
                    mysql = "INSERT OR REPLACE INTO _books_work (book,booktitle,authorname,seriesname,seriesindex) VALUES (?,null,?,null,null)"
                    my_db.execute(mysql,(my_book,correct_value))
                    my_db.commit()
                    sleep(0.01)
                else:
                    correct_value = ""
                my_cursor.execute ("BEGIN TRANSACTION")
                mysql = "DELETE FROM books_custom_column_4_link WHERE book = [BOOK] "
                mysql = str(mysql.replace("[BOOK]", str(my_book), 1))
                my_db.execute(mysql)
                my_db.commit()
                sleep(0.01)
                my_cursor.execute ("BEGIN TRANSACTION")
                mysql = "DELETE FROM custom_column_4 WHERE id NOT IN(SELECT value FROM books_custom_column_4_link)"
                my_db.execute(mysql)
                if correct_value == "":
                    mysql = "INSERT OR REPLACE INTO custom_column_4 SELECT book,name FROM __book_author_name_sort \
                                WHERE book = [BOOK] "
                    mysql = str(mysql.replace("[BOOK]", str(my_book), 1))
                    my_db.execute(mysql)
                else:
                    mysql = "INSERT OR REPLACE INTO custom_column_4 (id,value) VALUES (?,?)"
                    my_db.execute(mysql,(my_book,correct_value))
                my_db.commit()
                sleep(0.01)
                my_cursor.execute ("BEGIN TRANSACTION")
                mysql = "INSERT or REPLACE INTO books_custom_column_4_link SELECT book,book,book FROM __book_author_name_sort \
                                                                                                                         WHERE book = [BOOK] "
                mysql = str(mysql.replace("[BOOK]", str(my_book), 1))
                my_db.execute(mysql)
                my_db.commit()
                sleep(0.01)
            except Exception as e:
                my_db.close()
                raise e
                return
        sleep(0.05)
        my_db.close()
        self.force_refresh_of_cache(my_book_list)
        sleep(1.0)
        try:
            self.ui_toast_dialog.close()
        except:
            pass
    def __explode_custom_column_10_if_needed(self, payload):
        global guidb
        my_book_list = payload
        guidb = self.gui.library_view.model().db
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        error_found = False
        for row in my_book_list:
            my_book = str(row)
            try:
                my_cursor.execute ("BEGIN TRANSACTION")
                mysql = "SELECT count(*) FROM __books_work_populate WHERE book = [BOOK] AND authname NOT NULL AND booktitle NOT NULL "
                mysql = str(mysql.replace("[BOOK]", str(my_book), 1))
                my_cursor.execute(mysql)
                count1 = my_cursor.fetchall()
                for row in count1:
                    for col in row:
                        count1 = str(col)
                count1 = str(self.__strip_bytestring(count1))
                if not str(count1) > str("0") :
                    try:
                        error_found = True
                        my_cursor.execute ("BEGIN TRANSACTION")
                        mysql = "DELETE FROM books_custom_column_10_link WHERE book = [BOOK] "
                        mysql = str(mysql.replace("[BOOK]", str(my_book), 1))
                        my_db.execute(mysql)
                        mysql = "DELETE FROM custom_column_12 WHERE book = [BOOK] "
                        mysql = str(mysql.replace("[BOOK]", str(my_book), 1))
                        my_db.execute(mysql)
                        mysql = "DELETE FROM custom_column_15 WHERE book = [BOOK] "
                        mysql = str(mysql.replace("[BOOK]", str(my_book), 1))
                        my_db.execute(mysql)
                        my_db.commit()
                        sleep(0.01)
                        my_cursor.execute ("BEGIN TRANSACTION")
                        mysql = "DELETE FROM custom_column_10 WHERE id NOT IN(SELECT value FROM books_custom_column_10_link)"
                        my_db.execute(mysql)
                        my_db.commit()
                        sleep(0.01)
                        continue
                    except Exception as e:
                        my_db.close()
                        raise e
                        return
                else:
                    try:
                        sleep(0.01)
                        my_cursor.execute ("BEGIN TRANSACTION")
                        mysql = "DELETE FROM books_custom_column_10_link WHERE book = [BOOK] "
                        mysql = str(mysql.replace("[BOOK]", str(my_book), 1))
                        my_db.execute(mysql)
                        my_db.commit()
                        sleep(0.01)
                        my_cursor.execute ("BEGIN TRANSACTION")
                        mysql = "INSERT OR REPLACE INTO custom_column_10 SELECT book,name FROM __book_series_name_sort \
                                                                                                                WHERE book = [BOOK] "
                        mysql = str(mysql.replace("[BOOK]", str(my_book), 1))
                        my_db.execute(mysql)
                        my_db.commit()
                        sleep(0.01)
                        my_cursor.execute ("BEGIN TRANSACTION")
                        mysql = "INSERT or REPLACE INTO books_custom_column_10_link SELECT book,book,book FROM __book_series_name_sort \
                                                                                                                                 WHERE book = [BOOK] "
                        mysql = str(mysql.replace("[BOOK]", str(my_book), 1))
                        my_db.execute(mysql)
                        my_db.commit()
                        sleep(0.01)
                    except Exception as e:
                        my_db.close()
                        raise e
                        return
            except Exception as e:
                my_db.close()
                raise e
                return
        sleep(0.01)
        my_cursor.execute ("BEGIN TRANSACTION")
        mysql = "DELETE FROM custom_column_10 WHERE id NOT IN(SELECT value FROM books_custom_column_10_link)"
        my_db.execute(mysql)
        sleep(0.01)
        mysql = "DELETE FROM custom_column_12 WHERE book NOT IN(SELECT book FROM books_custom_column_10_link)"
        my_db.execute(mysql)
        sleep(0.01)
        mysql = "DELETE FROM custom_column_15 WHERE book NOT IN(SELECT book FROM books_custom_column_10_link)"
        my_db.execute(mysql)
        my_db.commit()
        sleep(0.05)
        my_db.close()
        self.force_refresh_of_cache(my_book_list)
        if  error_found:
            info_dialog(self.gui, 'Invalid Action(s) Requested','Cannot Have Work Series Without a Work Author and Work Title!').show()
            return
    def scrub_begin_dialog_derive_genres(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            book_ids_list = []
            book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
            n = len(book_ids_list)
            if  n == 0:
                info_dialog(self.gui, 'No Books Selected','No Books Were Selected for Derive Genres').show()
                return
            else:
                pass
            self._question_then_go_derive_genres(book_ids_list)
    def _question_then_go_derive_genres(self, book_ids_list):
        global guidb
        global library_is_quarantine_db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        else:
            tmp_list = []
            for item in book_ids_list:
                s = str(item['calibre_id'])
                tmp_list.append(s)
            del book_ids_list
            tmp_set = set(tmp_list)
            book_ids = list(tmp_set)
            del tmp_list
            del tmp_set
            payload = book_ids
            results_status = []
            if question_dialog(self.gui, "QuarantineAndScrub", (" Derive Genres [Q&S Version] \
                                                    <br><br>GUI Will Be Locked While Job Runs <br><br>Continue?")):
                start_threaded_derive_genres(self, self.gui, book_ids, results_status, Dispatcher(self._scrub_derive_genres_no_restart))
                self.create_ui_toast_dialog_for_jobs()
                if self.my_jobs_dialog_object:
                    if self.my_jobs_dialog_object <> "unknown":
                        try:
                            self.my_jobs_dialog_object.show()
                        except:
                            pass
            else:
                 info_dialog(self.gui, 'Canceled',
                     'Derive Genres is Canceled').show()
    def _scrub_derive_genres_no_restart(self, job):
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed: Q&S Derive Genres.....'))
            pass
        else:
            pass
        db = self.gui.current_db.new_api
        work_book_ids_frozenset = db.all_book_ids()
        books_to_refresh = []
        for row in work_book_ids_frozenset:
            books_to_refresh.append(row)
        self.gui.status_bar.show_message(_('Q&S Derive Genres Has Completed'), 5000)
        self.force_refresh_of_cache( books_to_refresh)
    def autopopulate_tags_by_comment(self,guidb):
        chosen_option = '0'
        self.choices_dialog = AutoPopChoicesDialog(self.gui,self.qaction.icon(),guidb,chosen_option,self.autopopulate_tags_by_comment_part2)
        self.choices_dialog.show()
    def autopopulate_tags_by_comment_part2(self,guidb,chosen_option):
        if chosen_option == '1':
            candidates_only = True
            info_dialog(self.gui, "Candidates Only Selected"\
                                            ,"<big><font color='#0404B4'><B>[1] Review Table _tags_by_comment_candidates</B></font></big>").show()
        else:
            self.choices_dialog.close()
            candidates_only = False
            info_dialog(self.gui, "Full Update Selected"\
                                            ,"<big><font color='#0404B4'><B>[2] Review Table _tags_by_comment Prior to Tag Scrubbing</B></font></big>").show()
        guidb = self.gui.library_view.model().db
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = apsw.Connection(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_cursor.execute(mysql)
        if candidates_only:
            my_cursor.execute("begin")
            mysql = "INSERT OR IGNORE INTO _tags_by_comment_candidates SELECT oldtag,newtag FROM __tags_by_comment_potential_rules;"
            my_cursor.execute(mysql)
            my_cursor.execute("commit")
            sleep(0.01)
        else:
            my_cursor.execute("begin")
            mysql = "INSERT OR IGNORE INTO _tags_by_comment SELECT null,comment,tag FROM  _tags_by_comment_candidates \
                            WHERE comment NOT IN(SELECT comment FROM _tags_by_comment WHERE tag = _tags_by_comment_candidates.tag ) ;"
            my_cursor.execute(mysql)
            my_cursor.execute("commit")
            sleep(0.01)
            my_cursor.execute("begin")
            mysql = "DELETE FROM _tags_by_comment_candidates;"
            my_cursor.execute(mysql)
            my_cursor.execute("commit")
            sleep(0.01)
    def populate_ddc_lcc_using_classify_api(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        from calibre_plugins.quarantine_and_scrub.config import prefs
        ddc_name = prefs['ddc']
        lcc_name = prefs['lcc']
        is_active = False
        active = prefs['classify']
        if active == "True":
            is_active = True
        if not is_active:
            return error_dialog(self.gui, _('Cannot Update DDC & LCC for Selected Books'),
                                                      _('You Have Not Activated This Functionality in Configuration.'), show=True)
        ddc_exists = True
        lcc_exists = True
        n = len(ddc_name)
        if (not ddc_name.startswith("#")) or (n < 2) or (ddc_name == "#none") or (ddc_name == "none"):
            ddc_exists = False
        n = len(lcc_name)
        if (not lcc_name.startswith("#")) or (n < 2) or (lcc_name == "#none") or (lcc_name == "none"):
            lcc_exists = False
        if ((not ddc_exists) and (not lcc_exists)):
            return error_dialog(self.gui, _('Cannot Update DDC & LCC for Selected Books'),
                                                      _('You Must Configure the Lookup Name for at least one of DDC or LCC.'), show=True)
        work_book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(work_book_ids_list)
        if  n == 0:
            return error_dialog(self.gui, _('Cannot Update DDC & LCC for Selected Books'),
                                                      _('You must select at least one (1) book to perform this action.'), show=True)
        else:
            pass
        db = self.gui.current_db.new_api
        isbn_dict = self._get_isbn_identifiers_for_selected_books(guidb,work_book_ids_list, ddc_name, lcc_name)
        if not isbn_dict:
            return error_dialog(self.gui, _('Cannot Update DDC & LCC for Selected Books'),
                                                      _('ISBN/ISSN for at least one of the selected books, plus DDC and/or LCC Custom Columns, must exist in this specific Q&S library order to perform this action.'), show=True)
        self.create_ui_toast_dialog(1)
        book_ids_list = []
        del book_ids_list
        book_ids_list = []
        ddc_dict = {}
        lcc_dict = {}
        owi_dict = {}
        final_list = []
        for row in work_book_ids_list:
            book = str(row['calibre_id'])
            book = str(self.__strip_bytestring(book))
            try:
                isbn = isbn_dict[book]
                if isbn:
                    paramtype = "stdnbr"
                    paramvalue = isbn
                    ddc,lcc,owi = oclc_classify_api(paramtype,paramvalue)
                    was_found = False
                    if ddc <> "NONE":
                        ddc_dict[book] = ddc
                        was_found = True
                    if lcc <> "NONE":
                        lcc_dict[book] = lcc
                        was_found = True
                    if owi <> "NONE":
                        owi_dict[book] = owi
                    if was_found:
                        book_ids_list.append(str(book))
                else:
                    continue
            except Exception as e:
                continue
        nd = len(ddc_dict)
        nl = len(lcc_dict)
        if nd == 0 and nl == 0:
            try:
                self.ui_toast_dialog.close()
            except:
                pass
            info_dialog(self.gui, "DDC & LCC ", "Nothing Found For Any Selected Book")
            return
        custom_columns = self.gui.current_db.field_metadata.custom_field_metadata()
        n = len(book_ids_list)
        if n > 0:
            id_map = {}
            for row in book_ids_list:
                data_changed = False
                book = str(row)
                mi = Metadata(_('Unknown'))
                try:
                    custcol1 = custom_columns[ddc_name]
                    custcol1['#value#'] = unicode(ddc_dict[book])
                    mi.set_user_metadata(ddc_name, custcol1)
                    data_changed = True
                except Exception as e:
                    pass
                try:
                    custcol2 = custom_columns[lcc_name]
                    custcol2['#value#'] = unicode(lcc_dict[book])
                    mi.set_user_metadata(lcc_name, custcol2)
                    data_changed = True
                except Exception as e:
                    pass
                if data_changed:
                    n_book = int(book)
                    id_map[n_book] = mi
                    final_list.append(n_book)
            n = len(final_list)
            if n == 0:
                try:
                    self.ui_toast_dialog.close()
                except:
                    pass
                info_dialog(self.gui, "DDC & LCC ", "Nothing Updated For Any Book")
                return
            payload = final_list, owi_dict
            edit_metadata_action = self.gui.iactions['Edit Metadata']
            edit_metadata_action.apply_metadata_changes(id_map, callback=self._finish_displaying_results_of_ddc_lcc(payload))
        else:
            info_dialog(self.gui, "DDC & LCC ", "Nothing Updated For Any Book")
        try:
            self.ui_toast_dialog.close()
        except:
            pass
    def _finish_displaying_results_of_ddc_lcc(self, payload):
        final_list, owi_dict = payload
        if len(owi_dict) > 0 and len(final_list) > 0:
            self._add_owi_identifiers(owi_dict,final_list)
        self.force_refresh_of_cache(final_list)
        try:
            self.ui_toast_dialog.close()
        except:
            pass
    def _get_isbn_identifiers_for_selected_books(self,guidb,work_book_ids_list, ddc_name, lcc_name):
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = apsw.Connection(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        ddc_name = ddc_name.replace("#","",2)
        lcc_name = lcc_name.replace("#","",2)
        mysql = "SELECT count(*) FROM custom_columns WHERE label = ? OR label = ?"
        my_cursor.execute(mysql,(ddc_name,lcc_name))
        count1 = my_cursor.fetchall()
        for row in count1:
            for col in row:
                count1 = str(col)
        count1 = str(self.__strip_bytestring(count1))
        if not str(count1) > str("0") :
            my_db.close()
            sleep(0.2)
            isbn_dict = {}
            return isbn_dict
        isbn_dict = {}
        for row in work_book_ids_list:
            book = str(row['calibre_id'])
            book = str(self.__strip_bytestring(book))
            mysql = "SELECT val FROM identifiers WHERE book = [BOOK] AND (type = 'isbn' OR type = 'issn') AND val NOT NULL "
            mysql = mysql.replace("[BOOK]", book, 1)
            my_cursor.execute(mysql)
            tmp_rows = []
            del tmp_rows
            tmp_rows = my_cursor.fetchall()
            if tmp_rows:
                tmp_rows.sort(reverse=True)
                for row in tmp_rows:
                    for col in row:
                        isbn_dict[book] = col
                    break
            else:
                continue
        my_db.close()
        sleep(0.2)
        return isbn_dict
    def _add_owi_identifiers(self,owi_dict,final_list):
        global guidb
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in final_list:
            book = str(row)
            try:
                owi = owi_dict[book]
                mysql = "INSERT OR REPLACE INTO identifiers (id,book,type,val) VALUES (null,?,'oclc-owi', ?)  "
                my_cursor.execute(mysql,(book,owi))
            except:
                continue
        try:
            my_db.commit()
        except:
            pass
        my_db.close()
        sleep(0.2)
    def _import_validation_data_from_qs_service_pack_db(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        gotten_files = choose_qs_service_pack_database(self)
        if not gotten_files:
            info_dialog(self.gui, 'Canceled','No qs_service_pack.db file was selected for importing; Cancelled.').show()
            return
        else:
            am_path = gotten_files[0]
        if not (am_path.count("qs_service_pack.db") > 0 ):
            info_dialog(self.gui, 'Canceled','No Q&S qs_service_pack.db file was selected for importing; Cancelled.').show()
            return
        payload = []
        if question_dialog(self.gui, "QuarantineAndScrub",("Reference Data will be imported from: " + str(am_path) \
                                                                                    + "<br><br>GUI Will Be Locked While Job Runs <br><br>Continue? ")):
            start_threaded_import_from_qs_service_pack(self, self.gui, am_path, Dispatcher(self._import_data_no_restart))
            self.create_ui_toast_dialog_for_jobs()
            if self.my_jobs_dialog_object:
                if self.my_jobs_dialog_object <> "unknown":
                    try:
                        self.my_jobs_dialog_object.show()
                    except:
                        pass
        else:
             info_dialog(self.gui, 'Canceled',
                 'QuarantineAndScrub is Canceled').show()
    def _import_data_no_restart(self, job):
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed to Import Data...'))
            return
        else:
            self.gui.status_bar.show_message(_('Importing of Data Has Completed'), 15000)
    def work_tag_destroy_dialog(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.worktagdestroy_dialog = WorkTagDestroyDialog(self.gui,self.qaction.icon(),guidb,self.execute_work_tag_search_and_destroy)
        self.worktagdestroy_dialog.show()
    def execute_work_tag_search_and_destroy(self,guidb,work_tag_keyword,use_substring_param):
        if not work_tag_keyword > " ":
            return
        if work_tag_keyword == "?":
            return
        self.worktagdestroy_dialog.hide()
        if use_substring_param == "True":
            use_substring = True
        else:
            use_substring = False
        book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list)
        if  n <= 0:
            return error_dialog(self.gui, _('No Books Selected'),
                                                      _('You must select one or more books to perform this action.'), show=True)
        tmp_list = []
        for item in book_ids_list:
            s = str(item['calibre_id'])
            tmp_list.append(s)
        del book_ids_list
        tmp_set = set(tmp_list)
        book_ids = list(tmp_set)
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        my_cursor.execute("BEGIN TRANSACTION")
        mysql = "INSERT or REPLACE INTO custom_column_13 (id,value) SELECT book, tagsall FROM __books_work_populate WHERE tagsall not null  ; "
        my_cursor.execute(mysql)
        sleep(0.1)
        mysql = "UPDATE books_custom_column_13_link  SET value = books_custom_column_13_link.book "
        my_cursor.execute(mysql)
        sleep(0.1)
        my_db.commit()
        sleep(0.1)
        my_cursor.execute("BEGIN TRANSACTION")
        mysql = "DELETE FROM custom_column_13 WHERE id NOT IN (SELECT value FROM books_custom_column_13_link)"
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.1)
        my_cursor.execute("BEGIN TRANSACTION")
        for row in book_ids:
            book = str(row)
            mysql = "SELECT book,tagsall, \
                                (SELECT value FROM books_custom_column_13_link \
                                    WHERE book = __books_work_populate.book AND value NOT NULL) as link_value \
                            FROM  __books_work_populate WHERE tagsall NOT NULL AND book = ?"
            my_cursor.execute(mysql,([book]))
            tmp_rows = my_cursor.fetchall()
            if not tmp_rows:
                continue
            else:
                if len(tmp_rows) == 0:
                    continue
                else:
                    for item in tmp_rows:
                        book,tagsall,link_value = item
                        break
                    orig_tagsall = tagsall
                    if tagsall.count(", ") == 0:
                        tagsall = tagsall + ", DUMMY"
                    new_tagsall = ""
                    s_split = tagsall.split(", ")
                    for  tag in s_split:
                        tag = tag.strip()
                        if not tag > " ":
                            continue
                        if tag == "DUMMY":
                            continue
                        if not use_substring:
                            if tag == work_tag_keyword:
                                continue
                            else:
                                new_tagsall = new_tagsall + ", " + tag
                        else:
                            if not tag.count(work_tag_keyword) > 0:
                                new_tagsall = new_tagsall + ", " + tag
                            else:
                                continue
                    new_tagsall = new_tagsall.replace("DUMMY","",4)
                    new_tagsall = new_tagsall.replace(", ,", ",", 1)
                    new_tagsall = new_tagsall.strip()
                    if new_tagsall.startswith(","):
                        new_tagsall = new_tagsall[1: ]
                        new_tagsall = new_tagsall.strip()
                    if new_tagsall.endswith(","):
                        new_tagsall = new_tagsall[0:-1]
                        new_tagsall = new_tagsall.strip()
                    try:
                        orig_tagsall = unicode(orig_tagsall)
                        new_tagsall = unicode(new_tagsall)
                    except:
                        pass
                    if orig_tagsall == new_tagsall:
                        continue
                    if new_tagsall > " ":
                        mysql = "UPDATE custom_column_13 SET value = ? WHERE id = ? "
                        my_cursor.execute(mysql,(new_tagsall,link_value))
                        sleep(0.02)
                    else:
                        mysql = "DELETE FROM custom_column_13 WHERE id = ? "
                        my_cursor.execute(mysql,([link_value]))
                        sleep(0.02)
                        mysql = "DELETE FROM books_custom_column_13_link WHERE book = ? "
                        my_cursor.execute(mysql,([book]))
                        sleep(0.02)
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        my_db.close()
        self.force_refresh_of_cache(book_ids)
        sleep(0.1)
        self.worktagdestroy_dialog.show()
    def work_tag_add_dialog(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.worktagadd_dialog = WorkTagAddDialog(self.gui,self.qaction.icon(),guidb,self.execute_work_tag_add)
        self.worktagadd_dialog.show()
    def execute_work_tag_add(self,guidb,work_tag_keyword):
        if not (work_tag_keyword > " " and work_tag_keyword <> '?' ):
            return
        self.worktagadd_dialog.hide()
        book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list)
        if  n <= 0:
            return error_dialog(self.gui, _('No Books Selected'),
                                                      _('You must select one or more books to perform this action.'), show=True)
        tmp_list = []
        for item in book_ids_list:
            s = str(item['calibre_id'])
            tmp_list.append(s)
        del book_ids_list
        tmp_set = set(tmp_list)
        book_ids = list(tmp_set)
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        my_cursor.execute("BEGIN TRANSACTION")
        mysql = "INSERT or REPLACE INTO custom_column_13 (id,value) SELECT book, tagsall FROM __books_work_populate WHERE tagsall not null  ; "
        my_cursor.execute(mysql)
        sleep(0.1)
        mysql = "UPDATE books_custom_column_13_link  SET value = books_custom_column_13_link.book "
        my_cursor.execute(mysql)
        sleep(0.1)
        my_db.commit()
        sleep(0.1)
        my_cursor.execute("BEGIN TRANSACTION")
        mysql = "DELETE FROM custom_column_13 WHERE id NOT IN (SELECT value FROM books_custom_column_13_link)"
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.1)
        for row in book_ids:
            book_has_no_tags = False
            book = str(row)
            mysql = "SELECT book,tagsall, \
                                (SELECT value FROM books_custom_column_13_link \
                                    WHERE book = __books_work_populate.book AND value NOT NULL) as link_value \
                            FROM  __books_work_populate WHERE tagsall NOT NULL AND book = ?"
            my_cursor.execute(mysql,([book]))
            tmp_rows = my_cursor.fetchall()
            if not tmp_rows:
                book_has_no_tags = True
                tagsall = work_tag_keyword
                link_value = book
            else:
                if len(tmp_rows) == 0:
                    book_has_no_tags = True
                    tagsall = work_tag_keyword
                    link_value = book
                else:
                    for item in tmp_rows:
                        book,tagsall,link_value = item
                        break
                    tagsall = tagsall + ", " + work_tag_keyword
                    del tmp_rows
            tagsall = self.sort_tags(tagsall)
            if not book_has_no_tags:
                my_cursor.execute("BEGIN TRANSACTION")
                mysql = "UPDATE custom_column_13 SET value = ? WHERE id = ? "
                my_cursor.execute(mysql,(tagsall,link_value))
                sleep(0.02)
            else:
                my_cursor.execute("BEGIN TRANSACTION")
                mysql = "INSERT OR REPLACE INTO custom_column_13 (id,value) VALUES(?,?)"
                my_cursor.execute(mysql,(link_value,tagsall))
                sleep(0.02)
                my_db.commit()
                sleep(0.1)
                my_cursor.execute("BEGIN TRANSACTION")
                mysql = "INSERT OR REPLACE INTO books_custom_column_13_link (id,book,value) VALUES(?,?,?)"
                my_cursor.execute(mysql,(book,book,link_value))
                sleep(0.02)
                my_db.commit()
                sleep(0.1)
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        my_db.close()
        self.force_refresh_of_cache(book_ids)
        sleep(0.1)
        self.worktagadd_dialog.show()
        return
    def sort_tags(self,tagsall):
        tagsall = tagsall.strip()
        if not tagsall.count(", ") > 0:
            return tagsall
        s_list = tagsall.split(", ")
        s_set = set(s_list)
        s_list = list(s_set)
        s_list.sort()
        new_tagsall = ""
        for item in s_list:
            item = item.strip()
            new_tagsall = new_tagsall + ", " + item
        if new_tagsall.startswith(","):
            new_tagsall = new_tagsall[1: ]
            new_tagsall = new_tagsall.strip()
        return new_tagsall
    def work_tag_replace_dialog(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.worktagreplace_dialog = WorkTagReplaceDialog(self.gui,self.qaction.icon(),guidb,self.execute_work_tag_search_and_replace)
        self.worktagreplace_dialog.show()
    def execute_work_tag_search_and_replace(self,guidb,work_tag_keyword,use_substring_param,work_tag_new):
        if not work_tag_keyword > " ":
            return
        if work_tag_keyword == "?":
            return
        if not work_tag_new > " ":
            return
        if work_tag_new == "?":
            return
        self.worktagreplace_dialog.hide()
        if use_substring_param == "True":
            use_substring = True
        else:
            use_substring = False
        if  work_tag_keyword == "*":
            use_wildcard = True
        else:
            use_wildcard = False
        book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list)
        if  n <= 0:
            self.worktagreplace_dialog.show()
            return error_dialog(self.gui, _('No Books Selected'),
                                                      _('You must select one or more books to perform this action.'), show=True)
        tmp_list = []
        for item in book_ids_list:
            s = str(item['calibre_id'])
            tmp_list.append(s)
        del book_ids_list
        tmp_set = set(tmp_list)
        book_ids = list(tmp_set)
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        my_cursor.execute("BEGIN TRANSACTION")
        mysql = "INSERT or REPLACE INTO custom_column_13 (id,value) SELECT book, tagsall FROM __books_work_populate WHERE tagsall not null  ; "
        my_cursor.execute(mysql)
        sleep(0.1)
        mysql = "UPDATE books_custom_column_13_link  SET value = books_custom_column_13_link.book "
        my_cursor.execute(mysql)
        sleep(0.1)
        my_db.commit()
        sleep(0.1)
        my_cursor.execute("BEGIN TRANSACTION")
        mysql = "DELETE FROM custom_column_13 WHERE id NOT IN (SELECT value FROM books_custom_column_13_link)"
        my_cursor.execute(mysql)
        my_db.commit()
        sleep(0.1)
        my_cursor.execute("BEGIN TRANSACTION")
        for row in book_ids:
            book = str(row)
            mysql = "SELECT book,tagsall, \
                                (SELECT value FROM books_custom_column_13_link \
                                    WHERE book = __books_work_populate.book AND value NOT NULL) as link_value \
                            FROM  __books_work_populate WHERE tagsall NOT NULL AND book = ?"
            my_cursor.execute(mysql,([book]))
            tmp_rows = my_cursor.fetchall()
            if not tmp_rows:
                continue
            else:
                if len(tmp_rows) == 0:
                    continue
                else:
                    for item in tmp_rows:
                        book,tagsall,link_value = item
                        break
                    orig_tagsall = tagsall
                    if tagsall.count(", ") == 0:
                        tagsall = tagsall + ", DUMMY"
                    new_tagsall = ""
                    s_split = tagsall.split(", ")
                    for  tag in s_split:
                        tag = tag.strip()
                        if not tag > " ":
                            continue
                        if tag == "DUMMY":
                            continue
                        if not use_substring:
                            if tag == work_tag_keyword:
                                new_tagsall = new_tagsall + ", " + work_tag_new
                            else:
                                new_tagsall = new_tagsall + ", " + tag
                        else:
                            if use_wildcard:
                                new_tagsall = new_tagsall + ", " + work_tag_new
                            else:
                                if not tag.count(work_tag_keyword) > 0:
                                    new_tagsall = new_tagsall + ", " + tag
                                else:
                                    new_tagsall = new_tagsall + ", " + work_tag_new
                    new_tagsall = new_tagsall.replace("DUMMY","",4)
                    new_tagsall = new_tagsall.replace(", ,", ",", 1)
                    new_tagsall = new_tagsall.strip()
                    if new_tagsall.startswith(","):
                        new_tagsall = new_tagsall[1: ]
                        new_tagsall = new_tagsall.strip()
                    if new_tagsall.endswith(","):
                        new_tagsall = new_tagsall[0:-1]
                        new_tagsall = new_tagsall.strip()
                    try:
                        orig_tagsall = unicode(orig_tagsall)
                        new_tagsall = unicode(new_tagsall)
                    except:
                        pass
                    if orig_tagsall == new_tagsall:
                        continue
                    new_tagsall = self.sort_tags(new_tagsall)
                    if new_tagsall > " ":
                        mysql = "UPDATE custom_column_13 SET value = ? WHERE id = ? "
                        my_cursor.execute(mysql,(new_tagsall,link_value))
                        sleep(0.02)
                    else:
                        pass
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        my_db.close()
        self.force_refresh_of_cache(book_ids)
        sleep(0.1)
        self.worktagreplace_dialog.show()
    def work_series_replace_dialog(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.workseriesreplace_dialog = WorkSeriesReplaceDialog(self.gui,self.qaction.icon(),guidb,self.execute_work_series_search_and_replace)
        self.workseriesreplace_dialog.show()
    def execute_work_series_search_and_replace(self,guidb,work_series_keyword,use_substring_param,work_series_new):
        if not work_series_keyword > " ":
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series'), show=True)
        if work_series_keyword == "?":
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series'), show=True)
        if not work_series_new > " ":
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series'), show=True)
        if work_series_new == "?":
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series'), show=True)
        if work_series_keyword.count("[") > 0:
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series, not the Name Plus Series Index (e.g. [2] )'), show=True)
        if work_series_new.count("[") > 0:
            return error_dialog(self.gui, _('No Books Selected'),
                                                      _('The Work Series is the Name of the Series, not the Name Plus Series Index (e.g. [2] )'), show=True)
        self.workseriesreplace_dialog.hide()
        if use_substring_param == "True":
            use_substring = True
        else:
            use_substring = False
        book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list)
        if  n <= 0:
            self.workseriesreplace_dialog.show()
            return error_dialog(self.gui, _('No Books Selected'),
                                                      _('You must select one or more books to perform this action.'), show=True)
        tmp_list = []
        for item in book_ids_list:
            s = str(item['calibre_id'])
            tmp_list.append(s)
        del book_ids_list
        tmp_set = set(tmp_list)
        book_ids = list(tmp_set)
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in book_ids:
            book = str(row)
            mysql = "SELECT book,seriesname, seriesindex, \
                                (SELECT value FROM books_custom_column_10_link \
                                    WHERE book = __books_work_populate.book ) as link_value \
                            FROM  __books_work_populate WHERE seriesname NOT NULL AND book = ?"
            my_cursor.execute(mysql,([book]))
            tmp_rows = my_cursor.fetchall()
            if not tmp_rows:
                continue
            else:
                if len(tmp_rows) == 0:
                    continue
                else:
                    for item in tmp_rows:
                        book,seriesname,seriesindex,link_value = item
                        break
            orig_seriesname = seriesname
            if not use_substring:
                if seriesname == work_series_keyword:
                    new_seriesname = work_series_new
                else:
                    new_seriesname = seriesname
            else:
                if not seriesname.count(work_series_keyword) > 0:
                    new_seriesname = seriesname
                else:
                    new_seriesname =  work_series_new
            new_seriesname = new_seriesname.strip()
            try:
                orig_seriesname = unicode(orig_seriesname)
                new_seriesname = unicode(new_seriesname)
            except:
                pass
            if orig_seriesname == new_seriesname:
                continue
            s_index = str(seriesindex)
            s_index = str(s_index.replace(".00","",1))
            s_index = str(s_index.replace(".0","",1))
            new_full_series = new_seriesname + " [" + s_index + "]"
            if new_seriesname > " ":
                mysql = "INSERT OR REPLACE INTO custom_column_10 (id,value) VALUES(?,?) "
                my_cursor.execute(mysql,(book,new_seriesname))
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM books_custom_column_10_link WHERE book = ?  "
                my_db.execute(mysql,([book]))
                sleep(0.02)
                mysql = "INSERT OR REPLACE INTO books_custom_column_10_link (id,book,value) VALUES(?,?,?) "
                my_cursor.execute(mysql,(book,book,book))
                sleep(0.02)
                mysql = "UPDATE custom_column_15 SET value = ? WHERE book = ? "
                my_cursor.execute(mysql,(new_full_series,book))
                sleep(0.02)
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM custom_column_10 WHERE id NOT IN(SELECT value FROM books_custom_column_10_link)"
                my_db.execute(mysql)
                my_db.commit()
                sleep(0.1)
            else:
                pass
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        my_db.close()
        self.force_refresh_of_cache(book_ids)
        sleep(0.1)
        self.workseriesreplace_dialog.show()
    def work_series_destroy_dialog(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.workseriesdestroy_dialog = WorkSeriesDestroyDialog(self.gui,self.qaction.icon(),guidb,self.execute_work_series_search_and_destroy)
        self.workseriesdestroy_dialog.show()
    def execute_work_series_search_and_destroy(self,guidb,work_series_keyword,use_substring_param):
        if not work_series_keyword > " ":
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series'), show=True)
        if work_series_keyword == "?":
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series'), show=True)
        if work_series_keyword.count("[") > 0:
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series, not the Name Plus Series Index (e.g. [2] )'), show=True)
        self.workseriesdestroy_dialog.hide()
        if use_substring_param == "True":
            use_substring = True
        else:
            use_substring = False
        book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list)
        if  n <= 0:
            self.workseriesdestroy_dialog.show()
            return error_dialog(self.gui, _('No Books Selected'),
                                                      _('You must select one or more books to perform this action.'), show=True)
        tmp_list = []
        for item in book_ids_list:
            s = str(item['calibre_id'])
            tmp_list.append(s)
        del book_ids_list
        tmp_set = set(tmp_list)
        book_ids = list(tmp_set)
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in book_ids:
            book = str(row)
            mysql = "SELECT book,seriesname, \
                                (SELECT value FROM books_custom_column_10_link \
                                    WHERE book = __books_work_populate.book ) as link_value \
                            FROM  __books_work_populate WHERE seriesname NOT NULL AND book = ?"
            my_cursor.execute(mysql,([book]))
            tmp_rows = my_cursor.fetchall()
            if not tmp_rows:
                continue
            else:
                if len(tmp_rows) == 0:
                    continue
                else:
                    for item in tmp_rows:
                        book,seriesname,link_value = item
                        break
            if not use_substring:
                if seriesname == work_series_keyword:
                    new_seriesname = 'DELETE'
                else:
                    new_seriesname = seriesname
            else:
                if not seriesname.count(work_series_keyword) > 0:
                    new_seriesname = seriesname
                else:
                    new_seriesname =  'DELETE'
            if not new_seriesname ==  'DELETE':
                continue
            else:
                mysql = "DELETE FROM books_custom_column_10_link WHERE book = ?  "
                my_db.execute(mysql,([book]))
                sleep(0.02)
                mysql = "DELETE FROM custom_column_15 WHERE book = ?  "
                my_db.execute(mysql,([book]))
                sleep(0.02)
                mysql = "UPDATE custom_column_12 SET value = 0.0 WHERE book = ?  "
                my_db.execute(mysql,([book]))
                sleep(0.02)
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM custom_column_10 WHERE id NOT IN(SELECT value FROM books_custom_column_10_link)"
                my_db.execute(mysql)
                my_db.commit()
                sleep(0.1)
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        my_db.close()
        self.force_refresh_of_cache(book_ids)
        sleep(0.1)
        self.workseriesdestroy_dialog.show()
    def work_series_add_dialog(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.workseriesadd_dialog = WorkSeriesAddDialog(self.gui,self.qaction.icon(),guidb,self.execute_work_series_add)
        self.workseriesadd_dialog.show()
    def execute_work_series_add(self,guidb,work_series_keyword,work_series_index):
        if not work_series_keyword > " ":
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series'), show=True)
        if work_series_keyword == "?":
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series'), show=True)
        if work_series_keyword.count("[") > 0:
            return error_dialog(self.gui, _('Invalid Work Series Specified'),
                                                      _('The Work Series is the Name of the Series, not the Name Plus Series Index (e.g. [2] )'), show=True)
        if work_series_index == 0:
            work_series_index = 1
        self.workseriesadd_dialog.hide()
        book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list)
        if  n <= 0:
            self.workseriesadd_dialog.show()
            return error_dialog(self.gui, _('No Books Selected'),
                                                      _('You must select one or more books to perform this action.'), show=True)
        tmp_list = []
        for item in book_ids_list:
            s = str(item['calibre_id'])
            tmp_list.append(s)
        del book_ids_list
        tmp_set = set(tmp_list)
        book_ids = list(tmp_set)
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in book_ids:
            book = str(row)
            mysql = "SELECT book,seriesname \
                            FROM  __books_work_populate WHERE seriesname NOT NULL AND book = ?"
            my_cursor.execute(mysql,([book]))
            tmp_rows = my_cursor.fetchall()
            if not tmp_rows:
                pass
            else:
                if len(tmp_rows) == 0:
                    pass
                else:
                    continue
            new_seriesname = work_series_keyword
            s_index = str(work_series_index)
            s_index = str(s_index.replace(".0","",1))
            new_full_series = new_seriesname + " [" + s_index + "]"
            if new_seriesname > " ":
                mysql = "INSERT OR REPLACE INTO custom_column_10 (id,value) VALUES(?,?) "
                my_cursor.execute(mysql,(book,new_seriesname))
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM books_custom_column_10_link WHERE book = ?  "
                my_db.execute(mysql,([book]))
                sleep(0.02)
                mysql = "INSERT OR REPLACE INTO books_custom_column_10_link (id,book,value) VALUES(?,?,?) "
                my_cursor.execute(mysql,(book,book,book))
                sleep(0.02)
                mysql = "INSERT OR REPLACE INTO custom_column_12 (id,book,value) VALUES(?,?,?) "
                my_cursor.execute(mysql,(book,book,work_series_index))
                sleep(0.02)
                mysql = "INSERT OR REPLACE INTO custom_column_15 (id,book,value) VALUES(?,?,?) "
                my_cursor.execute(mysql,(book,book,new_full_series))
                sleep(0.02)
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM custom_column_10 WHERE id NOT IN(SELECT value FROM books_custom_column_10_link)"
                my_db.execute(mysql)
                my_db.commit()
                sleep(0.1)
            else:
                pass
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        my_db.close()
        self.force_refresh_of_cache(book_ids)
        sleep(0.1)
        self.workseriesadd_dialog.show()
    def work_title_change_dialog(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.worktitlechange_dialog = WorkTitleChangeDialog(self.gui,self.qaction.icon(),guidb,self.execute_work_title_change)
        self.worktitlechange_dialog.show()
    def execute_work_title_change(self,guidb,work_title_new):
        work_title_new = work_title_new.strip()
        if not work_title_new > " ":
            return error_dialog(self.gui, _('Invalid Work Title Specified'),
                                                      _('Invalid Work Title Specified'), show=True)
        if work_title_new == "?":
            return error_dialog(self.gui, _('Invalid Work Title Specified'),
                                                      _('Invalid Work Title Specified'), show=True)
        self.worktitlechange_dialog.hide()
        book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list)
        if  n <> 1:
            self.worktitlechange_dialog.show()
            return error_dialog(self.gui, _('Exactly One Book Must Be Selected'),
                                                      _('You must select exactly one book to perform this action.'), show=True)
        tmp_list = []
        for item in book_ids_list:
            s = str(item['calibre_id'])
            tmp_list.append(s)
        del book_ids_list
        tmp_set = set(tmp_list)
        book_ids = list(tmp_set)
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in book_ids:
            book = str(row)
            mysql = "SELECT book, booktitle \
                            FROM  __books_work_populate WHERE booktitle NOT NULL AND book = ?"
            my_cursor.execute(mysql,([book]))
            tmp_rows = my_cursor.fetchall()
            if not tmp_rows:
                self.worktitlechange_dialog.show()
                return error_dialog(self.gui, _('No Work Title Exists'),
                                                      _('You Cannot Change What Does Not Exist.'), show=True)
            else:
                if len(tmp_rows) == 0:
                    self.worktitlechange_dialog.show()
                    return error_dialog(self.gui, _('No Work Title Exists'),
                                                          _('You Cannot Change What Does Not Exist.'), show=True)
                else:
                    pass
            if work_title_new > " ":
                mysql = "INSERT OR REPLACE INTO custom_column_8 (id,value) VALUES(?,?) "
                my_cursor.execute(mysql,(book,work_title_new))
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM books_custom_column_8_link WHERE book = ?  "
                my_db.execute(mysql,([book]))
                sleep(0.02)
                mysql = "INSERT OR REPLACE INTO books_custom_column_8_link (id,book,value) VALUES(?,?,?) "
                my_cursor.execute(mysql,(book,book,book))
                sleep(0.02)
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM custom_column_8 WHERE id NOT IN(SELECT value FROM books_custom_column_8_link)"
                my_db.execute(mysql)
                my_db.commit()
                sleep(0.1)
            else:
                pass
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        my_db.close()
        self.force_refresh_of_cache(book_ids)
        sleep(0.1)
        self.worktitlechange_dialog.show()
    def work_author_change_dialog(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.workauthorchange_dialog = WorkAuthorChangeDialog(self.gui,self.qaction.icon(),guidb,self.execute_work_author_change)
        self.workauthorchange_dialog.show()
    def execute_work_author_change(self,guidb,work_author_new):
        work_author_new = work_author_new.strip()
        if not work_author_new > " ":
            return error_dialog(self.gui, _('Invalid Work Author Specified'),
                                                      _('Invalid Work Author Specified'), show=True)
        if work_author_new == "?":
            return error_dialog(self.gui, _('Invalid Work Author Specified'),
                                                      _('Invalid Work Author Specified'), show=True)
        self.workauthorchange_dialog.hide()
        book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list)
        if  n <> 1:
            self.workauthorchange_dialog.show()
            return error_dialog(self.gui, _('Exactly One Book Must Be Selected'),
                                                      _('You must select exactly one book to perform this action.'), show=True)
        tmp_list = []
        for item in book_ids_list:
            s = str(item['calibre_id'])
            tmp_list.append(s)
        del book_ids_list
        tmp_set = set(tmp_list)
        book_ids = list(tmp_set)
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in book_ids:
            book = str(row)
            mysql = "SELECT book, authname \
                            FROM  __books_work_populate \
                            WHERE book = ? \
                                AND book = (SELECT book FROM books_custom_column_18_link \
                                                                          WHERE books_custom_column_18_link.book = \
                                                                                __books_work_populate.book \
                                                                                AND books_custom_column_18_link .value =\
                                                                                (SELECT id FROM custom_column_18 WHERE value = 'book_ok')  ) \
                                AND authname NOT NULL"
            my_cursor.execute(mysql,([book]))
            tmp_rows = my_cursor.fetchall()
            if not tmp_rows:
                self.workauthorchange_dialog.show()
                return error_dialog(self.gui, _('No Green Work Author Exists'),
                                                      _('You Can Only Change Work Authors with a Status of "book_ok".'), show=True)
            else:
                if len(tmp_rows) == 0:
                    self.workauthorchange_dialog.show()
                    return error_dialog(self.gui, _('No Green Work Author Exists'),
                                                      _('You Can Only Change Work Authors with a Status of "book_ok".'), show=True)
                else:
                    pass
            if work_author_new > " ":
                mysql = "INSERT OR REPLACE INTO custom_column_4 (id,value) VALUES(?,?) "
                my_cursor.execute(mysql,(book,work_author_new))
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM books_custom_column_4_link WHERE book = ?  "
                my_db.execute(mysql,([book]))
                sleep(0.02)
                mysql = "INSERT OR REPLACE INTO books_custom_column_4_link (id,book,value) VALUES(?,?,?) "
                my_cursor.execute(mysql,(book,book,book))
                sleep(0.02)
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM custom_column_4 WHERE id NOT IN(SELECT value FROM books_custom_column_4_link)"
                my_db.execute(mysql)
                my_db.commit()
                sleep(0.1)
            else:
                pass
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        my_db.close()
        self.force_refresh_of_cache(book_ids)
        sleep(0.1)
        self.workauthorchange_dialog.show()
    def work_series_index_change_dialog(self):
        global guidb
        global library_is_quarantine_db
        guidb = self.gui.library_view.model().db
        self.ensure_correct_library(guidb)
        if not library_is_quarantine_db:
            self.library_changed(guidb)
            return
        self.workseriesindexchange_dialog = WorkSeriesIndexChangeDialog(self.gui,self.qaction.icon(),guidb,self.execute_work_series_index_change)
        self.workseriesindexchange_dialog.show()
    def execute_work_series_index_change(self,guidb,work_series_index):
        if work_series_index == 0:
            work_series_index = 1
        self.workseriesindexchange_dialog.hide()
        book_ids_list = map( partial(self._convert_id_to_book), self.gui.library_view.get_selected_ids() )
        n = len(book_ids_list)
        if  n <> 1:
            self.workseriesindexchange_dialog.show()
            return error_dialog(self.gui, _('Single Book Was Not Selected'),
                                                      _('You must select exactly one book to perform this action.'), show=True)
        tmp_list = []
        for item in book_ids_list:
            s = str(item['calibre_id'])
            tmp_list.append(s)
        del book_ids_list
        tmp_set = set(tmp_list)
        book_ids = list(tmp_set)
        my_db = guidb
        path = my_db.library_path
        if isbytestring(path):
            path = path.decode(filesystem_encoding)
        path = path.replace(os.sep, '/')
        path = os.path.join(path, 'metadata.db')
        path = path.replace(os.sep, '/')
        try:
            my_db = sqlite3.connect(path)
        except Exception as e:
            raise e
            return
        my_cursor = my_db.cursor()
        mysql = "PRAGMA main.busy_timeout = 5000;"
        my_db.execute(mysql)
        for row in book_ids:
            book = str(row)
            mysql = "SELECT book,seriesname \
                            FROM  __books_work_populate WHERE seriesname NOT NULL AND book = ?"
            my_cursor.execute(mysql,([book]))
            tmp_rows = my_cursor.fetchall()
            if not tmp_rows:
                continue
            else:
                if len(tmp_rows) == 0:
                    continue
                else:
                    for row in tmp_rows:
                        book,seriesname = row
            new_seriesname = seriesname
            s_index = str(work_series_index)
            s_index = str(s_index.replace(".0","",1))
            new_full_series = new_seriesname + " [" + s_index + "]"
            if new_seriesname > " ":
                mysql = "INSERT OR REPLACE INTO custom_column_10 (id,value) VALUES(?,?) "
                my_cursor.execute(mysql,(book,new_seriesname))
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM books_custom_column_10_link WHERE book = ?  "
                my_db.execute(mysql,([book]))
                sleep(0.02)
                mysql = "INSERT OR REPLACE INTO books_custom_column_10_link (id,book,value) VALUES(?,?,?) "
                my_cursor.execute(mysql,(book,book,book))
                sleep(0.02)
                mysql = "INSERT OR REPLACE INTO custom_column_12 (id,book,value) VALUES(?,?,?) "
                my_cursor.execute(mysql,(book,book,work_series_index))
                sleep(0.02)
                mysql = "INSERT OR REPLACE INTO custom_column_15 (id,book,value) VALUES(?,?,?) "
                my_cursor.execute(mysql,(book,book,new_full_series))
                sleep(0.02)
                my_db.commit()
                sleep(0.1)
                mysql = "DELETE FROM custom_column_10 WHERE id NOT IN(SELECT value FROM books_custom_column_10_link)"
                my_db.execute(mysql)
                my_db.commit()
                sleep(0.1)
            else:
                pass
        try:
            my_db.commit()
            sleep(0.1)
        except:
            pass
        my_db.close()
        self.force_refresh_of_cache(book_ids)
        sleep(0.1)
        self.workseriesindexchange_dialog.show()
    def create_ui_toast_dialog(self,msg_num):
        self.ui_toast_dialog = UIToastDialog(self.gui,self.qaction.icon(),msg_num)
        self.ui_toast_dialog.show()
        self.ui_toast_dialog.setModal(True)
        self.ui_toast_dialog.update()
        self.ui_toast_dialog.repaint()
    def create_ui_toast_dialog_for_jobs(self):
        self.create_ui_toast_dialog(3)
        sleep(1.00)
        self.ui_toast_dialog.close()
    def create_ui_toast_dialog_for_main_icon(self):
        self.create_ui_toast_dialog(4)
        sleep(1.25)
        self.ui_toast_dialog.close()
        return
