From 1be2e50beb2f889e828fa5696d5bb43200f1ca2e Mon Sep 17 00:00:00 2001 From: Jim Miller Date: Wed, 1 Aug 2012 21:54:49 -0500 Subject: [PATCH] Add 'newonly' feature for standard and custom columns in plugin. --- calibre-plugin/__init__.py | 2 +- calibre-plugin/config.py | 114 +++++++++++++++++++++++++++++----- calibre-plugin/dialogs.py | 13 ++-- calibre-plugin/ffdl_plugin.py | 71 ++++++++++----------- 4 files changed, 136 insertions(+), 64 deletions(-) diff --git a/calibre-plugin/__init__.py b/calibre-plugin/__init__.py index 4b4b0d27..43020b42 100644 --- a/calibre-plugin/__init__.py +++ b/calibre-plugin/__init__.py @@ -27,7 +27,7 @@ class FanFictionDownLoaderBase(InterfaceActionBase): description = 'UI plugin to download FanFiction stories from various sites.' supported_platforms = ['windows', 'osx', 'linux'] author = 'Jim Miller' - version = (1, 6, 0) + version = (1, 6, 1) minimum_calibre_version = (0, 8, 57) #: This field defines the GUI plugin class that contains all the code diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py index 7d6105b6..5e5a9d16 100644 --- a/calibre-plugin/config.py +++ b/calibre-plugin/config.py @@ -8,6 +8,7 @@ __copyright__ = '2012, Jim Miller' __docformat__ = 'restructuredtext en' import traceback, copy +from collections import OrderedDict from PyQt4.Qt import (QDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QFont, QWidget, QTextEdit, QComboBox, QCheckBox, QPushButton, QTabWidget, QVariant, QScrollArea) @@ -62,6 +63,9 @@ default_prefs['countpagesstats'] = [] default_prefs['errorcol'] = '' default_prefs['custom_cols'] = {} +default_prefs['custom_cols_newonly'] = {} + +default_prefs['std_cols_newonly'] = {} def set_library_config(library_config): get_gui().current_db.prefs.set_namespaced(PREFS_NAMESPACE, @@ -75,14 +79,14 @@ def get_library_config(): # Check whether this is a configuration needing to be migrated # from json into database. If so: get it, set it, rename it in json. if library_id in old_prefs: - print("get prefs from old_prefs") + #print("get prefs from old_prefs") library_config = old_prefs[library_id] set_library_config(library_config) old_prefs["migrated to library db %s"%library_id] = old_prefs[library_id] del old_prefs[library_id] if library_config is None: - print("get prefs from db") + #print("get prefs from db") library_config = db.prefs.get_namespaced(PREFS_NAMESPACE, PREFS_KEY_SETTINGS, copy.deepcopy(default_prefs)) return library_config @@ -106,7 +110,7 @@ class PrefsFacade(): def _get_prefs(self): libraryid = get_library_uuid(get_gui().current_db) if self.current_prefs == None or self.libraryid != libraryid: - print("self.current_prefs == None(%s) or self.libraryid != libraryid(%s)"%(self.current_prefs == None,self.libraryid != libraryid)) + #print("self.current_prefs == None(%s) or self.libraryid != libraryid(%s)"%(self.current_prefs == None,self.libraryid != libraryid)) self.libraryid = libraryid self.current_prefs = get_library_config() return self.current_prefs @@ -172,8 +176,11 @@ class ConfigWidget(QWidget): if 'Count Pages' not in plugin_action.gui.iactions: self.countpages_tab.setEnabled(False) - self.columns_tab = CustomColumnsTab(self, plugin_action) - tab_widget.addTab(self.columns_tab, 'Custom Columns') + self.std_columns_tab = StandardColumnsTab(self, plugin_action) + tab_widget.addTab(self.std_columns_tab, 'Standard Columns') + + self.cust_columns_tab = CustomColumnsTab(self, plugin_action) + tab_widget.addTab(self.cust_columns_tab, 'Custom Columns') self.other_tab = OtherTab(self, plugin_action) tab_widget.addTab(self.other_tab, 'Other') @@ -241,19 +248,30 @@ class ConfigWidget(QWidget): prefs['countpagesstats'] = countpagesstats + # Standard Columns tab + colsnewonly = {} + for (col,checkbox) in self.std_columns_tab.stdcol_newonlycheck.iteritems(): + colsnewonly[col] = checkbox.isChecked() + prefs['std_cols_newonly'] = colsnewonly + # Custom Columns tab # error column - prefs['errorcol'] = unicode(self.columns_tab.errorcol.itemData(self.columns_tab.errorcol.currentIndex()).toString()) + prefs['errorcol'] = unicode(self.cust_columns_tab.errorcol.itemData(self.cust_columns_tab.errorcol.currentIndex()).toString()) # cust cols colsmap = {} - for (col,combo) in self.columns_tab.custcol_dropdowns.iteritems(): + for (col,combo) in self.cust_columns_tab.custcol_dropdowns.iteritems(): val = unicode(combo.itemData(combo.currentIndex()).toString()) if val != 'none': colsmap[col] = val #print("colsmap[%s]:%s"%(col,colsmap[col])) prefs['custom_cols'] = colsmap + colsnewonly = {} + for (col,checkbox) in self.cust_columns_tab.custcol_newonlycheck.iteritems(): + colsnewonly[col] = checkbox.isChecked() + prefs['custom_cols_newonly'] = colsnewonly + prefs.save_to_db() def edit_shortcuts(self): @@ -313,7 +331,7 @@ class BasicTab(QWidget): self.l.addLayout(horz) self.updatemeta = QCheckBox('Default Update Calibre &Metadata?',self) - self.updatemeta.setToolTip("On each download, FFDL offers an option to update Calibre's metadata (title, author, URL, tags, custom columns, etc) from the web site.
This sets whether that will default to on or off.") + self.updatemeta.setToolTip("On each download, FFDL offers an option to update Calibre's metadata (title, author, URL, tags, custom columns, etc) from the web site.
This sets whether that will default to on or off.
Columns set to 'New Only' in the column tabs will only be set for new books.") self.updatemeta.setChecked(prefs['updatemeta']) self.l.addWidget(self.updatemeta) @@ -322,16 +340,25 @@ class BasicTab(QWidget): self.updateepubcover.setChecked(prefs['updateepubcover']) self.l.addWidget(self.updateepubcover) + self.l.addSpacing(10) + + self.deleteotherforms = QCheckBox('Delete other existing formats?',self) + self.deleteotherforms.setToolTip('Check this to automatically delete all other ebook formats when updating an existing book.\nHandy if you have both a Nook(epub) and Kindle(mobi), for example.') + self.deleteotherforms.setChecked(prefs['deleteotherforms']) + self.l.addWidget(self.deleteotherforms) + self.updatecover = QCheckBox('Update Calibre Cover when Updating Metadata?',self) self.updatecover.setToolTip("Update calibre book cover image from EPUB when metadata is updated. (EPUB only.)\nDoesn't go looking for new images on 'Update Calibre Metadata Only'.") self.updatecover.setChecked(prefs['updatecover']) self.l.addWidget(self.updatecover) self.keeptags = QCheckBox('Keep Existing Tags when Updating Metadata?',self) - self.keeptags.setToolTip('Existing tags will be kept and any new tags added.\nCompleted and In-Progress tags will be still be updated, if known.\nLast Updated tags will be updated if lastupdate in include_subject_tags.') + self.keeptags.setToolTip("Existing tags will be kept and any new tags added.\nCompleted and In-Progress tags will be still be updated, if known.\nLast Updated tags will be updated if lastupdate in include_subject_tags.\n(If Tags is set to 'New Only' in the Standard Columns tab, this has no effect.)") self.keeptags.setChecked(prefs['keeptags']) self.l.addWidget(self.keeptags) + self.l.addSpacing(10) + self.urlsfromclip = QCheckBox('Take URLs from Clipboard?',self) self.urlsfromclip.setToolTip('Prefill URLs from valid URLs in Clipboard when Adding New.') self.urlsfromclip.setChecked(prefs['urlsfromclip']) @@ -343,16 +370,13 @@ class BasicTab(QWidget): self.updatedefault.setChecked(prefs['updatedefault']) self.l.addWidget(self.updatedefault) - self.deleteotherforms = QCheckBox('Delete other existing formats?',self) - self.deleteotherforms.setToolTip('Check this to automatically delete all other ebook formats when updating an existing book.\nHandy if you have both a Nook(epub) and Kindle(mobi), for example.') - self.deleteotherforms.setChecked(prefs['deleteotherforms']) - self.l.addWidget(self.deleteotherforms) - self.adddialogstaysontop = QCheckBox("Keep 'Add New from URL(s)' dialog on top?",self) self.adddialogstaysontop.setToolTip("Instructs the OS and Window Manager to keep the 'Add New from URL(s)'\ndialog on top of all other windows. Useful for dragging URLs onto it.") self.adddialogstaysontop.setChecked(prefs['adddialogstaysontop']) self.l.addWidget(self.adddialogstaysontop) + self.l.addSpacing(10) + # this is a cheat to make it easier for users to realize there's a new include_images features. self.includeimages = QCheckBox("Include images in EPUBs?",self) self.includeimages.setToolTip("Download and include images in EPUB stories. This is equivalent to adding:\n\n[epub]\ninclude_images:true\nkeep_summary_html:true\nmake_firstimage_cover:true\n\n ...to the top of personal.ini. Your settings in personal.ini will override this.") @@ -767,6 +791,7 @@ class CustomColumnsTab(QWidget): self.l.addSpacing(5) self.custcol_dropdowns = {} + self.custcol_newonlycheck = {} for key, column in custom_columns.iteritems(): @@ -775,8 +800,8 @@ class CustomColumnsTab(QWidget): # for (k,v) in column.iteritems(): # print("column['%s'] => %s"%(k,v)) horz = QHBoxLayout() - label = QLabel('%s(%s)'%(column['name'],key)) - label.setToolTip("Update this %s column with..."%column['datatype']) + label = QLabel(column['name']) + label.setToolTip("Update this %s column(%s) with..."%(key,column['datatype'])) horz.addWidget(label) dropdown = QComboBox(self) dropdown.addItem('',QVariant('none')) @@ -789,8 +814,15 @@ class CustomColumnsTab(QWidget): dropdown.setToolTip("Metadata values valid for this type of column.\nValues that aren't valid for this enumeration column will be ignored.") else: dropdown.setToolTip("Metadata values valid for this type of column.") - horz.addWidget(dropdown) + + newonlycheck = QCheckBox("New Only",self) + newonlycheck.setToolTip("Write to %s(%s) only for new\nbooks, not updates to existing books."%(column['name'],key)) + self.custcol_newonlycheck[key] = newonlycheck + if key in prefs['custom_cols_newonly']: + newonlycheck.setChecked(prefs['custom_cols_newonly'][key]) + horz.addWidget(newonlycheck) + self.l.addLayout(horz) self.l.insertStretch(-1) @@ -818,3 +850,51 @@ class CustomColumnsTab(QWidget): #print("prefs['custom_cols'] %s"%prefs['custom_cols']) + + +class StandardColumnsTab(QWidget): + + def __init__(self, parent_dialog, plugin_action): + self.parent_dialog = parent_dialog + self.plugin_action = plugin_action + QWidget.__init__(self) + + columns=OrderedDict() + + columns["title"]="Title" + columns["authors"]="Author(s)" + columns["publisher"]="Publisher" + columns["tags"]="Tags" + columns["languages"]="Languages" + columns["pubdate"]="Published Date" + columns["timestamp"]="Date" + columns["comments"]="Comments" + columns["series"]="Series" + columns["identifiers"]="Ids(url id only)" + + self.l = QVBoxLayout() + self.setLayout(self.l) + + label = QLabel("The standard calibre metadata columns are listed below. You may choose whether FFDL will fill each column automatically on updates or only for new books.") + label.setWordWrap(True) + self.l.addWidget(label) + self.l.addSpacing(5) + + self.stdcol_newonlycheck = {} + + for key, column in columns.iteritems(): + horz = QHBoxLayout() + label = QLabel(column) + #label.setToolTip("Update this %s column(%s) with..."%(key,column['datatype'])) + horz.addWidget(label) + + newonlycheck = QCheckBox("New Only",self) + newonlycheck.setToolTip("Write to %s only for new\nbooks, not updates to existing books."%column) + self.stdcol_newonlycheck[key] = newonlycheck + if key in prefs['std_cols_newonly']: + newonlycheck.setChecked(prefs['std_cols_newonly'][key]) + horz.addWidget(newonlycheck) + + self.l.addLayout(horz) + + self.l.insertStretch(-1) diff --git a/calibre-plugin/dialogs.py b/calibre-plugin/dialogs.py index 178dc945..86742304 100644 --- a/calibre-plugin/dialogs.py +++ b/calibre-plugin/dialogs.py @@ -106,26 +106,21 @@ class AddNewDialog(SizePersistedDialog): horz = QHBoxLayout() label = QLabel('If Story Already Exists?') - label.setToolTip("What to do if there's already an existing story with the same title and author.") horz.addWidget(label) self.collision = QComboBox(self) + self.collision.setToolTip("What to do if there's already an existing story with the same URL or title and author.") # add collision options self.set_collisions() i = self.collision.findText(prefs['collision']) if i > -1: self.collision.setCurrentIndex(i) - # self.collision.setToolTip(OVERWRITE+' will replace the existing story.\n'+ - # UPDATE+' will download new chapters only and add to existing EPUB.\n'+ - # ADDNEW+' will create a new story with the same title and author.\n'+ - # SKIP+' will not download existing stories.\n'+ - # CALIBREONLY+' will not download stories, but will update Calibre metadata.') label.setBuddy(self.collision) horz.addWidget(self.collision) self.l.addLayout(horz) horz = QHBoxLayout() self.updatemeta = QCheckBox('Update Calibre &Metadata?',self) - self.updatemeta.setToolTip('Update metadata for story in Calibre from web site?') + self.updatemeta.setToolTip("Update metadata for existing stories in Calibre from web site?\n(Columns set to 'New Only' in the column tabs will only be set for new books.)") self.updatemeta.setChecked(prefs['updatemeta']) horz.addWidget(self.updatemeta) @@ -431,9 +426,9 @@ class UpdateExistingDialog(SizePersistedDialog): options_layout.addWidget(self.fileform) label = QLabel('Update Mode:') - label.setToolTip("What sort of update to perform. May set default from plugin configuration.") options_layout.addWidget(label) self.collision = QComboBox(self) + self.collision.setToolTip("What sort of update to perform. May set default from plugin configuration.") # add collision options self.set_collisions() i = self.collision.findText(prefs['collision']) @@ -444,7 +439,7 @@ class UpdateExistingDialog(SizePersistedDialog): options_layout.addWidget(self.collision) self.updatemeta = QCheckBox('Update Calibre &Metadata?',self) - self.updatemeta.setToolTip('Update metadata for story in Calibre from web site? May set default from plugin configuration.') + self.updatemeta.setToolTip("Update metadata for existing stories in Calibre from web site?\n(Columns set to 'New Only' in the column tabs will only be set for new books.)") self.updatemeta.setChecked(prefs['updatemeta']) options_layout.addWidget(self.updatemeta) diff --git a/calibre-plugin/ffdl_plugin.py b/calibre-plugin/ffdl_plugin.py index 69c0b637..f02aab9e 100644 --- a/calibre-plugin/ffdl_plugin.py +++ b/calibre-plugin/ffdl_plugin.py @@ -116,9 +116,8 @@ class FanFictionDownLoaderPlugin(InterfaceAction): # items to prevent GC removing it. self.menu_actions = [] self.qaction.setMenu(self.menu) - self.menu.aboutToShow.connect(self.about_to_show_menu) - self.menus_lock = threading.RLock() + self.menu.aboutToShow.connect(self.about_to_show_menu) def initialization_complete(self): # otherwise configured hot keys won't work until the menu's @@ -134,10 +133,6 @@ class FanFictionDownLoaderPlugin(InterfaceAction): def rebuild_menus(self): with self.menus_lock: - # Show the config dialog - # The config dialog can also be shown from within - # Preferences->Plugins, which is why the do_user_config - # method is defined on the base plugin class do_user_config = self.interface_action_base_plugin.do_user_config self.menu.clear() self.actions_unique_map = {} @@ -179,15 +174,6 @@ class FanFictionDownLoaderPlugin(InterfaceAction): shortcut_name=rmmenutxt, triggered=partial(self.update_lists,add=False)) - # try: - # self.add_send_action.setEnabled( len(self.gui.library_view.get_selected_ids()) > 0 ) - # except: - # pass - # try: - # self.add_remove_action.setEnabled( len(self.gui.library_view.get_selected_ids()) > 0 ) - # except: - # pass - self.menu.addSeparator() self.get_list_action = self.create_menu_item_ex(self.menu, 'Get URLs from Selected Books', image='bookmarks.png', unique_name='Get URLs from Selected Books', @@ -203,13 +189,11 @@ class FanFictionDownLoaderPlugin(InterfaceAction): self.config_action = create_menu_action_unique(self, self.menu, '&Configure Plugin', shortcut=False, image= 'config.png', unique_name='Configure FanFictionDownLoader', - shortcut_name='Configure FanFictionDownLoader', triggered=partial(do_user_config,parent=self.gui)) - self.about_action = create_menu_action_unique(self, self.menu, '&About Plugin', shortcut=False, + self.about_action = create_menu_action_unique(self, self.menu, 'About Plugin', shortcut=False, image= 'images/icon.png', unique_name='About FanFictionDownLoader', - shortcut_name='About FanFictionDownLoader', triggered=self.about) # Before we finalize, make sure we delete any actions for menus that are no longer displayed @@ -551,7 +535,7 @@ make_firstimage_cover:true raise NotGoingToDownload("Skipping duplicate story.","list_remove.png") if len(identicalbooks) > 1: - raise NotGoingToDownload("More than one identical book--can't tell which to update/overwrite.","minusminus.png") + raise NotGoingToDownload("More than one identical book by Identifer URL or title/author(s)--can't tell which book to update/overwrite.","minusminus.png") ## changed: add new book when CALIBREONLY if none found. if collision == CALIBREONLY and not identicalbooks: @@ -708,7 +692,7 @@ make_firstimage_cover:true self._add_or_update_book(book,options,prefs,mi) if options['collision'] == CALIBREONLY or \ - (options['updatemeta'] and book['good']): + ( (options['updatemeta'] or book['added']) and book['good'] ): self._update_metadata(db, book['calibre_id'], book, mi, options) def _update_bad_book(self,book,db=None,label='errorcol', @@ -770,22 +754,6 @@ make_firstimage_cover:true self.previous = self.gui.library_view.currentIndex() db = self.gui.current_db - # if display_story_list(self.gui, - # 'Downloads finished, confirm to update Calibre', - # prefs, - # self.qaction.icon(), - # job.result, - # label_text='Stories will not be added or updated in Calibre without confirmation.', - # offer_skip=True): - - # payload = (job.statistics_cols_map, book_statistics_map) - # all_ids = set(book_statistics_map.keys()) - # msg = '

