diff --git a/calibre-plugin/about.txt b/calibre-plugin/about.txt index 3ae79b09..b63c1acf 100644 --- a/calibre-plugin/about.txt +++ b/calibre-plugin/about.txt @@ -1,4 +1,3 @@ -

FanFictionDownLoader Plugin


Created by Jim Miller, borrowing heavily from Grant Drake's @@ -12,10 +11,10 @@ Calibre officially distributes plugins from the mobileread.com forum site. The official distro channel for this plugin is there: FanFictionDownLoader

-

However, I monitor the +

I also monitor the general users -group for the downloader more closely. That also covers the web application and CLI. +group for the downloader. That covers the web application and CLI, too.

-The source project for this plugin is -also available. +The source for this plugin is available +here. diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py index 4ddbfb8b..a4731be9 100644 --- a/calibre-plugin/config.py +++ b/calibre-plugin/config.py @@ -10,7 +10,7 @@ __docformat__ = 'restructuredtext en' import traceback, copy from PyQt4.Qt import (QDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, - QTextEdit, QComboBox, QCheckBox, QPushButton) + QTextEdit, QComboBox, QCheckBox, QPushButton, QTabWidget) from calibre.gui2 import dynamic, info_dialog from calibre.utils.config import JSONConfig @@ -21,7 +21,9 @@ from calibre_plugins.fanfictiondownloader_plugin.dialogs \ CALIBREONLY,collision_order) from calibre_plugins.fanfictiondownloader_plugin.common_utils \ - import ( get_library_uuid ) + import ( get_library_uuid, KeyboardConfigDialog ) + +from calibre.gui2.complete import MultiCompleteLineEdit # This is where all preferences for this plugin will be stored # Remember that this name (i.e. plugins/fanfictiondownloader_plugin) is also @@ -32,7 +34,7 @@ all_prefs = JSONConfig('plugins/fanfictiondownloader_plugin') # Set defaults used by all. Library specific settings continue to # take from here. -all_prefs.defaults['personal.ini'] = get_resources('example.ini') +all_prefs.defaults['personal.ini'] = get_resources('plugin-example.ini') all_prefs.defaults['updatemeta'] = True all_prefs.defaults['keeptags'] = False all_prefs.defaults['urlsfromclip'] = True @@ -40,7 +42,11 @@ all_prefs.defaults['updatedefault'] = True all_prefs.defaults['fileform'] = 'epub' all_prefs.defaults['collision'] = OVERWRITE all_prefs.defaults['deleteotherforms'] = False +all_prefs.defaults['send_lists'] = '' +all_prefs.defaults['read_lists'] = '' all_prefs.defaults['addtolists'] = False +all_prefs.defaults['addtoreadlists'] = False +all_prefs.defaults['addtolistsonread'] = False # The list of settings to copy from all_prefs or the previous library # when config is called for the first time on a library. @@ -52,11 +58,13 @@ copylist = ['personal.ini', 'fileform', 'collision', 'deleteotherforms', - 'addtolists'] + 'addtolists', + 'addtoreadlists', + 'addtolistsonread'] -## fake out so I don't have to change the prefs calls anywhere. The -## Java programmer in me is offended by op-overloading, but it's very -## tidy. +# fake out so I don't have to change the prefs calls anywhere. The +# Java programmer in me is offended by op-overloading, but it's very +# tidy. class PrefsFacade(): def __init__(self,all_prefs): self.all_prefs = all_prefs @@ -75,18 +83,30 @@ class PrefsFacade(): self.lastlibid = libraryid return self.all_prefs[libraryid] + + def _save_prefs(self,prefs): + libraryid = get_library_uuid(get_gui().current_db) + self.all_prefs[libraryid] = prefs def __getitem__(self,k): prefs = self._get_prefs() if k not in prefs: - ## pulls from all_prefs.defaults automatically if not set - ## in all_prefs + # pulls from all_prefs.defaults automatically if not set + # in all_prefs return self.all_prefs[k] return prefs[k] def __setitem__(self,k,v): prefs = self._get_prefs() prefs[k]=v + self._save_prefs(prefs) + + # to be avoided--can cause unexpected results as possibly ancient + # all_pref settings may be pulled. + def __delitem__(self,k): + prefs = self._get_prefs() + del prefs[k] + self._save_prefs(prefs) prefs = PrefsFacade(all_prefs) @@ -99,6 +119,71 @@ class ConfigWidget(QWidget): self.l = QVBoxLayout() self.setLayout(self.l) + tab_widget = QTabWidget(self) + self.l.addWidget(tab_widget) + + self.basic_tab = BasicTab(self, plugin_action) + tab_widget.addTab(self.basic_tab, 'Basic') + + self.personalini_tab = PersonalIniTab(self, plugin_action) + tab_widget.addTab(self.personalini_tab, 'personal.ini') + + if 'Reading List' in plugin_action.gui.iactions: + self.list_tab = ListTab(self, plugin_action) + tab_widget.addTab(self.list_tab, 'Reading Lists') + else: + self.list_tab = None + + self.other_tab = OtherTab(self, plugin_action) + tab_widget.addTab(self.other_tab, 'Other') + + def save_settings(self): + + # basic + prefs['fileform'] = unicode(self.basic_tab.fileform.currentText()) + prefs['collision'] = unicode(self.basic_tab.collision.currentText()) + prefs['updatemeta'] = self.basic_tab.updatemeta.isChecked() + prefs['keeptags'] = self.basic_tab.keeptags.isChecked() + prefs['urlsfromclip'] = self.basic_tab.urlsfromclip.isChecked() + prefs['updatedefault'] = self.basic_tab.updatedefault.isChecked() + prefs['deleteotherforms'] = self.basic_tab.deleteotherforms.isChecked() + + if self.list_tab: + # lists + prefs['send_lists'] = ', '.join(map( lambda x : x.strip(), filter( lambda x : x.strip() != '', unicode(self.list_tab.send_lists_box.text()).split(',')))) + prefs['read_lists'] = ', '.join(map( lambda x : x.strip(), filter( lambda x : x.strip() != '', unicode(self.list_tab.read_lists_box.text()).split(',')))) + # print("send_lists: %s"%prefs['send_lists']) + # print("read_lists: %s"%prefs['read_lists']) + prefs['addtolists'] = self.list_tab.addtolists.isChecked() + prefs['addtoreadlists'] = self.list_tab.addtoreadlists.isChecked() + prefs['addtolistsonread'] = self.list_tab.addtolistsonread.isChecked() + + # personal.ini + ini = unicode(self.personalini_tab.ini.toPlainText()) + if ini: + prefs['personal.ini'] = ini + else: + # if they've removed everything, reset to default. + prefs['personal.ini'] = get_resources('plugin-example.ini') + + def edit_shortcuts(self): + self.save_settings() + # Force the menus to be rebuilt immediately, so we have all our actions registered + self.plugin_action.rebuild_menus() + d = KeyboardConfigDialog(self.plugin_action.gui, self.plugin_action.action_spec[0]) + if d.exec_() == d.Accepted: + self.plugin_action.gui.keyboard.finalize() + +class BasicTab(QWidget): + + def __init__(self, parent_dialog, plugin_action): + self.parent_dialog = parent_dialog + self.plugin_action = plugin_action + QWidget.__init__(self) + + self.l = QVBoxLayout() + self.setLayout(self.l) + horz = QHBoxLayout() label = QLabel('Default Output &Format:') horz.addWidget(label) @@ -140,7 +225,7 @@ class ConfigWidget(QWidget): self.l.addWidget(self.keeptags) self.urlsfromclip = QCheckBox('Take URLs from Clipboard?',self) - self.urlsfromclip.setToolTip('Prefill URLs from valid URLs in Clipboard when Adding New?') + self.urlsfromclip.setToolTip('Prefill URLs from valid URLs in Clipboard when Adding New.') self.urlsfromclip.setChecked(prefs['urlsfromclip']) self.l.addWidget(self.urlsfromclip) @@ -154,20 +239,33 @@ class ConfigWidget(QWidget): 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) - - try: - ## XXX hide when Reading List not installed? - rl_plugin = plugin_action.gui.iactions['Reading List'] - print("Reading Lists:%s"%rl_plugin.get_list_names()) - - self.addtolists = QCheckBox('Add new/updated stories to Reading List(s)?',self) - self.addtolists.setToolTip('Check this to automatically add new/updated stories to list in the Reading List plugin.') - self.addtolists.setChecked(prefs['addtolists']) - self.l.addWidget(self.addtolists) - except Exception as e: - print("no Reading List available:%s"%unicode(e)) - traceback.print_exc() + self.l.insertStretch(-1) + + def set_collisions(self): + prev=self.collision.currentText() + self.collision.clear() + for o in collision_order: + if self.fileform.currentText() == 'epub' or o not in [UPDATE,UPDATEALWAYS]: + self.collision.addItem(o) + i = self.collision.findText(prev) + if i > -1: + self.collision.setCurrentIndex(i) + + def show_defaults(self): + text = get_resources('plugin-defaults.ini') + ShowDefaultsIniDialog(self.windowIcon(),text,self).exec_() + +class PersonalIniTab(QWidget): + + def __init__(self, parent_dialog, plugin_action): + self.parent_dialog = parent_dialog + self.plugin_action = plugin_action + QWidget.__init__(self) + + self.l = QVBoxLayout() + self.setLayout(self.l) + self.label = QLabel('personal.ini:') self.l.addWidget(self.label) @@ -181,53 +279,13 @@ class ConfigWidget(QWidget): self.defaults.clicked.connect(self.show_defaults) self.l.addWidget(self.defaults) - reset_confirmation_button = QPushButton(_('Reset disabled &confirmation dialogs'), self) - reset_confirmation_button.setToolTip(_( - 'Reset all show me again dialogs for the FanFictionDownLoader plugin')) - reset_confirmation_button.clicked.connect(self.reset_dialogs) - self.l.addWidget(reset_confirmation_button) - - def set_collisions(self): - prev=self.collision.currentText() - self.collision.clear() - for o in collision_order: - if self.fileform.currentText() == 'epub' or o not in [UPDATE,UPDATEALWAYS]: - self.collision.addItem(o) - i = self.collision.findText(prev) - if i > -1: - self.collision.setCurrentIndex(i) - - def save_settings(self): - prefs['fileform'] = unicode(self.fileform.currentText()) - prefs['collision'] = unicode(self.collision.currentText()) - prefs['updatemeta'] = self.updatemeta.isChecked() - prefs['keeptags'] = self.keeptags.isChecked() - prefs['urlsfromclip'] = self.urlsfromclip.isChecked() - prefs['updatedefault'] = self.updatedefault.isChecked() - prefs['deleteotherforms'] = self.deleteotherforms.isChecked() - prefs['addtolists'] = self.addtolists.isChecked() - - ini = unicode(self.ini.toPlainText()) - if ini: - prefs['personal.ini'] = ini - else: - # if they've removed everything, clear it so they get the - # default next time. - del prefs['personal.ini'] + # self.l.insertStretch(-1) + # let edit box fill the space. def show_defaults(self): text = get_resources('plugin-defaults.ini') ShowDefaultsIniDialog(self.windowIcon(),text,self).exec_() - def reset_dialogs(self): - for key in dynamic.keys(): - if key.startswith('fanfictiondownloader_') and key.endswith('_again') \ - and dynamic[key] is False: - dynamic[key] = True - info_dialog(self, _('Done'), - _('Confirmation dialogs have all been reset'), show=True) - - class ShowDefaultsIniDialog(QDialog): def __init__(self, icon, text, parent=None): @@ -252,3 +310,93 @@ class ShowDefaultsIniDialog(QDialog): self.ok_button.clicked.connect(self.hide) self.l.addWidget(self.ok_button) +class ListTab(QWidget): + + def __init__(self, parent_dialog, plugin_action): + self.parent_dialog = parent_dialog + self.plugin_action = plugin_action + QWidget.__init__(self) + + self.l = QVBoxLayout() + self.setLayout(self.l) + + rl_plugin = plugin_action.gui.iactions['Reading List'] + reading_lists = rl_plugin.get_list_names() + # print("Reading Lists:%s"%reading_lists) + + label = QLabel('These settings provide integration with the Reading List Plugin. Reading List can automatically send to devices and change custom columns. You have to create and configure the lists in Reading List to be useful.') + label.setWordWrap(True) + self.l.addWidget(label) + self.l.addSpacing(5) + + self.addtolists = QCheckBox('Add new/updated stories to "Send to Device" Reading List(s).',self) + self.addtolists.setToolTip('Automatically add new/updated stories to these lists in the Reading List plugin.') + self.addtolists.setChecked(prefs['addtolists']) + self.l.addWidget(self.addtolists) + + horz = QHBoxLayout() + label = QLabel('"Send to Device" Reading Lists') + label.setToolTip("When enabled, new/updated stories will be automatically added to these lists.") + horz.addWidget(label) + self.send_lists_box = MultiCompleteLineEdit(self) + self.send_lists_box.setToolTip("When enabled, new/updated stories will be automatically added to these lists.") + self.send_lists_box.update_items_cache(reading_lists) + self.send_lists_box.setText(prefs['send_lists']) + horz.addWidget(self.send_lists_box) + self.l.addLayout(horz) + + self.addtoreadlists = QCheckBox('Add new/updated stories to "To Read" Reading List(s).',self) + self.addtoreadlists.setToolTip('Automatically add new/updated stories to these lists in the Reading List plugin.\nAlso offers menu option to remove stories from the "To Read" lists.') + self.addtoreadlists.setChecked(prefs['addtoreadlists']) + self.l.addWidget(self.addtoreadlists) + + horz = QHBoxLayout() + label = QLabel('"To Read" Reading Lists') + label.setToolTip("When enabled, new/updated stories will be automatically added to these lists.") + horz.addWidget(label) + self.read_lists_box = MultiCompleteLineEdit(self) + self.read_lists_box.setToolTip("When enabled, new/updated stories will be automatically added to these lists.") + self.read_lists_box.update_items_cache(reading_lists) + self.read_lists_box.setText(prefs['read_lists']) + horz.addWidget(self.read_lists_box) + self.l.addLayout(horz) + + self.addtolistsonread = QCheckBox('Add stories back to "Send to Device" Reading List(s) when marked "Read".',self) + self.addtolistsonread.setToolTip('Menu option to remove from "To Read" lists will also add stories back to "Send to Device" Reading List(s)') + self.addtolistsonread.setChecked(prefs['addtolistsonread']) + self.l.addWidget(self.addtolistsonread) + + self.l.insertStretch(-1) + +class OtherTab(QWidget): + + def __init__(self, parent_dialog, plugin_action): + self.parent_dialog = parent_dialog + self.plugin_action = plugin_action + QWidget.__init__(self) + + self.l = QVBoxLayout() + self.setLayout(self.l) + + keyboard_shortcuts_button = QPushButton('Keyboard shortcuts...', self) + keyboard_shortcuts_button.setToolTip(_( + 'Edit the keyboard shortcuts associated with this plugin')) + keyboard_shortcuts_button.clicked.connect(parent_dialog.edit_shortcuts) + self.l.addWidget(keyboard_shortcuts_button) + + reset_confirmation_button = QPushButton(_('Reset disabled &confirmation dialogs'), self) + reset_confirmation_button.setToolTip(_( + 'Reset all show me again dialogs for the FanFictionDownLoader plugin')) + reset_confirmation_button.clicked.connect(self.reset_dialogs) + self.l.addWidget(reset_confirmation_button) + + self.l.insertStretch(-1) + + def reset_dialogs(self): + for key in dynamic.keys(): + if key.startswith('fanfictiondownloader_') and key.endswith('_again') \ + and dynamic[key] is False: + dynamic[key] = True + info_dialog(self, _('Done'), + _('Confirmation dialogs have all been reset'), show=True) + diff --git a/calibre-plugin/ffdl_plugin.py b/calibre-plugin/ffdl_plugin.py index ac0fa472..0a9a0695 100644 --- a/calibre-plugin/ffdl_plugin.py +++ b/calibre-plugin/ffdl_plugin.py @@ -7,7 +7,7 @@ __license__ = 'GPL v3' __copyright__ = '2012, Jim Miller' __docformat__ = 'restructuredtext en' -import time, os, copy +import time, os, copy, threading from ConfigParser import SafeConfigParser from StringIO import StringIO from functools import partial @@ -20,6 +20,7 @@ from calibre.ebooks.metadata import MetaInformation, authors_to_string from calibre.ebooks.metadata.meta import get_metadata from calibre.gui2 import error_dialog, warning_dialog, question_dialog, info_dialog from calibre.gui2.dialogs.message_box import ViewLog +from calibre.gui2.dialogs.confirm_delete import confirm # The class that all interface action plugins must inherit from from calibre.gui2.actions import InterfaceAction @@ -78,11 +79,6 @@ class FanFictionDownLoaderPlugin(InterfaceAction): icon_resources = self.load_resources(PLUGIN_ICONS) set_plugin_icon_resources(self.name, icon_resources) - # 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 base = self.interface_action_base_plugin self.version = base.name+" v%d.%d.%d"%base.version @@ -107,58 +103,99 @@ class FanFictionDownLoaderPlugin(InterfaceAction): # Assign our menu to this action self.menu = QMenu(self.gui) + self.old_actions_unique_map = {} self.qaction.setMenu(self.menu) - self.menu.aboutToShow.connect(self.about_to_show_menu) - self.actions_unique_map = {} - - self.add_action = self.create_menu_item_ex(self.menu, '&Add New from URL(s)', image='plus.png', - unique_name='Add New FanFiction Book(s) from URL(s)', - shortcut_name='Add New FanFiction Book(s) from URL(s)', - triggered=self.add_dialog ) - - self.update_action = self.create_menu_item_ex(self.menu, '&Update Existing FanFiction Book(s)', image='plusplus.png', - unique_name='Update Existing FanFiction Book(s)', - shortcut_name='Update Existing FanFiction Book(s)', - triggered=self.update_existing) - - self.menu.addSeparator() - self.add_send_action = self.create_menu_item_ex(self.menu, 'Add Selected to 000 and Send Lists', image='plusplus.png', - unique_name='Add Selected to 000 and Send Lists', - shortcut_name='Add Selected to 000 and Send Lists', - triggered=partial(self.update_lists,add=True)) - - self.add_remove_action = self.create_menu_item_ex(self.menu, 'Remove Selected from 000, add to Send Lists', image='minusminus.png', - unique_name='Remove Selected from 000, add to Send Lists', - shortcut_name='Remove Selected from 000, add to Send Lists', - triggered=partial(self.update_lists,add=False)) - - 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', - shortcut_name='Get URLs from Selected Books', - triggered=self.get_list_urls) - - self.menu.addSeparator() - 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.config_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) - + self.menus_lock = threading.RLock() def about_to_show_menu(self): - self.update_action.setEnabled( len(self.gui.library_view.get_selected_ids()) > 0 ) - self.add_send_action.setEnabled( prefs['addtolists'] and len(self.gui.library_view.get_selected_ids()) > 0 ) - self.add_remove_action.setEnabled( prefs['addtolists'] and len(self.gui.library_view.get_selected_ids()) > 0 ) - self.get_list_action.setEnabled( len(self.gui.library_view.get_selected_ids()) > 0 ) + self.rebuild_menus() + + 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 = {} + self.add_action = self.create_menu_item_ex(self.menu, '&Add New from URL(s)', image='plus.png', + unique_name='Add New FanFiction Book(s) from URL(s)', + shortcut_name='Add New FanFiction Book(s) from URL(s)', + triggered=self.add_dialog ) + + self.update_action = self.create_menu_item_ex(self.menu, '&Update Existing FanFiction Book(s)', image='plusplus.png', + unique_name='Update Existing FanFiction Book(s)', + shortcut_name='Update Existing FanFiction Book(s)', + triggered=self.update_existing) + + if 'Reading List' in self.gui.iactions and (prefs['addtolists'] or prefs['addtoreadlists']) : + ## XXX mod and rebuild menu when lists selected/empty + self.menu.addSeparator() + addmenutxt, rmmenutxt = None, None + if prefs['addtolists'] and prefs['addtoreadlists'] : + addmenutxt = 'Add to "To Read" and "Send to Device" Lists' + if prefs['addtolistsonread']: + rmmenutxt = 'Remove from "To Read" and add to "Send to Device" Lists' + else: + rmmenutxt = 'Remove from "To Read" Lists' + elif prefs['addtolists'] : + addmenutxt = 'Add Selected to "Send to Device" Lists' + elif prefs['addtoreadlists']: + addmenutxt = 'Add to "To Read" Lists' + rmmenutxt = 'Remove from "To Read" Lists' + + if addmenutxt: + self.add_send_action = self.create_menu_item_ex(self.menu, addmenutxt, image='plusplus.png', + unique_name=addmenutxt, + shortcut_name=addmenutxt, + triggered=partial(self.update_lists,add=True)) + + if rmmenutxt: + self.add_remove_action = self.create_menu_item_ex(self.menu, rmmenutxt, image='minusminus.png', + unique_name=rmmenutxt, + 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', + shortcut_name='Get URLs from Selected Books', + triggered=self.get_list_urls) + + self.menu.addSeparator() + 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.config_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) + + self.update_action.setEnabled( len(self.gui.library_view.get_selected_ids()) > 0 ) + self.get_list_action.setEnabled( len(self.gui.library_view.get_selected_ids()) > 0 ) + + # Before we finalize, make sure we delete any actions for menus that are no longer displayed + for menu_id, unique_name in self.old_actions_unique_map.iteritems(): + if menu_id not in self.actions_unique_map: + self.gui.keyboard.unregister_shortcut(unique_name) + self.old_actions_unique_map = self.actions_unique_map + self.gui.keyboard.finalize() def about(self): # Get the about text from a file inside the plugin zip file @@ -170,8 +207,9 @@ class FanFictionDownLoaderPlugin(InterfaceAction): # should pass a list of names to get_resources. In this case, # get_resources will return a dictionary mapping names to bytes. Names that # are not found in the zip file will not be in the returned dictionary. + text = get_resources('about.txt') - AboutDialog(self.gui,self.qaction.icon(),text).exec_() + AboutDialog(self.gui,self.qaction.icon(),self.version + text).exec_() def create_menu_item_ex(self, parent_menu, menu_text, image=None, tooltip=None, shortcut=None, triggered=None, is_checked=None, shortcut_name=None, @@ -188,9 +226,10 @@ class FanFictionDownLoaderPlugin(InterfaceAction): self.add_dialog() def update_lists(self,add=True): - if len(self.gui.library_view.get_selected_ids()) > 0 and prefs['addtolists']: + if len(self.gui.library_view.get_selected_ids()) > 0 and \ + (prefs['addtolists'] or prefs['addtoreadlists']) : self._update_reading_lists(self.gui.library_view.get_selected_ids(),add) - self.gui.library_view.model().refresh_ids(self.gui.library_view.get_selected_ids()) + #self.gui.library_view.model().refresh_ids(self.gui.library_view.get_selected_ids()) def get_list_urls(self): if len(self.gui.library_view.get_selected_ids()) > 0: @@ -547,6 +586,7 @@ class FanFictionDownLoaderPlugin(InterfaceAction): self.gui.job_exception(job, dialog_title='Failed to Download Stories') return + previous = self.gui.library_view.currentIndex() db = self.gui.current_db d = DisplayStoryListDialog(self.gui, @@ -590,6 +630,10 @@ class FanFictionDownLoaderPlugin(InterfaceAction): if update_ids: self.gui.library_view.model().refresh_ids(update_ids) + current = self.gui.library_view.currentIndex() + self.gui.library_view.model().current_changed(current, previous) + self.gui.tags_view.recount() + self.gui.status_bar.show_message(_('Finished Adding/Updating %d books.'%(len(update_list) + len(add_list))), 3000) if len(update_list) + len(add_list) != total_good: @@ -634,7 +678,7 @@ class FanFictionDownLoaderPlugin(InterfaceAction): print("remove f:"+fmt) db.remove_format(book['calibre_id'], fmt, index_is_id=True)#, notify=False - if prefs['addtolists']: + if prefs['addtolists'] or prefs['addtoreadlists']: self._update_reading_lists([book_id],add=True) return book_id @@ -653,35 +697,59 @@ class FanFictionDownLoaderPlugin(InterfaceAction): mi.tags = list(set(list(old_tags)+mi.tags)) db.set_metadata(book_id,mi) - + + def _get_clean_reading_lists(self,lists): + if lists == None or lists.strip() == "" : + return [] + else: + return filter( lambda x : x, map( lambda x : x.strip(), lists.split(',') ) ) + def _update_reading_lists(self,book_ids,add=True): try: rl_plugin = self.gui.iactions['Reading List'] except: - confirm("You don't have the plugin Reading List installed.",'fanfictiondownloader_no_readinglist_plugin_item', self) + if prefs['addtolists'] or prefs['addtoreadlists']: + message="

