mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Catalog generation for the calibre GUI
This commit is contained in:
commit
22035b8e07
@ -2,10 +2,11 @@ from __future__ import with_statement
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import sys
|
import atexit, os, shutil, sys, tempfile, zipfile
|
||||||
|
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
|
||||||
from calibre.constants import numeric_version
|
from calibre.constants import numeric_version
|
||||||
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
|
|
||||||
|
|
||||||
class Plugin(object):
|
class Plugin(object):
|
||||||
'''
|
'''
|
||||||
@ -231,6 +232,8 @@ class CatalogPlugin(Plugin):
|
|||||||
A plugin that implements a catalog generator.
|
A plugin that implements a catalog generator.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
resources_path = None
|
||||||
|
|
||||||
#: Output file type for which this plugin should be run
|
#: Output file type for which this plugin should be run
|
||||||
#: For example: 'epub' or 'xml'
|
#: For example: 'epub' or 'xml'
|
||||||
file_types = set([])
|
file_types = set([])
|
||||||
@ -249,14 +252,18 @@ class CatalogPlugin(Plugin):
|
|||||||
|
|
||||||
cli_options = []
|
cli_options = []
|
||||||
|
|
||||||
|
|
||||||
def search_sort_db(self, db, opts):
|
def search_sort_db(self, db, opts):
|
||||||
if opts.search_text:
|
|
||||||
|
# If declared, --ids overrides any declared search criteria
|
||||||
|
if not opts.ids and opts.search_text:
|
||||||
db.search(opts.search_text)
|
db.search(opts.search_text)
|
||||||
|
|
||||||
if opts.sort_by:
|
if opts.sort_by:
|
||||||
# 2nd arg = ascending
|
# 2nd arg = ascending
|
||||||
db.sort(opts.sort_by, True)
|
db.sort(opts.sort_by, True)
|
||||||
|
|
||||||
return db.get_data_as_dict()
|
return db.get_data_as_dict(ids=opts.ids)
|
||||||
|
|
||||||
def get_output_fields(self, opts):
|
def get_output_fields(self, opts):
|
||||||
# Return a list of requested fields, with opts.sort_by first
|
# Return a list of requested fields, with opts.sort_by first
|
||||||
@ -272,11 +279,40 @@ class CatalogPlugin(Plugin):
|
|||||||
fields = list(all_fields & requested_fields)
|
fields = list(all_fields & requested_fields)
|
||||||
else:
|
else:
|
||||||
fields = list(all_fields)
|
fields = list(all_fields)
|
||||||
|
|
||||||
fields.sort()
|
fields.sort()
|
||||||
|
if opts.sort_by:
|
||||||
fields.insert(0,fields.pop(int(fields.index(opts.sort_by))))
|
fields.insert(0,fields.pop(int(fields.index(opts.sort_by))))
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
def run(self, path_to_output, opts, db):
|
def initialize(self):
|
||||||
|
'''
|
||||||
|
If plugin is not a built-in, copy the plugin's .ui and .py files from
|
||||||
|
the zip file to $TMPDIR.
|
||||||
|
Tab will be dynamically generated and added to the Catalog Options dialog in
|
||||||
|
calibre.gui2.dialogs.catalog.py:Catalog
|
||||||
|
'''
|
||||||
|
from calibre.customize.builtins import plugins as builtin_plugins
|
||||||
|
from calibre.customize.ui import config
|
||||||
|
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||||
|
|
||||||
|
if not type(self) in builtin_plugins and \
|
||||||
|
not self.name in config['disabled_plugins']:
|
||||||
|
files_to_copy = ["%s.%s" % (self.name.lower(),ext) for ext in ["ui","py"]]
|
||||||
|
resources = zipfile.ZipFile(self.plugin_path,'r')
|
||||||
|
|
||||||
|
if self.resources_path is None:
|
||||||
|
self.resources_path = PersistentTemporaryDirectory('_plugin_resources', prefix='')
|
||||||
|
|
||||||
|
for file in files_to_copy:
|
||||||
|
try:
|
||||||
|
resources.extract(file, self.resources_path)
|
||||||
|
except:
|
||||||
|
print " customize:__init__.initialize(): %s not found in %s" % (file, os.path.basename(self.plugin_path))
|
||||||
|
continue
|
||||||
|
resources.close()
|
||||||
|
|
||||||
|
def run(self, path_to_output, opts, db, ids):
|
||||||
'''
|
'''
|
||||||
Run the plugin. Must be implemented in subclasses.
|
Run the plugin. Must be implemented in subclasses.
|
||||||
It should generate the catalog in the format specified
|
It should generate the catalog in the format specified
|
||||||
|
@ -4,9 +4,14 @@ __license__ = 'GPL 3'
|
|||||||
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
from calibre.ebooks.conversion.plumber import Plumber
|
import os
|
||||||
from calibre.utils.logging import Log
|
from optparse import OptionParser
|
||||||
|
|
||||||
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
||||||
|
from calibre.ebooks.conversion.plumber import Plumber
|
||||||
|
from calibre.customize.ui import plugin_for_catalog_format
|
||||||
|
from calibre.utils.logging import Log
|
||||||
|
from calibre.gui2 import choose_dir, Application
|
||||||
|
|
||||||
def gui_convert(input, output, recommendations, notification=DummyReporter(),
|
def gui_convert(input, output, recommendations, notification=DummyReporter(),
|
||||||
abort_after_input_dump=False, log=None):
|
abort_after_input_dump=False, log=None):
|
||||||
@ -20,7 +25,7 @@ def gui_convert(input, output, recommendations, notification=DummyReporter(),
|
|||||||
|
|
||||||
plumber.run()
|
plumber.run()
|
||||||
|
|
||||||
def gui_catalog(fmt, title, dbspec, ids, out_file_name,
|
def gui_catalog(fmt, title, dbspec, ids, out_file_name, fmt_options,
|
||||||
notification=DummyReporter(), log=None):
|
notification=DummyReporter(), log=None):
|
||||||
if log is None:
|
if log is None:
|
||||||
log = Log()
|
log = Log()
|
||||||
@ -31,8 +36,28 @@ def gui_catalog(fmt, title, dbspec, ids, out_file_name,
|
|||||||
db = LibraryDatabase2(dbpath)
|
db = LibraryDatabase2(dbpath)
|
||||||
else: # To be implemented in the future
|
else: # To be implemented in the future
|
||||||
pass
|
pass
|
||||||
# Implement the interface to the catalog generating code here
|
|
||||||
db
|
# Create a minimal OptionParser that we can append to
|
||||||
|
parser = OptionParser()
|
||||||
|
args = []
|
||||||
|
parser.add_option("--verbose", action="store_true", dest="verbose", default=True)
|
||||||
|
opts, args = parser.parse_args()
|
||||||
|
|
||||||
|
# Populate opts
|
||||||
|
opts.ids = ids
|
||||||
|
opts.search_text = None
|
||||||
|
opts.sort_by = None
|
||||||
|
|
||||||
|
# Extract the option dictionary to comma-separated lists
|
||||||
|
for option in fmt_options:
|
||||||
|
setattr(opts,option, ','.join(fmt_options[option]))
|
||||||
|
|
||||||
|
# Fetch and run the plugin for fmt
|
||||||
|
plugin = plugin_for_catalog_format(fmt)
|
||||||
|
plugin.run(out_file_name, opts, db)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,29 +6,121 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog
|
import os, shutil, sys, tempfile
|
||||||
|
|
||||||
|
from PyQt4.Qt import QDialog, QWidget
|
||||||
|
|
||||||
|
from calibre.customize.ui import config
|
||||||
from calibre.gui2.dialogs.catalog_ui import Ui_Dialog
|
from calibre.gui2.dialogs.catalog_ui import Ui_Dialog
|
||||||
from calibre.gui2 import dynamic
|
from calibre.gui2 import gprefs, dynamic
|
||||||
from calibre.customize.ui import available_catalog_formats
|
from calibre.customize.ui import available_catalog_formats, catalog_plugins
|
||||||
|
from calibre.gui2.catalog.catalog_csv_xml import PluginWidget
|
||||||
|
|
||||||
class Catalog(QDialog, Ui_Dialog):
|
class Catalog(QDialog, Ui_Dialog):
|
||||||
|
''' Catalog Dialog builder'''
|
||||||
|
widgets = []
|
||||||
|
|
||||||
def __init__(self, parent, dbspec, ids):
|
def __init__(self, parent, dbspec, ids):
|
||||||
|
import re, cStringIO
|
||||||
|
from calibre import prints as info
|
||||||
|
from calibre.gui2 import dynamic
|
||||||
|
from PyQt4.uic import compileUi
|
||||||
|
|
||||||
QDialog.__init__(self, parent)
|
QDialog.__init__(self, parent)
|
||||||
|
|
||||||
|
# Run the dialog setup generated from catalog.ui
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.dbspec, self.ids = dbspec, ids
|
self.dbspec, self.ids = dbspec, ids
|
||||||
|
|
||||||
|
# Display the number of books we've been passed
|
||||||
self.count.setText(unicode(self.count.text()).format(len(ids)))
|
self.count.setText(unicode(self.count.text()).format(len(ids)))
|
||||||
|
|
||||||
|
# Display the last-used title
|
||||||
self.title.setText(dynamic.get('catalog_last_used_title',
|
self.title.setText(dynamic.get('catalog_last_used_title',
|
||||||
_('My Books')))
|
_('My Books')))
|
||||||
fmts = sorted([x.upper() for x in available_catalog_formats()])
|
|
||||||
|
|
||||||
|
# GwR *** Add option tabs for built-in formats
|
||||||
|
# This code models #69 in calibre/gui2/dialogs/config/__init__.py
|
||||||
|
|
||||||
|
self.fmts = []
|
||||||
|
|
||||||
|
from calibre.customize.builtins import plugins as builtin_plugins
|
||||||
|
from calibre.customize import CatalogPlugin
|
||||||
|
|
||||||
|
for plugin in catalog_plugins():
|
||||||
|
if plugin.name in config['disabled_plugins']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = plugin.name.lower().replace(' ', '_')
|
||||||
|
if type(plugin) in builtin_plugins:
|
||||||
|
#info("Adding widget for builtin Catalog plugin %s" % plugin.name)
|
||||||
|
try:
|
||||||
|
catalog_widget = __import__('calibre.gui2.catalog.'+name,
|
||||||
|
fromlist=[1])
|
||||||
|
pw = catalog_widget.PluginWidget()
|
||||||
|
pw.initialize(name)
|
||||||
|
pw.ICON = I('forward.svg')
|
||||||
|
self.widgets.append(pw)
|
||||||
|
[self.fmts.append([file_type.upper(), pw.sync_enabled,pw]) for file_type in plugin.file_types]
|
||||||
|
except ImportError:
|
||||||
|
info("ImportError with %s" % name)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Load dynamic tab
|
||||||
|
form = os.path.join(plugin.resources_path,'%s.ui' % name)
|
||||||
|
klass = os.path.join(plugin.resources_path,'%s.py' % name)
|
||||||
|
compiled_form = os.path.join(plugin.resources_path,'%s_ui.py' % name)
|
||||||
|
|
||||||
|
if os.path.exists(form) and os.path.exists(klass):
|
||||||
|
#info("Adding widget for user-installed Catalog plugin %s" % plugin.name)
|
||||||
|
|
||||||
|
# Compile the .ui form provided in plugin.zip
|
||||||
|
if not os.path.exists(compiled_form):
|
||||||
|
# info('\tCompiling form', form)
|
||||||
|
buf = cStringIO.StringIO()
|
||||||
|
compileUi(form, buf)
|
||||||
|
dat = buf.getvalue()
|
||||||
|
dat = re.compile(r'QtGui.QApplication.translate\(.+?,\s+"(.+?)(?<!\\)",.+?\)',
|
||||||
|
re.DOTALL).sub(r'_("\1")', dat)
|
||||||
|
open(compiled_form, 'wb').write(dat)
|
||||||
|
|
||||||
|
# Import the dynamic PluginWidget() from .py file provided in plugin.zip
|
||||||
|
try:
|
||||||
|
sys.path.insert(0, plugin.resources_path)
|
||||||
|
catalog_widget = __import__(name, fromlist=[1])
|
||||||
|
pw = catalog_widget.PluginWidget()
|
||||||
|
pw.initialize(name)
|
||||||
|
pw.ICON = I('forward.svg')
|
||||||
|
self.widgets.append(pw)
|
||||||
|
[self.fmts.append([file_type.upper(), pw.sync_enabled,pw]) for file_type in plugin.file_types]
|
||||||
|
except ImportError:
|
||||||
|
info("ImportError with %s" % name)
|
||||||
|
continue
|
||||||
|
finally:
|
||||||
|
sys.path.remove(plugin.resources_path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
info("No dynamic tab resources found for %s" % name)
|
||||||
|
|
||||||
|
self.widgets = sorted(self.widgets, key=lambda x:(x.TITLE, x.TITLE))
|
||||||
|
for pw in self.widgets:
|
||||||
|
page = self.tabs.addTab(pw,pw.TITLE)
|
||||||
|
|
||||||
|
# Generate a sorted list of installed catalog formats/sync_enabled pairs
|
||||||
|
fmts = sorted([x[0] for x in self.fmts])
|
||||||
|
|
||||||
|
self.sync_enabled_formats = []
|
||||||
|
for fmt in self.fmts:
|
||||||
|
if fmt[1]:
|
||||||
|
self.sync_enabled_formats.append(fmt[0])
|
||||||
|
|
||||||
|
# Callback when format changes
|
||||||
self.format.currentIndexChanged.connect(self.format_changed)
|
self.format.currentIndexChanged.connect(self.format_changed)
|
||||||
|
|
||||||
|
# Add the installed catalog format list to the format QComboBox
|
||||||
self.format.addItems(fmts)
|
self.format.addItems(fmts)
|
||||||
|
|
||||||
pref = dynamic.get('catalog_preferred_format', 'EPUB')
|
pref = dynamic.get('catalog_preferred_format', 'CSV')
|
||||||
idx = self.format.findText(pref)
|
idx = self.format.findText(pref)
|
||||||
if idx > -1:
|
if idx > -1:
|
||||||
self.format.setCurrentIndex(idx)
|
self.format.setCurrentIndex(idx)
|
||||||
@ -38,7 +130,7 @@ class Catalog(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
def format_changed(self, idx):
|
def format_changed(self, idx):
|
||||||
cf = unicode(self.format.currentText())
|
cf = unicode(self.format.currentText())
|
||||||
if cf in ('EPUB', 'MOBI'):
|
if cf in self.sync_enabled_formats:
|
||||||
self.sync.setEnabled(True)
|
self.sync.setEnabled(True)
|
||||||
else:
|
else:
|
||||||
self.sync.setDisabled(True)
|
self.sync.setDisabled(True)
|
||||||
|
@ -6,20 +6,26 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>628</width>
|
<width>611</width>
|
||||||
<height>503</height>
|
<height>514</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Generate catalog</string>
|
<string>Generate catalog</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset resource="../../../work/calibre/resources/images.qrc">
|
<iconset>
|
||||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>430</x>
|
||||||
|
<y>470</y>
|
||||||
|
<width>164</width>
|
||||||
|
<height>32</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
@ -27,9 +33,15 @@
|
|||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QTabWidget" name="tabs">
|
<widget class="QTabWidget" name="tabs">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>12</x>
|
||||||
|
<y>39</y>
|
||||||
|
<width>579</width>
|
||||||
|
<height>411</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
@ -64,6 +76,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QLineEdit" name="title"/>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QCheckBox" name="sync">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Send catalog to device automatically</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -77,22 +99,18 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QCheckBox" name="sync">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Send catalog to device automatically</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="2">
|
|
||||||
<widget class="QLineEdit" name="title"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="count">
|
<widget class="QLabel" name="count">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>12</x>
|
||||||
|
<y>12</y>
|
||||||
|
<width>205</width>
|
||||||
|
<height>17</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<weight>75</weight>
|
<weight>75</weight>
|
||||||
@ -103,8 +121,6 @@
|
|||||||
<string>Generate catalog for {0} books</string>
|
<string>Generate catalog for {0} books</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../../../work/calibre/resources/images.qrc"/>
|
<include location="../../../work/calibre/resources/images.qrc"/>
|
||||||
|
@ -238,19 +238,36 @@ def fetch_scheduled_recipe(arg):
|
|||||||
|
|
||||||
def generate_catalog(parent, dbspec, ids):
|
def generate_catalog(parent, dbspec, ids):
|
||||||
from calibre.gui2.dialogs.catalog import Catalog
|
from calibre.gui2.dialogs.catalog import Catalog
|
||||||
|
|
||||||
|
# Build the Catalog dialog in gui2.dialogs.catalog
|
||||||
d = Catalog(parent, dbspec, ids)
|
d = Catalog(parent, dbspec, ids)
|
||||||
|
|
||||||
if d.exec_() != d.Accepted:
|
if d.exec_() != d.Accepted:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Create the output file
|
||||||
out = PersistentTemporaryFile(suffix='_catalog_out.'+d.catalog_format.lower())
|
out = PersistentTemporaryFile(suffix='_catalog_out.'+d.catalog_format.lower())
|
||||||
|
|
||||||
|
# Retrieve plugin options
|
||||||
|
fmt_options = {}
|
||||||
|
for x in range(d.tabs.count()):
|
||||||
|
if str(d.tabs.tabText(x)).find(str(d.catalog_format)) > -1:
|
||||||
|
for fmt in d.fmts:
|
||||||
|
if fmt[0] == d.catalog_format:
|
||||||
|
fmt_options = fmt[2].options()
|
||||||
|
# print "gui2.tools:generate_catalog(): options for %s: %s" % (fmt[0], fmt_options)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
d.catalog_format,
|
d.catalog_format,
|
||||||
d.catalog_title,
|
d.catalog_title,
|
||||||
dbspec,
|
dbspec,
|
||||||
ids,
|
ids,
|
||||||
out.name,
|
out.name,
|
||||||
|
fmt_options
|
||||||
]
|
]
|
||||||
out.close()
|
out.close()
|
||||||
|
|
||||||
|
# This calls gui2.convert.gui_conversion:gui_catalog()
|
||||||
return 'gui_catalog', args, _('Generate catalog'), out.name, d.catalog_sync, \
|
return 'gui_catalog', args, _('Generate catalog'), out.name, d.catalog_sync, \
|
||||||
d.catalog_title
|
d.catalog_title
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
'''The main GUI'''
|
'''The main GUI'''
|
||||||
|
|
||||||
import os, sys, textwrap, collections, time
|
import atexit, os, shutil, sys, tempfile, textwrap, collections, time
|
||||||
from xml.parsers.expat import ExpatError
|
from xml.parsers.expat import ExpatError
|
||||||
from Queue import Queue, Empty
|
from Queue import Queue, Empty
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
@ -357,7 +357,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
cm.addAction(_('Bulk convert'))
|
cm.addAction(_('Bulk convert'))
|
||||||
cm.addSeparator()
|
cm.addSeparator()
|
||||||
ac = cm.addAction(
|
ac = cm.addAction(
|
||||||
_('Create catalog of the books in your calibre library'))
|
_('Create catalog of books in your calibre library'))
|
||||||
ac.triggered.connect(self.generate_catalog)
|
ac.triggered.connect(self.generate_catalog)
|
||||||
self.action_convert.setMenu(cm)
|
self.action_convert.setMenu(cm)
|
||||||
self._convert_single_hook = partial(self.convert_ebook, bulk=False)
|
self._convert_single_hook = partial(self.convert_ebook, bulk=False)
|
||||||
@ -1361,23 +1361,29 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
|
|
||||||
def generate_catalog(self):
|
def generate_catalog(self):
|
||||||
rows = self.library_view.selectionModel().selectedRows()
|
rows = self.library_view.selectionModel().selectedRows()
|
||||||
if not rows or len(rows) < 3:
|
if not rows or len(rows) < 2:
|
||||||
rows = xrange(self.library_view.model().rowCount(QModelIndex()))
|
rows = xrange(self.library_view.model().rowCount(QModelIndex()))
|
||||||
ids = map(self.library_view.model().id, rows)
|
ids = map(self.library_view.model().id, rows)
|
||||||
|
|
||||||
dbspec = None
|
dbspec = None
|
||||||
if not ids:
|
if not ids:
|
||||||
return error_dialog(self, _('No books selected'),
|
return error_dialog(self, _('No books selected'),
|
||||||
_('No books selected to generate catalog for'),
|
_('No books selected to generate catalog for'),
|
||||||
show=True)
|
show=True)
|
||||||
|
|
||||||
|
# Calling gui2.tools:generate_catalog()
|
||||||
ret = generate_catalog(self, dbspec, ids)
|
ret = generate_catalog(self, dbspec, ids)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
func, args, desc, out, sync, title = ret
|
func, args, desc, out, sync, title = ret
|
||||||
|
|
||||||
fmt = os.path.splitext(out)[1][1:].upper()
|
fmt = os.path.splitext(out)[1][1:].upper()
|
||||||
job = self.job_manager.run_job(
|
job = self.job_manager.run_job(
|
||||||
Dispatcher(self.catalog_generated), func, args=args,
|
Dispatcher(self.catalog_generated), func, args=args,
|
||||||
description=desc)
|
description=desc)
|
||||||
job.catalog_file_path = out
|
job.catalog_file_path = out
|
||||||
|
job.fmt = fmt
|
||||||
job.catalog_sync, job.catalog_title = sync, title
|
job.catalog_sync, job.catalog_title = sync, title
|
||||||
self.status_bar.showMessage(_('Generating %s catalog...')%fmt)
|
self.status_bar.showMessage(_('Generating %s catalog...')%fmt)
|
||||||
|
|
||||||
@ -1392,7 +1398,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
dynamic.set('catalogs_to_be_synced', sync)
|
dynamic.set('catalogs_to_be_synced', sync)
|
||||||
self.status_bar.showMessage(_('Catalog generated.'), 3000)
|
self.status_bar.showMessage(_('Catalog generated.'), 3000)
|
||||||
self.sync_catalogs()
|
self.sync_catalogs()
|
||||||
|
if job.fmt in ['CSV','XML']:
|
||||||
|
export_dir = choose_dir(self, 'Export Catalog Directory',
|
||||||
|
'Select destination for %s.%s' % (job.catalog_title, job.fmt.lower()))
|
||||||
|
if export_dir:
|
||||||
|
destination = os.path.join(export_dir, '%s.%s' % (job.catalog_title, job.fmt.lower()))
|
||||||
|
shutil.copyfile(job.catalog_file_path, destination)
|
||||||
|
|
||||||
############################### Fetch news #################################
|
############################### Fetch news #################################
|
||||||
|
|
||||||
|
@ -40,8 +40,9 @@ class CSV_XML(CatalogPlugin):
|
|||||||
from calibre.utils.logging import Log
|
from calibre.utils.logging import Log
|
||||||
|
|
||||||
log = Log()
|
log = Log()
|
||||||
self.fmt = path_to_output[path_to_output.rfind('.') + 1:]
|
self.fmt = path_to_output.rpartition('.')[2]
|
||||||
if opts.verbose:
|
|
||||||
|
if False and opts.verbose:
|
||||||
log("%s:run" % self.name)
|
log("%s:run" % self.name)
|
||||||
log(" path_to_output: %s" % path_to_output)
|
log(" path_to_output: %s" % path_to_output)
|
||||||
log(" Output format: %s" % self.fmt)
|
log(" Output format: %s" % self.fmt)
|
||||||
|
@ -644,6 +644,10 @@ def catalog_option_parser(args):
|
|||||||
output, fmt = validate_command_line(parser, args, log)
|
output, fmt = validate_command_line(parser, args, log)
|
||||||
|
|
||||||
# Add options common to all catalog plugins
|
# Add options common to all catalog plugins
|
||||||
|
parser.add_option('-i', '--ids', default=None, dest='ids',
|
||||||
|
help=_("Comma-separated list of database IDs to catalog.\n"
|
||||||
|
"If declared, --search is ignored.\n"
|
||||||
|
"Default: all"))
|
||||||
parser.add_option('-s', '--search', default=None, dest='search_text',
|
parser.add_option('-s', '--search', default=None, dest='search_text',
|
||||||
help=_("Filter the results by the search query. "
|
help=_("Filter the results by the search query. "
|
||||||
"For the format of the search query, please see "
|
"For the format of the search query, please see "
|
||||||
@ -656,31 +660,6 @@ def catalog_option_parser(args):
|
|||||||
# Add options specific to fmt plugin
|
# Add options specific to fmt plugin
|
||||||
plugin = add_plugin_parser_options(fmt, parser, log)
|
plugin = add_plugin_parser_options(fmt, parser, log)
|
||||||
|
|
||||||
# Merge options from GUI Preferences
|
|
||||||
'''
|
|
||||||
# Placeholder sample code until we implement GUI preferences
|
|
||||||
from calibre.library.save_to_disk import config
|
|
||||||
c = config()
|
|
||||||
for pref in ['asciiize', 'update_metadata', 'write_opf', 'save_cover']:
|
|
||||||
opt = c.get_option(pref)
|
|
||||||
switch = '--dont-'+pref.replace('_', '-')
|
|
||||||
parser.add_option(switch, default=True, action='store_false',
|
|
||||||
help=opt.help+' '+_('Specifying this switch will turn '
|
|
||||||
'this behavior off.'), dest=pref)
|
|
||||||
|
|
||||||
for pref in ['timefmt', 'template', 'formats']:
|
|
||||||
opt = c.get_option(pref)
|
|
||||||
switch = '--'+pref
|
|
||||||
parser.add_option(switch, default=opt.default,
|
|
||||||
help=opt.help, dest=pref)
|
|
||||||
|
|
||||||
for pref in ('replace_whitespace', 'to_lowercase'):
|
|
||||||
opt = c.get_option(pref)
|
|
||||||
switch = '--'+pref.replace('_', '-')
|
|
||||||
parser.add_option(switch, default=False, action='store_true',
|
|
||||||
help=opt.help)
|
|
||||||
'''
|
|
||||||
|
|
||||||
return parser, plugin, log
|
return parser, plugin, log
|
||||||
|
|
||||||
def command_catalog(args, dbpath):
|
def command_catalog(args, dbpath):
|
||||||
@ -693,6 +672,9 @@ def command_catalog(args, dbpath):
|
|||||||
return 1
|
return 1
|
||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
log("library.cli:command_catalog dispatching to plugin %s" % plugin.name)
|
log("library.cli:command_catalog dispatching to plugin %s" % plugin.name)
|
||||||
|
if opts.ids:
|
||||||
|
opts.ids = [int(id) for id in opts.ids.split(',')]
|
||||||
|
|
||||||
with plugin:
|
with plugin:
|
||||||
plugin.run(args[1], opts, get_db(dbpath, opts))
|
plugin.run(args[1], opts, get_db(dbpath, opts))
|
||||||
return 0
|
return 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user