Count Pages plugin found %d statistics(s). ' % len(all_ids) + \ - # 'Proceed with updating columns in your library?' - # self.gui.proceed_question(self._update_database_columns, - # payload, job.details, - # 'Count log', 'Count complete', msg, - # show_copy_button=False) book_list = job.result good_list = filter(lambda x : x['good'], book_list) @@ -890,6 +858,7 @@ make_firstimage_cover:true return book_id def _update_metadata(self, db, book_id, book, mi, options): + oldmi = db.get_metadata(book_id,index_is_id=True) if prefs['keeptags']: old_tags = db.get_tags(book_id) # remove old Completed/In-Progress only if there's a new one. @@ -905,7 +874,6 @@ make_firstimage_cover:true mi.languages=[book['all_metadata']['langcode']] else: # Set language english, but only if not already set. - oldmi = db.get_metadata(book_id,index_is_id=True) if not oldmi.languages: mi.languages=['eng'] @@ -922,6 +890,32 @@ make_firstimage_cover:true autid=db.get_author_id(auth) db.set_link_field_for_author(autid, unicode(authurls[i]), commit=False, notify=False) + + # mi.title = oldmi.title + # mi.authors = oldmi.authors + # mi.publisher = oldmi.publisher + # mi.tags = oldmi.tags + # mi.languages = oldmi.languages + # mi.pubdate = oldmi.pubdate + # mi.timestamp = oldmi.timestamp + # mi.comments = oldmi.comments + # mi.series = oldmi.series + + # mi.set_identifiers(oldmi.get_identifiers()) + + # implement 'newonly' flags here by setting to the current + # value again. + if not book['added']: + for (col,newonly) in prefs['std_cols_newonly'].iteritems(): + if newonly: + if col == "identifiers": + mi.set_identifiers(oldmi.get_identifiers()) + else: + try: + mi.__setattr__(col,oldmi.__getattribute__(col)) + except AttributeError: + print("AttributeError? %s"%col) + pass db.set_metadata(book_id,mi) @@ -936,6 +930,9 @@ make_firstimage_cover:true print("%s not an existing column, skipping."%col) continue coldef = custom_columns[col] + if col in prefs['custom_cols_newonly'] and prefs['custom_cols_newonly'][col] and not book['added']: + print("Skipping custom column(%s) update, set to New Books Only"%coldef['name']) + continue if not meta.startswith('status-') and meta not in book['all_metadata'] or \ meta.startswith('status-') and 'status' not in book['all_metadata']: print("No value for %s, skipping custom column(%s) update."%(meta,coldef['name']))