You configured FanFictionDownLoader to automatically update Reading Lists, but you don't have the Reading List plugin installed anymore?

" + confirm(message,'fanfictiondownloader_no_reading_list_plugin', self.gui) return - - if add: - for l in readlists: + + # XXX check for existence of lists, warning if not. + if prefs['addtoreadlists']: + if add: + addremovefunc = rl_plugin.add_books_to_list + else: + addremovefunc = rl_plugin.remove_books_from_list + + lists = self._get_clean_reading_lists(prefs['read_lists']) + if len(lists) < 1 : + message="

You configured FanFictionDownLoader to automatically update \"To Read\" Reading Lists, but you don't have any lists set?

" + confirm(message,'fanfictiondownloader_no_read_lists', self.gui) + for l in lists: if l in rl_plugin.get_list_names(): + #print("add good read l:(%s)"%l) + addremovefunc(l, + book_ids, + display_warnings=False) + else: + if l != '': + message="

You configured FanFictionDownLoader to automatically update Reading List '%s', but you don't have a list of that name?

"%l + confirm(message,'fanfictiondownloader_no_reading_list_%s'%l, self.gui) + + if prefs['addtolists'] and (add or (prefs['addtolistsonread'] and prefs['addtoreadlists']) ): + lists = self._get_clean_reading_lists(prefs['send_lists']) + if len(lists) < 1 : + message="

