diff --git a/calibre-plugin/__init__.py b/calibre-plugin/__init__.py new file mode 100644 index 0000000..544fa3a --- /dev/null +++ b/calibre-plugin/__init__.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +# -*- coding: utf-8 -*- +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'Apache License' +__copyright__ = '2011, Fanficdownloader team' +__docformat__ = 'restructuredtext en' + +# The class that all Interface Action plugin wrappers must inherit from +from calibre.customize import InterfaceActionBase + +class InterfacePluginDemo(InterfaceActionBase): + ''' + This class is a simple wrapper that provides information about the actual + plugin class. The actual interface plugin class is called InterfacePlugin + and is defined in the ui.py file, as specified in the actual_plugin field + below. + + The reason for having two classes is that it allows the command line + calibre utilities to run without needing to load the GUI libraries. + ''' + name = 'aa FanFictionDownLoader Plugin' + description = 'UI plugin to download and maintain FanFiction from various sites.' + supported_platforms = ['windows', 'osx', 'linux'] + author = 'Jim Miller' + version = (1, 0, 0) + minimum_calibre_version = (0, 7, 53) + + #: This field defines the GUI plugin class that contains all the code + #: that actually does something. Its format is module_path:class_name + #: The specified class must be defined in the specified module. + actual_plugin = 'calibre_plugins.fanfictiondownloader_plugin.ui:InterfacePlugin' + + def is_customizable(self): + ''' + This method must return True to enable customization via + Preferences->Plugins + ''' + return True + + def config_widget(self): + ''' + Implement this method and :meth:`save_settings` in your plugin to + use a custom configuration dialog. + + This method, if implemented, must return a QWidget. The widget can have + an optional method validate() that takes no arguments and is called + immediately after the user clicks OK. Changes are applied if and only + if the method returns True. + + If for some reason you cannot perform the configuration at this time, + return a tuple of two strings (message, details), these will be + displayed as a warning dialog to the user and the process will be + aborted. + + The base class implementation of this method raises NotImplementedError + so by default no user configuration is possible. + ''' + # It is important to put this import statement here rather than at the + # top of the module as importing the config class will also cause the + # GUI libraries to be loaded, which we do not want when using calibre + # from the command line + from calibre_plugins.fanfictiondownloader_plugin.config import ConfigWidget + return ConfigWidget() + + def save_settings(self, config_widget): + ''' + Save the settings specified by the user with config_widget. + + :param config_widget: The widget returned by :meth:`config_widget`. + ''' + config_widget.save_settings() + + # Apply the changes + ac = self.actual_plugin_ + if ac is not None: + ac.apply_settings() + + diff --git a/calibre-plugin/about.txt b/calibre-plugin/about.txt new file mode 100644 index 0000000..ae4d602 --- /dev/null +++ b/calibre-plugin/about.txt @@ -0,0 +1,8 @@ +FanFictionDownLoader Plugin Demo +=========================== + +Created by Jim Miller, borrowing heavily from Kovid Goyal's 'The +InterfacePlugin Demo' + +Requires calibre >= 0.7.53 + diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py new file mode 100644 index 0000000..f36cb71 --- /dev/null +++ b/calibre-plugin/config.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'Apache License' +__copyright__ = '2011, Fanficdownloader team' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QWidget, QHBoxLayout, QLabel, QLineEdit + +from calibre.utils.config import JSONConfig + +# This is where all preferences for this plugin will be stored +# Remember that this name (i.e. plugins/fanfictiondownloader_plugin) is also +# in a global namespace, so make it as unique as possible. +# You should always prefix your config file name with plugins/, +# so as to ensure you dont accidentally clobber a calibre config file +prefs = JSONConfig('plugins/fanfictiondownloader_plugin') + +# Set defaults +prefs.defaults['hello_world_msg'] = 'Hello, World!' + +class ConfigWidget(QWidget): + + def __init__(self): + QWidget.__init__(self) + self.l = QHBoxLayout() + self.setLayout(self.l) + + self.label = QLabel('Hello world &message:') + self.l.addWidget(self.label) + + self.msg = QLineEdit(self) + self.msg.setText(prefs['hello_world_msg']) + self.l.addWidget(self.msg) + self.label.setBuddy(self.msg) + + def save_settings(self): + prefs['hello_world_msg'] = unicode(self.msg.text()) + diff --git a/calibre-plugin/images/icon.png b/calibre-plugin/images/icon.png new file mode 100644 index 0000000..ad823e2 Binary files /dev/null and b/calibre-plugin/images/icon.png differ diff --git a/calibre-plugin/plugin-import-name-fanfictiondownloader_plugin.txt b/calibre-plugin/plugin-import-name-fanfictiondownloader_plugin.txt new file mode 100644 index 0000000..e69de29 diff --git a/calibre-plugin/plugin.py b/calibre-plugin/plugin.py new file mode 100644 index 0000000..52ddd7a --- /dev/null +++ b/calibre-plugin/plugin.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'Apache License' +__copyright__ = '2011, Fanficdownloader team' +__docformat__ = 'restructuredtext en' + +if False: + # This is here to keep my python error checker from complaining about + # the builtin functions that will be defined by the plugin loading system + # You do not need this code in your plugins + get_icons = get_resources = None + +from StringIO import StringIO + +from PyQt4.Qt import QDialog, QVBoxLayout, QPushButton, QMessageBox, QLabel +from calibre.ptempfile import PersistentTemporaryFile + +from calibre.ebooks.metadata.epub import get_metadata + +from calibre_plugins.fanfictiondownloader_plugin.config import prefs + +from calibre_plugins.fanfictiondownloader_plugin.fanficdownloader import adapters,writers,exceptions + +import ConfigParser + +class DemoDialog(QDialog): + + def __init__(self, gui, icon, do_user_config): + QDialog.__init__(self, gui) + self.gui = gui + self.do_user_config = do_user_config + + # The current database shown in the GUI + # db is an instance of the class LibraryDatabase2 from database.py + # This class has many, many methods that allow you to do a lot of + # things. + self.db = gui.current_db + + self.l = QVBoxLayout() + self.setLayout(self.l) + + self.label = QLabel(prefs['hello_world_msg']) + self.l.addWidget(self.label) + + self.setWindowTitle('FanFictionDownLoader') + self.setWindowIcon(icon) + + self.about_button = QPushButton('About', self) + self.about_button.clicked.connect(self.about) + self.l.addWidget(self.about_button) + + self.marked_button = QPushButton( + 'Show books with only one format in the calibre GUI', self) + self.marked_button.clicked.connect(self.marked) + self.l.addWidget(self.marked_button) + + self.view_button = QPushButton( + 'View the most recently added book', self) + self.view_button.clicked.connect(self.view) + self.l.addWidget(self.view_button) + + self.ffdl_button = QPushButton( + 'Attempt FFDL', self) + self.ffdl_button.clicked.connect(self.ffdl) + self.l.addWidget(self.ffdl_button) + + self.conf_button = QPushButton( + 'Configure this plugin', self) + self.conf_button.clicked.connect(self.config) + self.l.addWidget(self.conf_button) + + self.resize(self.sizeHint()) + + def about(self): + # Get the about text from a file inside the plugin zip file + # The get_resources function is a builtin function defined for all your + # plugin code. It loads files from the plugin zip file. It returns + # the bytes from the specified file. + # + # Note that if you are loading more than one file, for performance, you + # 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') + QMessageBox.about(self, 'About the Interface Plugin Demo', + text.decode('utf-8')) + + def marked(self): + fmt_idx = self.db.FIELD_MAP['formats'] + matched_ids = set() + for record in self.db.data.iterall(): + # Iterate over all records + fmts = record[fmt_idx] + # fmts is either None or a comma separated list of formats + if fmts and ',' not in fmts: + matched_ids.add(record[0]) + # Mark the records with the matching ids + self.db.set_marked_ids(matched_ids) + + # Tell the GUI to search for all marked records + self.gui.search.setEditText('marked:true') + self.gui.search.do_search() + + def ffdl(self): + + config = ConfigParser.SafeConfigParser() + config.readfp(StringIO(get_resources("defaults.ini"))) + adapter = adapters.getAdapter(config,"http://test1.com?sid=6646") # http://www.fanfiction.net/s/6439390/1/All_Hallows_Eve") # + + writer = writers.getWriter("epub",config,adapter) + tmp = PersistentTemporaryFile(".epub") + writer.writeStory(tmp) + print("tmp: "+tmp.name) + mi = get_metadata(tmp,extract_cover=False) + self.db.add_books([tmp],["EPUB"],[mi]) + QMessageBox.about(self, 'FFDL Metadata', + str(adapter.getStoryMetadataOnly()).decode('utf-8')) + + def view(self): + most_recent = most_recent_id = None + timestamp_idx = self.db.FIELD_MAP['timestamp'] + + for record in self.db.data: + # Iterate over all currently showing records + timestamp = record[timestamp_idx] + if most_recent is None or timestamp > most_recent: + most_recent = timestamp + most_recent_id = record[0] + + if most_recent_id is not None: + # Get the row number of the id as shown in the GUI + row_number = self.db.row(most_recent_id) + # Get a reference to the View plugin + view_plugin = self.gui.iactions['View'] + # Ask the view plugin to launch the viewer for row_number + view_plugin._view_books([row_number]) + + def config(self): + self.do_user_config(parent=self) + # Apply the changes + self.label.setText(prefs['hello_world_msg']) + diff --git a/calibre-plugin/ui.py b/calibre-plugin/ui.py new file mode 100644 index 0000000..d978581 --- /dev/null +++ b/calibre-plugin/ui.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'Apache License' +__copyright__ = '2011, Fanficdownloader team' +__docformat__ = 'restructuredtext en' + +if False: + # This is here to keep my python error checker from complaining about + # the builtin functions that will be defined by the plugin loading system + # You do not need this code in your plugins + get_icons = get_resources = None + +# The class that all interface action plugins must inherit from +from calibre.gui2.actions import InterfaceAction +from calibre_plugins.fanfictiondownloader_plugin.plugin import DemoDialog + +class InterfacePlugin(InterfaceAction): + + name = 'FanFictionDownLoader' + + # Declare the main action associated with this plugin + # The keyboard shortcut can be None if you dont want to use a keyboard + # shortcut. Remember that currently calibre has no central management for + # keyboard shortcuts, so try to use an unusual/unused shortcut. + action_spec = ('FanFictionDownLoader', None, + 'Run the FanFictionDownLoader Plugin', None) + + def genesis(self): + # This method is called once per plugin, do initial setup here + + # Set the icon for this interface action + # The get_icons function is a builtin function defined for all your + # plugin code. It loads icons from the plugin zip file. It returns + # QIcon objects, if you want the actual data, use the analogous + # get_resources builtin function. + # + # Note that if you are loading more than one icon, for performance, you + # should pass a list of names to get_icons. In this case, get_icons + # will return a dictionary mapping names to QIcons. Names that + # are not found in the zip file will result in null QIcons. + icon = get_icons('images/icon.png') + + # The qaction is automatically created from the action_spec defined + # above + self.qaction.setIcon(icon) + self.qaction.triggered.connect(self.show_dialog) + + def show_dialog(self): + # The base plugin object defined in __init__.py + base_plugin_object = self.interface_action_base_plugin + # 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 = base_plugin_object.do_user_config + + # self.gui is the main calibre GUI. It acts as the gateway to access + # all the elements of the calibre user interface, it should also be the + # parent of the dialog + d = DemoDialog(self.gui, self.qaction.icon(), do_user_config) + d.show() + + def apply_settings(self): + from calibre_plugins.fanfictiondownloader_plugin.config import prefs + # In an actual non trivial plugin, you would probably need to + # do something based on the settings in prefs + prefs +