You configured FanFictionDownLoader to automatically update \"Send to Device\" Reading Lists, but you don't have any lists set?

" + confirm(message,'fanfictiondownloader_no_send_lists', self.gui) + for l in lists: + if l in rl_plugin.get_list_names(): + #print("good send l:(%s)"%l) rl_plugin.add_books_to_list(l, book_ids, - refresh_screen=False, display_warnings=False) - else: - for l in readlists: - if l in rl_plugin.get_list_names(): - rl_plugin.remove_books_from_list(l, - book_ids, - refresh_screen=False, - display_warnings=False) - - for l in sendlists: - if l in rl_plugin.get_list_names(): - rl_plugin.add_books_to_list(l, - book_ids, - refresh_screen=False, - display_warnings=False) + else: + if l != '': + message="

You configured FanFictionDownLoader to automatically update Reading List '%s', but you don't have a list of that name?

"%l + confirm(message,'fanfictiondownloader_no_reading_list_%s'%l, self.gui) def _find_existing_book_id(self,db,book,matchurl=True): mi = MetaInformation(book["title"],(book["author"],)) # author is a list. @@ -708,8 +776,14 @@ class FanFictionDownLoaderPlugin(InterfaceAction): def _convert_urls_to_books(self, urls): books = [] + uniqueurls = set() for url in urls: - books.append(self._convert_url_to_book(url)) + book = self._convert_url_to_book(url) + if book['url'] in uniqueurls: + book['good'] = False + book['comment'] = "Same story already included." + uniqueurls.add(book['url']) + books.append(book) return books def _convert_url_to_book(self, url): diff --git a/makeplugin.py b/makeplugin.py index 9cdb3dce..e4abac41 100644 --- a/makeplugin.py +++ b/makeplugin.py @@ -27,7 +27,7 @@ if __name__=="__main__": exclude=['*.pyc','*~','*.xcf'] # from top dir. 'w' for overwrite createZipFile(filename,"w", - ['plugin-defaults.ini','example.ini','epubmerge.py','fanficdownloader'], + ['plugin-defaults.ini','plugin-example.ini','epubmerge.py','fanficdownloader'], exclude=exclude) #from calibre-plugin dir. 'a' for append os.chdir('calibre-plugin') diff --git a/plugin-defaults.ini b/plugin-defaults.ini index b2e8e626..5c58b67f 100644 --- a/plugin-defaults.ini +++ b/plugin-defaults.ini @@ -213,9 +213,6 @@ extratags: ## this should go in your personal.ini, not defaults.ini. #is_adult:true -## fictionally.org storyIds are not unique. Combine with authorId. -output_filename: ${title}-${siteabbrev}_${authorId}_${storyId}${formatext} - [www.harrypotterfanfiction.com] ## Some sites do not require a login, but do require the user to ## confirm they are adult for adult content. In commandline version, @@ -233,6 +230,12 @@ output_filename: ${title}-${siteabbrev}_${authorId}_${storyId}${formatext} ## confirm they are adult for adult content. In commandline version, ## this should go in your personal.ini, not defaults.ini. #is_adult:true +## tth is a little unusual--it doesn't require user/pass, but the site +## keeps track of which chapters you've read and won't send another +## update until it thinks you're up to date. This way, on download, +## it thinks you're up to date. +#username:YourName +#password:yourpassword [overrides] ## It may sometimes be useful to override all of the specific format, diff --git a/plugin-example.ini b/plugin-example.ini new file mode 100644 index 00000000..1af67327 --- /dev/null +++ b/plugin-example.ini @@ -0,0 +1,55 @@ +## This is an example of what your personal configuration might look +## like. + +[defaults] +## Some sites also require the user to confirm they are adult for +## adult content. In commandline version, this should go in your +## personal.ini, not defaults.ini. +#is_adult:true + +## Most common, I expect will be using this to save username/passwords +## for different sites. +[www.twilighted.net] +#username:YourPenname +#password:YourPassword + +[www.ficwad.com] +#username:YourUsername +#password:YourPassword + +[www.twiwrite.net] +#username:YourName +#password:yourpassword + +[www.adastrafanfic.com] +## Some sites do not require a login, but do require the user to +## confirm they are adult for adult content. +#is_adult:true + +[www.thewriterscoffeeshop.com] +#username:YourName +#password:yourpassword +#is_adult:true + +[www.fictionalley.org] +#is_adult:true + +[www.harrypotterfanfiction.com] +#is_adult:true + +[www.fimfiction.net] +#is_adult:true + +[www.tthfanfic.org] +#is_adult:true +## tth is a little unusual--it doesn't require user/pass, but the site +## keeps track of which chapters you've read and won't send another +## update until it thinks you're up to date. This way, on download, +## it thinks you're up to date. +#username:YourName +#password:yourpassword + + +## This section will override anything in the system defaults or other +## sections here. +[overrides]