From 230d92dcb562705fb18fb9578431074e07697fab Mon Sep 17 00:00:00 2001 From: GRiker Date: Thu, 23 Dec 2010 12:49:53 -0700 Subject: [PATCH 1/8] GwR catalog UI revisions --- resources/catalog/stylesheet.css | 21 +- src/calibre/gui2/actions/catalog.py | 2 +- src/calibre/gui2/catalog/catalog_epub_mobi.py | 356 +++++++-- src/calibre/gui2/catalog/catalog_epub_mobi.ui | 756 +++++++++++++++--- .../gui2/catalog/catalog_tab_template.ui | 4 +- src/calibre/gui2/dialogs/catalog.ui | 16 +- src/calibre/library/catalog.py | 234 ++++-- 7 files changed, 1114 insertions(+), 275 deletions(-) diff --git a/resources/catalog/stylesheet.css b/resources/catalog/stylesheet.css index 057c6c9f42..07a95661be 100644 --- a/resources/catalog/stylesheet.css +++ b/resources/catalog/stylesheet.css @@ -108,6 +108,13 @@ p.date_read { text-indent:-6em; } +hr.annotations_divider { + width:50%; + margin-left:1em; + margin-top:0em; + margin-bottom:0em; + } + hr.description_divider { width:90%; margin-left:5%; @@ -117,18 +124,20 @@ hr.description_divider { border-left: solid white 0px; } -hr.annotations_divider { - width:50%; - margin-left:1em; - margin-top:0em; - margin-bottom:0em; +hr.merged_comments_divider { + width:80%; + margin-left:10%; + border-top: solid white 0px; + border-right: solid white 0px; + border-bottom: dotted grey 2px; + border-left: solid white 0px; } td.publisher, td.date { font-weight:bold; text-align:center; } -td.rating { +td.rating, td.notes { text-align: center; } td.thumbnail img { diff --git a/src/calibre/gui2/actions/catalog.py b/src/calibre/gui2/actions/catalog.py index a253664a1e..0eba0406a1 100644 --- a/src/calibre/gui2/actions/catalog.py +++ b/src/calibre/gui2/actions/catalog.py @@ -57,7 +57,7 @@ class GenerateCatalogAction(InterfaceAction): if job.result: # Search terms nulled catalog results return error_dialog(self.gui, _('No books found'), - _("No books to catalog\nCheck exclude tags"), + _("No books to catalog\nCheck exclusion criteria"), show=True) if job.failed: return self.gui.job_exception(job) diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index 1ae4efd014..f5fabca80e 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -17,18 +17,55 @@ class PluginWidget(QWidget,Ui_Form): TITLE = _('E-book options') HELP = _('Options specific to')+' EPUB/MOBI '+_('output') - OPTION_FIELDS = [('exclude_genre','\[.+\]'), - ('exclude_tags','~,'+_('Catalog')), - ('generate_titles', True), - ('generate_series', True), - ('generate_recently_added', True), - ('note_tag','*'), - ('numbers_as_text', False), - ('read_pattern','+'), - ('read_source_field_cb','Tag'), - ('wishlist_tag','Wishlist'), - ] + CheckBoxControls = [ + 'generate_titles', + 'generate_series', + 'generate_genres', + 'generate_recently_added', + 'generate_descriptions', + 'include_hr' + ] + ComboBoxControls = [ + 'read_source_field', + 'exclude_source_field', + 'header_note_source_field', + 'merge_source_field' + ] + LineEditControls = [ + 'exclude_genre', + 'exclude_pattern', + 'exclude_tags', + 'read_pattern', + 'wishlist_tag' + ] + RadioButtonControls = [ + 'merge_before', + 'merge_after' + ] + SpinBoxControls = [ + 'thumb_width' + ] + + OPTION_FIELDS = zip(CheckBoxControls, + [True for i in CheckBoxControls], + ['check_box' for i in CheckBoxControls]) + OPTION_FIELDS += zip(ComboBoxControls, + [None for i in ComboBoxControls], + ['combo_box' for i in ComboBoxControls]) + OPTION_FIELDS += zip(RadioButtonControls, + [None for i in RadioButtonControls], + ['radio_button' for i in RadioButtonControls]) + + # LineEditControls + OPTION_FIELDS += zip(['exclude_genre'],['\[.+\]'],['line_edit']) + OPTION_FIELDS += zip(['exclude_pattern'],[None],['line_edit']) + OPTION_FIELDS += zip(['exclude_tags'],['~,'+_('Catalog')],['line_edit']) + OPTION_FIELDS += zip(['read_pattern'],['+'],['line_edit']) + OPTION_FIELDS += zip(['wishlist_tag'],['Wishlist'],['line_edit']) + + # SpinBoxControls + OPTION_FIELDS += zip(['thumb_width'],[1.00],['spin_box']) # Output synced to the connected device? sync_enabled = True @@ -42,105 +79,203 @@ class PluginWidget(QWidget,Ui_Form): def initialize(self, name, db): self.name = name - - # Populate the 'Read book' source fields - all_custom_fields = db.custom_field_keys() - custom_fields = {} - custom_fields['Tag'] = {'field':'tag', 'datatype':u'text'} - for custom_field in all_custom_fields: - field_md = db.metadata_for_field(custom_field) - if field_md['datatype'] in ['bool','composite','datetime','text']: - custom_fields[field_md['name']] = {'field':custom_field, - 'datatype':field_md['datatype']} - - # Add the sorted eligible fields to the combo box - for cf in sorted(custom_fields): - self.read_source_field_cb.addItem(cf) - - self.read_source_fields = custom_fields - self.read_source_field_cb.currentIndexChanged.connect(self.read_source_field_changed) + self.db = db + self.populateComboBoxes() # Update dialog fields from stored options for opt in self.OPTION_FIELDS: - opt_value = gprefs.get(self.name + '_' + opt[0], opt[1]) - if opt[0] in [ - 'generate_recently_added', - 'generate_series', - 'generate_titles', - 'numbers_as_text', - ]: - getattr(self, opt[0]).setChecked(opt_value) + c_name, c_def, c_type = opt + opt_value = gprefs.get(self.name + '_' + c_name, c_def) + if c_type in ['check_box']: + getattr(self, c_name).setChecked(eval(str(opt_value))) + elif c_type in ['combo_box'] and opt_value is not None: + # *** Test this code with combo boxes *** + #index = self.read_source_field.findText(opt_value) + index = getattr(self,c_name).findText(opt_value) + if index == -1 and c_name == 'read_source_field': + index = self.read_source_field.findText('Tag') + #self.read_source_field.setCurrentIndex(index) + getattr(self,c_name).setCurrentIndex(index) + elif c_type in ['line_edit']: + getattr(self, c_name).setText(opt_value) + elif c_type in ['radio_button'] and opt_value is not None: + getattr(self, c_name).setChecked(opt_value) + elif c_type in ['spin_box']: + getattr(self, c_name).setValue(float(opt_value)) - # Combo box - elif opt[0] in ['read_source_field_cb']: - # Look for last-stored combo box value - index = self.read_source_field_cb.findText(opt_value) - if index == -1: - index = self.read_source_field_cb.findText('Tag') - self.read_source_field_cb.setCurrentIndex(index) - - # Text fields - else: - getattr(self, opt[0]).setText(opt_value) - - # Init self.read_source_field - cs = unicode(self.read_source_field_cb.currentText()) + # Init self.read_source_field_name + cs = unicode(self.read_source_field.currentText()) read_source_spec = self.read_source_fields[cs] - self.read_source_field = read_source_spec['field'] + self.read_source_field_name = read_source_spec['field'] + + # Init self.exclude_source_field_name + self.exclude_source_field_name = '' + cs = unicode(self.exclude_source_field.currentText()) + if cs > '': + exclude_source_spec = self.exclude_source_fields[cs] + self.exclude_source_field_name = exclude_source_spec['field'] + + # Init self.merge_source_field_name + self.merge_source_field_name = '' + cs = unicode(self.merge_source_field.currentText()) + if cs > '': + merge_source_spec = self.merge_source_fields[cs] + self.merge_source_field_name = merge_source_spec['field'] + + # Init self.header_note_source_field_name + self.header_note_source_field_name = '' + cs = unicode(self.header_note_source_field.currentText()) + if cs > '': + header_note_source_spec = self.header_note_source_fields[cs] + self.header_note_source_field_name = header_note_source_spec['field'] + + # Hook changes to thumb_width + self.thumb_width.valueChanged.connect(self.thumb_width_changed) def options(self): # Save/return the current options # exclude_genre stores literally # generate_titles, generate_recently_added, numbers_as_text stores as True/False # others store as lists + opts_dict = {} + # Save values to gprefs for opt in self.OPTION_FIELDS: - # Save values to gprefs - if opt[0] in [ - 'generate_recently_added', - 'generate_series', - 'generate_titles', - 'numbers_as_text', - ]: - opt_value = getattr(self,opt[0]).isChecked() + c_name, c_def, c_type = opt + if c_type in ['check_box', 'radio_button']: + opt_value = getattr(self, c_name).isChecked() + elif c_type in ['combo_box']: + opt_value = unicode(getattr(self,c_name).currentText()) + elif c_type in ['line_edit']: + opt_value = unicode(getattr(self, c_name).text()) + elif c_type in ['spin_box']: + opt_value = unicode(getattr(self, c_name).cleanText()) + gprefs.set(self.name + '_' + c_name, opt_value) - # Combo box uses .currentText() - elif opt[0] in ['read_source_field_cb']: - opt_value = unicode(getattr(self, opt[0]).currentText()) - - # text fields use .text() + # Construct opts object + if c_name == 'exclude_tags': + # store as list + opts_dict[c_name] = opt_value.split(',') else: - opt_value = unicode(getattr(self, opt[0]).text()) - gprefs.set(self.name + '_' + opt[0], opt_value) + opts_dict[c_name] = opt_value - # Construct opts - if opt[0] in [ - 'exclude_genre', - 'generate_recently_added', - 'generate_series', - 'generate_titles', - 'numbers_as_text', - ]: - opts_dict[opt[0]] = opt_value - else: - opts_dict[opt[0]] = opt_value.split(',') + # Generate markers for hybrids + opts_dict['read_book_marker'] = "%s:%s" % (self.read_source_field_name, + self.read_pattern.text()) + opts_dict['exclude_book_marker'] = "%s:%s" % (self.exclude_source_field_name, + self.exclude_pattern.text()) - # Generate read_book_marker - opts_dict['read_book_marker'] = "%s:%s" % (self.read_source_field, self.read_pattern.text()) + # Generate specs for merge_comments, header_note_source_field + checked = '' + if self.merge_before.isChecked(): + checked = 'before' + elif self.merge_after.isChecked(): + checked = 'after' + include_hr = self.include_hr.isChecked() + opts_dict['merge_comments'] = "%s:%s:%s" % \ + (self.merge_source_field_name, checked, include_hr) + + opts_dict['header_note_source_field'] = self.header_note_source_field_name # Append the output profile opts_dict['output_profile'] = [load_defaults('page_setup')['output_profile']] + if False: + print "opts_dict" + for opt in sorted(opts_dict.keys()): + print " %s: %s" % (opt, repr(opts_dict[opt])) return opts_dict + def populateComboBoxes(self): + # Custom column types declared in + # gui2.preferences.create_custom_column:CreateCustomColumn() + # As of 0.7.34: + # bool Yes/No + # comments Long text, like comments, not shown in tag browser + # composite Column built from other columns + # datetime Date + # enumeration Text, but with a fixed set of permitted values + # float Floating point numbers + # int Integers + # rating Ratings, shown with stars + # series Text column for keeping series-like information + # text Column shown in the tag browser + # *text Comma-separated text, like tags, shown in tag browser + + all_custom_fields = self.db.custom_field_keys() + # Populate the 'Read book' hybrid + custom_fields = {} + custom_fields['Tag'] = {'field':'tag', 'datatype':u'text'} + for custom_field in all_custom_fields: + field_md = self.db.metadata_for_field(custom_field) + if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']: + custom_fields[field_md['name']] = {'field':custom_field, + 'datatype':field_md['datatype']} + # Add the sorted eligible fields to the combo box + for cf in sorted(custom_fields): + self.read_source_field.addItem(cf) + self.read_source_fields = custom_fields + self.read_source_field.currentIndexChanged.connect(self.read_source_field_changed) + + + # Populate the 'Excluded books' hybrid + custom_fields = {} + for custom_field in all_custom_fields: + field_md = self.db.metadata_for_field(custom_field) + if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']: + custom_fields[field_md['name']] = {'field':custom_field, + 'datatype':field_md['datatype']} + # Blank field first + self.exclude_source_field.addItem('') + # Add the sorted eligible fields to the combo box + for cf in sorted(custom_fields): + self.exclude_source_field.addItem(cf) + self.exclude_source_fields = custom_fields + self.exclude_source_field.currentIndexChanged.connect(self.exclude_source_field_changed) + + + # Populate the 'Header note' combo box + custom_fields = {} + for custom_field in all_custom_fields: + field_md = self.db.metadata_for_field(custom_field) + if field_md['datatype'] in ['composite','datetime','enumeration','text']: + custom_fields[field_md['name']] = {'field':custom_field, + 'datatype':field_md['datatype']} + # Blank field first + self.header_note_source_field.addItem('') + # Add the sorted eligible fields to the combo box + for cf in sorted(custom_fields): + self.header_note_source_field.addItem(cf) + self.header_note_source_fields = custom_fields + self.header_note_source_field.currentIndexChanged.connect(self.header_note_source_field_changed) + + + # Populate the 'Merge with Comments' combo box + custom_fields = {} + for custom_field in all_custom_fields: + field_md = self.db.metadata_for_field(custom_field) + if field_md['datatype'] in ['text','comments']: + custom_fields[field_md['name']] = {'field':custom_field, + 'datatype':field_md['datatype']} + # Blank field first + self.merge_source_field.addItem('') + # Add the sorted eligible fields to the combo box + for cf in sorted(custom_fields): + self.merge_source_field.addItem(cf) + self.merge_source_fields = custom_fields + self.merge_source_field.currentIndexChanged.connect(self.merge_source_field_changed) + self.merge_before.setEnabled(False) + self.merge_after.setEnabled(False) + self.include_hr.setEnabled(False) + def read_source_field_changed(self,new_index): ''' Process changes in the read_source_field combo box Currently using QLineEdit for all field types Possible to modify to switch QWidget type ''' - new_source = str(self.read_source_field_cb.currentText()) + new_source = str(self.read_source_field.currentText()) read_source_spec = self.read_source_fields[str(new_source)] - self.read_source_field = read_source_spec['field'] + self.read_source_field_name = read_source_spec['field'] # Change pattern input widget to match the source field datatype if read_source_spec['datatype'] in ['bool','composite','datetime','text']: @@ -152,3 +287,62 @@ class PluginWidget(QWidget,Ui_Form): self.read_pattern = dw self.read_spec_hl.addWidget(dw) + def exclude_source_field_changed(self,new_index): + ''' + Process changes in the exclude_source_field combo box + Currently using QLineEdit for all field types + Possible to modify to switch QWidget type + ''' + new_source = str(self.exclude_source_field.currentText()) + self.exclude_source_field_name = new_source + if new_source > '': + exclude_source_spec = self.exclude_source_fields[str(new_source)] + self.exclude_source_field_name = exclude_source_spec['field'] + + # Change pattern input widget to match the source field datatype + if exclude_source_spec['datatype'] in ['bool','composite','datetime','text']: + if not isinstance(self.exclude_pattern, QLineEdit): + self.exclude_spec_hl.removeWidget(self.exclude_pattern) + dw = QLineEdit(self) + dw.setObjectName('exclude_pattern') + dw.setToolTip('Exclusion pattern') + self.exclude_pattern = dw + self.exclude_spec_hl.addWidget(dw) + else: + self.exclude_pattern.setText('') + + def header_note_source_field_changed(self,new_index): + ''' + Process changes in the header_note_source_field combo box + ''' + new_source = str(self.header_note_source_field.currentText()) + self.header_note_source_field_name = new_source + if new_source > '': + header_note_source_spec = self.header_note_source_fields[str(new_source)] + self.header_note_source_field_name = header_note_source_spec['field'] + + def merge_source_field_changed(self,new_index): + ''' + Process changes in the header_note_source_field combo box + ''' + new_source = str(self.merge_source_field.currentText()) + self.merge_source_field_name = new_source + if new_source > '': + merge_source_spec = self.merge_source_fields[str(new_source)] + self.merge_source_field_name = merge_source_spec['field'] + if not self.merge_before.isChecked() and not self.merge_after.isChecked(): + self.merge_after.setChecked(True) + self.merge_before.setEnabled(True) + self.merge_after.setEnabled(True) + self.include_hr.setEnabled(True) + + else: + self.merge_before.setEnabled(False) + self.merge_after.setEnabled(False) + self.include_hr.setEnabled(False) + + def thumb_width_changed(self,new_value): + ''' + Process changes in the thumb_width spin box + ''' + pass diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.ui b/src/calibre/gui2/catalog/catalog_epub_mobi.ui index d72566f581..481763c76d 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.ui +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.ui @@ -6,163 +6,653 @@ 0 0 - 627 - 549 + 650 + 582 + + + 0 + 0 + + Form - - - - - 'Don't include this book' tag: + + + + + + 0 + 0 + - - - - - - - - - - - - - Additional note tag prefix: - - - - - - - - - - - - - - - - - - - - - Regex pattern describing tags to exclude as genres: - - - Qt::LogText - - - true - - - - - - - Regex tips: -- The default regex - \[.+\] - excludes genre tags of the form [tag], e.g., [Amazon Freebie] -- A regex pattern of a single dot excludes all genre tags, generating no Genre Section - - - true - - - - - - - Qt::Vertical - - + - 20 - 40 + 0 + 0 - - - - - - Include 'Titles' Section + + Sections to include in generated catalog. A minimal catalog includes 'Books by Author'. + + Included sections (Books by Author included by default) + + + + + + Books by Title + + + + + + + Books by Series + + + + + + + Recently Added + + + + + + + Books by Genre + + + + + + + Descriptions + + + + - - - - Include 'Recently Added' Section + + + + + 0 + 0 + + + + 0 + 0 + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Default pattern </p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">\[.+\]</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">excludes tags of the form [<span style=" font-style:italic;">tag</span>]</p></body></html> + + + Excluded genres + + + + QFormLayout::FieldsStayAtSizeHint + + + + + -1 + + + 0 + + + + + + 175 + 0 + + + + + 200 + 16777215 + + + + Tags to exclude + + + Qt::AutoText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + + + - - - - Sort numbers as text + + + + + 0 + 0 + + + + 0 + 0 + + + + Exclude matching books from generated catalog + + + Excluded books + + + + + + + + + 0 + 0 + + + + + 175 + 0 + + + + + 200 + 16777215 + + + + Tags to exclude + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:12pt;">Comma-separated list of tags to exclude.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:12pt;">Default:</span><span style=" font-family:'Courier New,courier'; font-size:12pt;"> ~,Catalog</span></p></body></html> + + + + + + + + + + + + 175 + 0 + + + + + 200 + 16777215 + + + + Column/value + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + Column containing exclusion criteria + + + QComboBox::AdjustToMinimumContentsLengthWithIcon + + + 18 + + + + + + + + 150 + 0 + + + + Exclusion pattern + + + + + + - - - - Include 'Series' Section + + + + + 0 + 0 + + + + 0 + 0 + + + + Matching books will be displayed with ✓ + + + Read books + + + + + + QLayout::SetDefaultConstraint + + + + + + 175 + 0 + + + + + 200 + 16777215 + + + + Column/value + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + Column containing 'read' status + + + + + + QComboBox::AdjustToMinimumContentsLengthWithIcon + + + 18 + + + + + + + + 150 + 0 + + + + 'read book' pattern + + + + + + + + + - - - - - - - Wishlist tag: + + + + + 0 + 0 + - - - - - - QLayout::SetMinimumSize + + + 0 + 0 + - - - - - 0 - 0 - - - - Source column for read book - - - - - - - - - - Pattern for read book - - - - - - - - - - - - Books marked as read: + + Other options + + + QFormLayout::FieldsStayAtSizeHint + + + + + + + + 175 + 0 + + + + + 200 + 16777215 + + + + + + + Wishlist tag + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + + + + + Wishlist items will be displayed with ✕ + + + + + + + + + + + + 175 + 0 + + + + + 200 + 16777215 + + + + Thumbnail width + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + + + + + + 0 + 0 + + + + Size hint for cover thumbnails included in Descriptions + + + " + + + 2 + + + 1.000000000000000 + + + 2.000000000000000 + + + 0.100000000000000 + + + + + + + + + + + + 0 + 0 + + + + + 175 + 0 + + + + + 200 + 16777215 + + + + Header note + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Column containing header note + + + + + + + + + + + + 175 + 0 + + + + + 200 + 16777215 + + + + Merge with Comments + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Column containing additional content to merge + + + + + + + Qt::Vertical + + + + + + + Merge before Comments + + + Before + + + + + + + Merge after Comments + + + After + + + + + + + Qt::Vertical + + + + + + + Separate with horizontal rule + + + <hr /> + + + + + + diff --git a/src/calibre/gui2/catalog/catalog_tab_template.ui b/src/calibre/gui2/catalog/catalog_tab_template.ui index 5df881beac..4b24507f80 100644 --- a/src/calibre/gui2/catalog/catalog_tab_template.ui +++ b/src/calibre/gui2/catalog/catalog_tab_template.ui @@ -6,8 +6,8 @@ 0 0 - 579 - 411 + 650 + 575 diff --git a/src/calibre/gui2/dialogs/catalog.ui b/src/calibre/gui2/dialogs/catalog.ui index e1de9407ea..62ac7cb5af 100644 --- a/src/calibre/gui2/dialogs/catalog.ui +++ b/src/calibre/gui2/dialogs/catalog.ui @@ -6,8 +6,8 @@ 0 0 - 611 - 514 + 674 + 660 @@ -33,6 +33,18 @@ + + + 0 + 0 + + + + + 650 + 575 + + 0 diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 0b317d6a6e..5eb20159c1 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -550,6 +550,13 @@ class EPUB_MOBI(CatalogPlugin): "of the conversion process a bug is occurring.\n" "Default: '%default'None\n" "Applies to: ePub, MOBI output formats")), + Option('--exclude-book-marker', + default=':', + dest='exclude_book_marker', + action = None, + help=_("field:pattern specifying custom field/contents indicating book should be excluded.\n" + "Default: '%default'\n" + "Applies to ePub, MOBI output formats")), Option('--exclude-genre', default='\[.+\]', dest='exclude_genre', @@ -585,6 +592,23 @@ class EPUB_MOBI(CatalogPlugin): help=_("Include 'Recently Added' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), + Option('--header-note-source-field', + default='', + dest='header_note_source_field', + action = None, + help=_("Custom field containing note text to insert in Description header.\n" + "Default: '%default'\n" + "Applies to: ePub, MOBI output formats")), + Option('--merge-comments', + default='::', + dest='merge_comments', + action = None, + help=_(":[before|after]:[True|False] specifying:\n" + " Custom field containing notes to merge with Comments\n" + " [before|after] Placement of notes with respect to Comments\n" + " [True|False] - A horizontal rule is inserted between notes and Comments\n" + "Default: '%default'\n" + "Applies to ePub, MOBI output formats")), Option('--note-tag', default='*', dest='note_tag', @@ -845,6 +869,7 @@ class EPUB_MOBI(CatalogPlugin): catalog.copyResources() catalog.buildSources() ''' + # A single number creates 'Last x days' only. # Multiple numbers create 'Last x days', 'x to y days ago' ... # e.g, [7,15,30,60], [30] @@ -889,6 +914,7 @@ class EPUB_MOBI(CatalogPlugin): and self.generateForKindle \ else False self.__genres = None + self.genres = [] self.__genre_tags_dict = None self.__htmlFileList = [] self.__markerTags = self.getMarkerTags() @@ -900,13 +926,15 @@ class EPUB_MOBI(CatalogPlugin): self.__progressString = '' f, _, p = opts.read_book_marker.partition(':') self.__read_book_marker = {'field':f, 'pattern':p} + f, p, hr = self.opts.merge_comments.split(':') + self.__merge_comments = {'field':f, 'position':p, 'hr':hr} self.__reporter = report_progress self.__stylesheet = stylesheet self.__thumbs = None self.__thumbWidth = 0 self.__thumbHeight = 0 self.__title = opts.catalog_title - self.__totalSteps = 11.0 + self.__totalSteps = 8.0 self.__useSeriesPrefixInTitlesSection = False self.__verbose = opts.verbose @@ -916,17 +944,36 @@ class EPUB_MOBI(CatalogPlugin): self.__output_profile = profile break - # Confirm/create thumbs archive + # Confirm/create thumbs archive. if not os.path.exists(self.__cache_dir): self.opts.log.info(" creating new thumb cache '%s'" % self.__cache_dir) os.makedirs(self.__cache_dir) if not os.path.exists(self.__archive_path): - self.opts.log.info(" creating thumbnail archive") + self.opts.log.info(' creating thumbnail archive, thumb_width: %1.2f"' % + float(self.opts.thumb_width)) zfw = ZipFile(self.__archive_path, mode='w') zfw.writestr("Catalog Thumbs Archive",'') + zfw.comment = "thumb_width: %1.2f" % float(self.opts.thumb_width) zfw.close() else: - self.opts.log.info(" existing thumb cache at '%s'" % self.__archive_path) + with closing(ZipFile(self.__archive_path, mode='r')) as zfr: + try: + cached_thumb_width = float(zfr.comment[len('thumb_width: '):]) + except: + cached_thumb_width = "0.0" + + if float(cached_thumb_width) != float(self.opts.thumb_width): + self.opts.log.info(" invalidating cache at '%s'" % self.__archive_path) + self.opts.log.info(' thumb_width: %1.2f" => %1.2f"' % + (float(cached_thumb_width),float(self.opts.thumb_width))) + os.remove(self.__archive_path) + zfw = ZipFile(self.__archive_path, mode='w') + zfw.writestr("Catalog Thumbs Archive",'') + zfw.comment = "thumb_width: %1.2f" % float(self.opts.thumb_width) + zfw.close() + else: + self.opts.log.info(' existing thumb cache at %s, cached_thumb_width: %1.2f"' % + (self.__archive_path, float(cached_thumb_width))) # Tweak build steps based on optional sections: 1 call for HTML, 1 for NCX if self.opts.generate_titles: @@ -937,6 +984,9 @@ class EPUB_MOBI(CatalogPlugin): self.__totalSteps += 2 if self.opts.generate_series: self.__totalSteps += 2 + if self.opts.generate_descriptions: + # +1 thumbs + self.__totalSteps += 3 # Accessors if True: @@ -1246,7 +1296,8 @@ class EPUB_MOBI(CatalogPlugin): return False self.fetchBooksByAuthor() self.fetchBookmarks() - self.generateHTMLDescriptions() + if self.opts.generate_descriptions: + self.generateHTMLDescriptions() self.generateHTMLByAuthor() if self.opts.generate_titles: self.generateHTMLByTitle() @@ -1256,10 +1307,10 @@ class EPUB_MOBI(CatalogPlugin): self.generateHTMLByDateAdded() if self.generateRecentlyRead: self.generateHTMLByDateRead() - self.generateHTMLByTags() - - self.generateThumbnails() - + if self.opts.generate_genres: + self.generateHTMLByTags() + if self.opts.generate_descriptions: + self.generateThumbnails() self.generateOPF() self.generateNCXHeader() self.generateNCXByAuthor("Authors") @@ -1271,8 +1322,11 @@ class EPUB_MOBI(CatalogPlugin): self.generateNCXByDateAdded("Recently Added") if self.generateRecentlyRead: self.generateNCXByDateRead("Recently Read") - self.generateNCXByGenre("Genres") - self.generateNCXDescriptions("Descriptions") + if self.opts.generate_genres: + self.generateNCXByGenre("Genres") + if self.opts.generate_descriptions: + self.generateNCXDescriptions("Descriptions") + self.writeNCX() return True @@ -1340,6 +1394,7 @@ class EPUB_MOBI(CatalogPlugin): #print "fetchBooksByTitle(): opts.search_text: %s" % self.opts.search_text # Fetch the database as a dictionary data = self.plugin.search_sort_db(self.db, self.opts) + data = self.processExclusions(data) # Populate this_title{} from data[{},{}] titles = [] @@ -1388,6 +1443,8 @@ class EPUB_MOBI(CatalogPlugin): record['comments'] = record['comments'][:ad_offset] this_title['description'] = self.markdownComments(record['comments']) + + # Create short description paras = BeautifulSoup(this_title['description']).findAll('p') tokens = [] for p in paras: @@ -1399,6 +1456,10 @@ class EPUB_MOBI(CatalogPlugin): this_title['description'] = None this_title['short_description'] = None + # Merge with custom field/value + if self.__merge_comments['field']: + this_title['description'] = self.mergeComments(this_title) + if record['cover']: this_title['cover'] = re.sub('&', '&', record['cover']) @@ -1413,6 +1474,14 @@ class EPUB_MOBI(CatalogPlugin): formats.append(self.convertHTMLEntities(format)) this_title['formats'] = formats + # Add user notes to be displayed in header + if self.opts.header_note_source_field: + notes = self.__db.get_field(record['id'], + self.opts.header_note_source_field, + index_is_id=True) + if notes: + this_title['notes'] = notes + titles.append(this_title) # Re-sort based on title_sort @@ -1712,7 +1781,8 @@ class EPUB_MOBI(CatalogPlugin): for tag in title.get('tags', []): aTag = Tag(soup,'a') #print "aTag: %s" % "Genre_%s.html" % re.sub("\W","",tag.lower()) - aTag['href'] = "Genre_%s.html" % re.sub("\W","",tag.lower()) + if self.opts.generate_genres: + aTag['href'] = "Genre_%s.html" % re.sub("\W","",tag.lower()) aTag.insert(0,escape(NavigableString(tag))) emTag = Tag(soup, "em") emTag.insert(0, aTag) @@ -1771,7 +1841,6 @@ class EPUB_MOBI(CatalogPlugin): #ratingLabel = body.find('td',text="Rating").replaceWith("Unrated") ratingTag.insert(0,NavigableString('
')) - # Insert user notes or remove Notes label. Notes > 1 line will push formatting down if 'notes' in title: notesTag = body.find(attrs={'class':'notes'}) @@ -1894,7 +1963,8 @@ class EPUB_MOBI(CatalogPlugin): # Link to book aTag = Tag(soup, "a") - aTag['href'] = "book_%d.html" % (int(float(book['id']))) + if self.opts.generate_descriptions: + aTag['href'] = "book_%d.html" % (int(float(book['id']))) aTag.insert(0,escape(book['title'])) pBookTag.insert(ptc, aTag) ptc += 1 @@ -2067,8 +2137,9 @@ class EPUB_MOBI(CatalogPlugin): ptc += 1 aTag = Tag(soup, "a") - aTag['href'] = "book_%d.html" % (int(float(book['id']))) - # Use series, series index if avail else just title, + year of publication + if self.opts.generate_descriptions: + aTag['href'] = "book_%d.html" % (int(float(book['id']))) + # Use series, series index if avail else title, + year of publication if current_series: aTag.insert(0,'%s (%s)' % (escape(book['title'][len(book['series'])+1:]), book['date'].split()[1])) @@ -2079,7 +2150,6 @@ class EPUB_MOBI(CatalogPlugin): pBookTag.insert(ptc, aTag) ptc += 1 - divTag.insert(dtc, pBookTag) dtc += 1 @@ -2200,7 +2270,8 @@ class EPUB_MOBI(CatalogPlugin): ptc += 1 aTag = Tag(soup, "a") - aTag['href'] = "book_%d.html" % (int(float(new_entry['id']))) + if self.opts.generate_descriptions: + aTag['href'] = "book_%d.html" % (int(float(new_entry['id']))) if current_series: aTag.insert(0,escape(new_entry['title'][len(new_entry['series'])+1:])) else: @@ -2251,7 +2322,8 @@ class EPUB_MOBI(CatalogPlugin): ptc += 1 aTag = Tag(soup, "a") - aTag['href'] = "book_%d.html" % (int(float(new_entry['id']))) + if self.opts.generate_descriptions: + aTag['href'] = "book_%d.html" % (int(float(new_entry['id']))) aTag.insert(0,escape(new_entry['title'])) pBookTag.insert(ptc, aTag) ptc += 1 @@ -2411,7 +2483,8 @@ class EPUB_MOBI(CatalogPlugin): ptc += 1 aTag = Tag(soup, "a") - aTag['href'] = "book_%d.html" % (int(float(new_entry['id']))) + if self.opts.generate_descriptions: + aTag['href'] = "book_%d.html" % (int(float(new_entry['id']))) aTag.insert(0,escape(new_entry['title'])) pBookTag.insert(ptc, aTag) ptc += 1 @@ -2458,7 +2531,8 @@ class EPUB_MOBI(CatalogPlugin): ptc += 1 aTag = Tag(soup, "a") - aTag['href'] = "book_%d.html" % (int(float(new_entry['id']))) + if self.opts.generate_descriptions: + aTag['href'] = "book_%d.html" % (int(float(new_entry['id']))) aTag.insert(0,escape(new_entry['title'])) pBookTag.insert(ptc, aTag) ptc += 1 @@ -2699,7 +2773,8 @@ class EPUB_MOBI(CatalogPlugin): ptc += 1 aTag = Tag(soup, "a") - aTag['href'] = "book_%d.html" % (int(float(book['id']))) + if self.opts.generate_descriptions: + aTag['href'] = "book_%d.html" % (int(float(book['id']))) # Use series, series index if avail else just title #aTag.insert(0,'%d. %s · %s' % (book['series_index'],escape(book['title']), ' & '.join(book['authors']))) @@ -2983,18 +3058,20 @@ class EPUB_MOBI(CatalogPlugin): manifest.insert(mtc, itemTag) mtc += 1 - # Write the thumbnail images to the manifest - for thumb in self.thumbs: - itemTag = Tag(soup, "item") - itemTag['href'] = "images/%s" % (thumb) - end = thumb.find('.jpg') - itemTag['id'] = "%s-image" % thumb[:end] - itemTag['media-type'] = 'image/jpeg' - manifest.insert(mtc, itemTag) - mtc += 1 + # Write the thumbnail images, descriptions to the manifest + sort_descriptions_by = [] + if self.opts.generate_descriptions: + for thumb in self.thumbs: + itemTag = Tag(soup, "item") + itemTag['href'] = "images/%s" % (thumb) + end = thumb.find('.jpg') + itemTag['id'] = "%s-image" % thumb[:end] + itemTag['media-type'] = 'image/jpeg' + manifest.insert(mtc, itemTag) + mtc += 1 - # HTML files - add books to manifest and spine - sort_descriptions_by = self.booksByAuthor if self.opts.sort_descriptions_by_author \ + # HTML files - add descriptions to manifest and spine + sort_descriptions_by = self.booksByAuthor if self.opts.sort_descriptions_by_author \ else self.booksByTitle # Add html_files to manifest and spine @@ -3970,15 +4047,15 @@ class EPUB_MOBI(CatalogPlugin): from calibre.customize.ui import output_profiles for x in output_profiles(): if x.short_name == self.opts.output_profile: - # .9" width aspect ratio: 3:4 - self.thumbWidth = int(x.dpi * 1) - self.thumbHeight = int(self.thumbWidth * 1.33) + # aspect ratio: 3:4 + self.thumbWidth = x.dpi * float(self.opts.thumb_width) + self.thumbHeight = self.thumbWidth * 1.33 if 'kindle' in x.short_name and self.opts.fmt == 'mobi': # Kindle DPI appears to be off by a factor of 2 - self.thumbWidth = int(self.thumbWidth/2) - self.thumbHeight = int(self.thumbHeight/2) + self.thumbWidth = self.thumbWidth/2 + self.thumbHeight = self.thumbHeight/2 break - if False and self.verbose: + if True and self.verbose: self.opts.log(" DPI = %d; thumbnail dimensions: %d x %d" % \ (x.dpi, self.thumbWidth, self.thumbHeight)) @@ -4238,7 +4315,8 @@ class EPUB_MOBI(CatalogPlugin): # Add the book title aTag = Tag(soup, "a") - aTag['href'] = "book_%d.html" % (int(float(book['id']))) + if self.opts.generate_descriptions: + aTag['href'] = "book_%d.html" % (int(float(book['id']))) # Use series, series index if avail else just title if current_series: aTag.insert(0,escape(book['title'][len(book['series'])+1:])) @@ -4460,7 +4538,9 @@ class EPUB_MOBI(CatalogPlugin): # Leading numbers optionally translated to text equivalent # Capitalize leading sort word if i==0: - if self.opts.numbers_as_text and re.match('[0-9]+',word[0]): + # *** Keep this code in case we need to restore numbers_as_text *** + if False: + #if self.opts.numbers_as_text and re.match('[0-9]+',word[0]): translated.append(EPUB_MOBI.NumberToText(word).text.capitalize()) else: if re.match('[0-9]+',word[0]): @@ -4540,7 +4620,6 @@ class EPUB_MOBI(CatalogPlugin): ''' Return a list of special marker tags to be excluded from genre list ''' markerTags = [] markerTags.extend(self.opts.exclude_tags.split(',')) - markerTags.extend(self.opts.note_tag.split(',')) return markerTags def letter_or_symbol(self,char): @@ -4663,13 +4742,63 @@ class EPUB_MOBI(CatalogPlugin): return result.renderContents(encoding=None) + def mergeComments(self, record): + ''' + merge ['description'] with custom field contents to be displayed in Descriptions + ''' + merged = '' + if record['description']: + addendum = self.__db.get_field(record['id'], + self.__merge_comments['field'], + index_is_id=True) + include_hr = eval(self.__merge_comments['hr']) + if self.__merge_comments['position'] == 'before': + merged = addendum + if include_hr: + merged += '
' + else: + merged += '\n' + merged += record['description'] + else: + merged = record['description'] + if include_hr: + merged += '
' + else: + merged += '\n' + merged += addendum + else: + # Return the custom field contents + merged = self.__db.get_field(record['id'], + self.__merge_comments['field'], + index_is_id=True) + + return merged + + def processExclusions(self, data_set): + ''' + Remove excluded entries + ''' + field, pat = self.opts.exclude_book_marker.split(':') + if pat == '': + return data_set + filtered_data_set = [] + for record in data_set: + field_contents = self.__db.get_field(record['id'], + field, + index_is_id=True) + if field_contents: + if re.search(pat, unicode(field_contents), + re.IGNORECASE) is not None: + continue + filtered_data_set.append(record) + + return filtered_data_set + def processSpecialTags(self, tags, this_title, opts): tag_list = [] for tag in tags: tag = self.convertHTMLEntities(tag) - if tag.startswith(opts.note_tag): - this_title['notes'] = tag[len(self.opts.note_tag):] - elif re.search(opts.exclude_genre, tag): + if re.search(opts.exclude_genre, tag): continue elif self.__read_book_marker['field'] == 'tag' and \ tag == self.__read_book_marker['pattern']: @@ -4767,13 +4896,16 @@ class EPUB_MOBI(CatalogPlugin): if opts_dict['ids']: build_log.append(" book count: %d" % len(opts_dict['ids'])) - sections_list = ['Descriptions','Authors'] + sections_list = ['Authors'] if opts.generate_titles: sections_list.append('Titles') if opts.generate_recently_added: sections_list.append('Recently Added') - if not opts.exclude_genre.strip() == '.': + if opts.generate_genres: sections_list.append('Genres') + if opts.generate_descriptions: + sections_list.append('Descriptions') + build_log.append(u" Sections: %s" % ', '.join(sections_list)) # Display opts @@ -4782,11 +4914,12 @@ class EPUB_MOBI(CatalogPlugin): build_log.append(" opts:") for key in keys: if key in ['catalog_title','authorClip','connected_kindle','descriptionClip', - 'exclude_genre','exclude_tags','note_tag','numbers_as_text', + 'exclude_book_marker','exclude_genre','exclude_tags', + 'header_note_source_field','merge_comments', 'output_profile','read_book_marker', 'search_text','sort_by','sort_descriptions_by_author','sync', - 'wishlist_tag']: - build_log.append(" %s: %s" % (key, opts_dict[key])) + 'thumb_width','wishlist_tag']: + build_log.append(" %s: %s" % (key, repr(opts_dict[key]))) if opts.verbose: log('\n'.join(line for line in build_log)) @@ -4801,6 +4934,7 @@ class EPUB_MOBI(CatalogPlugin): catalog.copyResources() catalog.calculateThumbnailSize() catalog_source_built = catalog.buildSources() + if opts.verbose: if catalog_source_built: log.info(" Completed catalog source generation\n") From a76a09d6338bc85b084e57402dae4879c5816457 Mon Sep 17 00:00:00 2001 From: GRiker Date: Thu, 23 Dec 2010 17:15:17 -0700 Subject: [PATCH 2/8] GwR wip --- src/calibre/library/catalog.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 5eb20159c1..eb85ad9e6e 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -106,6 +106,9 @@ class CSV_XML(CatalogPlugin): if self.fmt == 'csv': outfile = codecs.open(path_to_output, 'w', 'utf8') + # Write a UTF-8 BOM + outfile.write('\xef\xbb\xbf') + # Output the field headers outfile.write(u'%s\n' % u','.join(fields)) @@ -132,7 +135,6 @@ class CSV_XML(CatalogPlugin): elif field == 'comments': item = item.replace(u'\r\n',u' ') item = item.replace(u'\n',u' ') - outstr.append(u'"%s"' % unicode(item).replace('"','""')) outfile.write(u','.join(outstr) + u'\n') @@ -953,24 +955,22 @@ class EPUB_MOBI(CatalogPlugin): float(self.opts.thumb_width)) zfw = ZipFile(self.__archive_path, mode='w') zfw.writestr("Catalog Thumbs Archive",'') - zfw.comment = "thumb_width: %1.2f" % float(self.opts.thumb_width) + #zfw.comment = "thumb_width: %1.2f" % float(self.opts.thumb_width) zfw.close() else: with closing(ZipFile(self.__archive_path, mode='r')) as zfr: try: - cached_thumb_width = float(zfr.comment[len('thumb_width: '):]) + cached_thumb_width = zfr.read('thumb_width') except: - cached_thumb_width = "0.0" + cached_thumb_width = "-1" if float(cached_thumb_width) != float(self.opts.thumb_width): self.opts.log.info(" invalidating cache at '%s'" % self.__archive_path) self.opts.log.info(' thumb_width: %1.2f" => %1.2f"' % (float(cached_thumb_width),float(self.opts.thumb_width))) os.remove(self.__archive_path) - zfw = ZipFile(self.__archive_path, mode='w') - zfw.writestr("Catalog Thumbs Archive",'') - zfw.comment = "thumb_width: %1.2f" % float(self.opts.thumb_width) - zfw.close() + with closing(ZipFile(self.__archive_path, mode='w')) as zfw: + zfw.writestr("Catalog Thumbs Archive",'') else: self.opts.log.info(' existing thumb cache at %s, cached_thumb_width: %1.2f"' % (self.__archive_path, float(cached_thumb_width))) @@ -2997,6 +2997,11 @@ class EPUB_MOBI(CatalogPlugin): title['cover'] = cover self.generateThumbnail(title, image_dir, "thumbnail_default.jpg") + # Write the thumb_width to the file validating cache contents + # Allows detection of aborted catalog builds + with closing(ZipFile(self.__archive_path, mode='a'))as zfw: + zfw.writestr('thumb_width', self.opts.thumb_width) + self.thumbs = thumbs def generateOPF(self): From ac3c3ec086c8c2f9e7a59dc486e30924970d03e1 Mon Sep 17 00:00:00 2001 From: GRiker Date: Sat, 25 Dec 2010 11:16:05 -0700 Subject: [PATCH 3/8] GwR catalog updates wip --- resources/catalog/stylesheet.css | 44 +- resources/catalog/template.xhtml | 41 ++ src/calibre/customize/__init__.py | 18 +- src/calibre/gui2/catalog/catalog_csv_xml.py | 11 +- src/calibre/gui2/catalog/catalog_epub_mobi.py | 5 +- src/calibre/gui2/catalog/catalog_epub_mobi.ui | 30 +- src/calibre/library/catalog.py | 567 +++++++++--------- 7 files changed, 411 insertions(+), 305 deletions(-) create mode 100644 resources/catalog/template.xhtml diff --git a/resources/catalog/stylesheet.css b/resources/catalog/stylesheet.css index 07a95661be..dd25566b22 100644 --- a/resources/catalog/stylesheet.css +++ b/resources/catalog/stylesheet.css @@ -2,19 +2,29 @@ body { background-color: white; } p.title { margin-top:0em; - margin-bottom:1em; + margin-bottom:0em; text-align:center; font-style:italic; font-size:xx-large; - border-bottom: solid black 2px; + } + +p.series_id { + margin-top:0em; + margin-bottom:0em; + text-align:center; + } + +a.series_id { + font-style:normal; + font-size:large; } p.author { + font-size:large; margin-top:0em; margin-bottom:0em; text-align: center; text-indent: 0em; - font-size:large; } p.author_index { @@ -26,7 +36,8 @@ p.author_index { text-indent: 0em; } -p.tags { +p.genres { + font-style:normal; margin-top:0.5em; margin-bottom:0em; text-align: left; @@ -124,22 +135,37 @@ hr.description_divider { border-left: solid white 0px; } +hr.header_divider { + width:100%; + border-top: solid white 1px; + border-right: solid white 0px; + border-bottom: solid black 2px; + border-left: solid white 0px; + } + hr.merged_comments_divider { width:80%; margin-left:10%; border-top: solid white 0px; border-right: solid white 0px; - border-bottom: dotted grey 2px; + border-bottom: dashed gray 2px; border-left: solid white 0px; } -td.publisher, td.date { - font-weight:bold; +td { text-align:center; } -td.rating, td.notes { - text-align: center; +td.publisher, td.date { + font-weight:bold; } + +td.rating{ + } + +td.notes { + font-size: 100%; + } + td.thumbnail img { -webkit-box-shadow: 4px 4px 12px #999; } \ No newline at end of file diff --git a/resources/catalog/template.xhtml b/resources/catalog/template.xhtml new file mode 100644 index 0000000000..409086d343 --- /dev/null +++ b/resources/catalog/template.xhtml @@ -0,0 +1,41 @@ + + + {title_str} + + + + +

{title}

+

{series} [{series_index}]

+
+

{author_prefix}{author}

+

{genres}

+

{formats}

+ + + + + + + + + + + + + + + + + + + + + + + +
 
 
{publisher}
{pubyear}
{rating}
{note_source}: {note_content}
 
+
+
+ + diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index a76cb71acd..770d405203 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -307,6 +307,14 @@ class CatalogPlugin(Plugin): # {{{ #: cli_options parsed in library.cli:catalog_option_parser() cli_options = [] + def _field_sorter(self, key): + ''' + Custom fields sort after standard fields + ''' + if key.startswith('#'): + return '~%s' % key[1:] + else: + return key def search_sort_db(self, db, opts): @@ -315,18 +323,18 @@ class CatalogPlugin(Plugin): # {{{ if opts.sort_by: # 2nd arg = ascending db.sort(opts.sort_by, True) - return db.get_data_as_dict(ids=opts.ids) - def get_output_fields(self, opts): + def get_output_fields(self, db, opts): # Return a list of requested fields, with opts.sort_by first - all_fields = set( + all_std_fields = set( ['author_sort','authors','comments','cover','formats', 'id','isbn','ondevice','pubdate','publisher','rating', 'series_index','series','size','tags','timestamp', 'title','uuid']) + all_custom_fields = set(db.custom_field_keys()) + all_fields = all_std_fields.union(all_custom_fields) - fields = all_fields if opts.fields != 'all': # Make a list from opts.fields requested_fields = set(opts.fields.split(',')) @@ -337,7 +345,7 @@ class CatalogPlugin(Plugin): # {{{ if not opts.connected_device['is_device_connected'] and 'ondevice' in fields: fields.pop(int(fields.index('ondevice'))) - fields.sort() + fields = sorted(fields, key=self._field_sorter) if opts.sort_by and opts.sort_by in fields: fields.insert(0,fields.pop(int(fields.index(opts.sort_by)))) return fields diff --git a/src/calibre/gui2/catalog/catalog_csv_xml.py b/src/calibre/gui2/catalog/catalog_csv_xml.py index 077d4cbbca..eeca5f7f72 100644 --- a/src/calibre/gui2/catalog/catalog_csv_xml.py +++ b/src/calibre/gui2/catalog/catalog_csv_xml.py @@ -6,9 +6,11 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' - +import os from calibre.gui2 import gprefs from calibre.gui2.catalog.catalog_csv_xml_ui import Ui_Form +from calibre.library.database2 import LibraryDatabase2 +from calibre.utils.config import prefs from PyQt4.Qt import QWidget, QListWidgetItem class PluginWidget(QWidget, Ui_Form): @@ -28,6 +30,13 @@ class PluginWidget(QWidget, Ui_Form): self.all_fields.append(x) QListWidgetItem(x, self.db_fields) + dbpath = os.path.abspath(prefs['library_path']) + db = LibraryDatabase2(dbpath) + for x in sorted(db.custom_field_keys()): + self.all_fields.append(x) + QListWidgetItem(x, self.db_fields) + + def initialize(self, name, db): self.name = name fields = gprefs.get(name+'_db_fields', self.all_fields) diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index f5fabca80e..e3cb8ab5fe 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -237,7 +237,7 @@ class PluginWidget(QWidget,Ui_Form): custom_fields = {} for custom_field in all_custom_fields: field_md = self.db.metadata_for_field(custom_field) - if field_md['datatype'] in ['composite','datetime','enumeration','text']: + if field_md['datatype'] in ['bool','composite','datetime','enumeration','text']: custom_fields[field_md['name']] = {'field':custom_field, 'datatype':field_md['datatype']} # Blank field first @@ -298,6 +298,7 @@ class PluginWidget(QWidget,Ui_Form): if new_source > '': exclude_source_spec = self.exclude_source_fields[str(new_source)] self.exclude_source_field_name = exclude_source_spec['field'] + self.exclude_pattern.setEnabled(True) # Change pattern input widget to match the source field datatype if exclude_source_spec['datatype'] in ['bool','composite','datetime','text']: @@ -309,7 +310,7 @@ class PluginWidget(QWidget,Ui_Form): self.exclude_pattern = dw self.exclude_spec_hl.addWidget(dw) else: - self.exclude_pattern.setText('') + self.exclude_pattern.setEnabled(False) def header_note_source_field_changed(self,new_index): ''' diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.ui b/src/calibre/gui2/catalog/catalog_epub_mobi.ui index 481763c76d..63ccb912c5 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.ui +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.ui @@ -35,10 +35,10 @@ - Sections to include in generated catalog. A minimal catalog includes 'Books by Author'. + Sections to include in catalog. All catalogs include 'Books by Author'. - Included sections (Books by Author included by default) + Included sections @@ -100,7 +100,8 @@ p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Lucida Grande'; font-size:13pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Default pattern </p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier';">\[.+\]</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">excludes tags of the form [<span style=" font-style:italic;">tag</span>]</p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">excludes tags of the form [<span style=" font-family:'Courier New,courier';">tag</span>], </p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">e.g., [Project Gutenberg]</p></body></html> Excluded genres @@ -184,7 +185,7 @@ p, li { white-space: pre-wrap; } - Exclude matching books from generated catalog + Books matching either pattern will not be included in generated catalog. Excluded books @@ -279,7 +280,7 @@ p, li { white-space: pre-wrap; } - Column containing exclusion criteria + Column containing additional exclusion criteria QComboBox::AdjustToMinimumContentsLengthWithIcon @@ -455,7 +456,7 @@ p, li { white-space: pre-wrap; } - Wishlist items will be displayed with ✕ + Books tagged as Wishlist items will be displayed with ✕ @@ -497,7 +498,7 @@ p, li { white-space: pre-wrap; } - Size hint for cover thumbnails included in Descriptions + Size hint for Description cover thumbnails " @@ -540,8 +541,11 @@ p, li { white-space: pre-wrap; } 16777215 + + + - Header note + Description note Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -563,7 +567,7 @@ p, li { white-space: pre-wrap; } - Column containing header note + Custom column source for note to include in Description header area
@@ -602,7 +606,7 @@ p, li { white-space: pre-wrap; } - Column containing additional content to merge + Additional content merged with Comments during catalog generation @@ -616,7 +620,7 @@ p, li { white-space: pre-wrap; } - Merge before Comments + Merge additional content before Comments Before @@ -626,7 +630,7 @@ p, li { white-space: pre-wrap; } - Merge after Comments + Merge additional content after Comments After @@ -643,7 +647,7 @@ p, li { white-space: pre-wrap; } - Separate with horizontal rule + Separate Comments and additional content with horizontal rule <hr /> diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index eb85ad9e6e..168f4968e2 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -1,22 +1,24 @@ # -*- coding: utf-8 -*- __license__ = 'GPL v3' -__copyright__ = '2010, Greg Riker ' +__copyright__ = '2010, Greg Riker' import codecs, datetime, htmlentitydefs, os, re, shutil, time, zlib from contextlib import closing from collections import namedtuple from copy import deepcopy from xml.sax.saxutils import escape +from lxml import etree from calibre import prints, prepare_string_for_xml, strftime from calibre.constants import preferred_encoding from calibre.customize import CatalogPlugin from calibre.customize.conversion import OptionRecommendation, DummyReporter from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString +from calibre.ebooks.oeb.base import RECOVER_PARSER, XHTML_NS from calibre.ptempfile import PersistentTemporaryDirectory from calibre.utils.config import config_dir -from calibre.utils.date import isoformat, now as nowf +from calibre.utils.date import format_date, isoformat, now as nowf from calibre.utils.logging import default_log as log from calibre.utils.zipfile import ZipFile, ZipInfo from calibre.utils.magick.draw import thumbnail @@ -26,6 +28,7 @@ FIELDS = ['all', 'author_sort', 'authors', 'comments', 'series_index', 'series', 'size', 'tags', 'timestamp', 'title', 'uuid'] + #Allowed fields for template TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', 'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ] @@ -96,7 +99,7 @@ class CSV_XML(CatalogPlugin): #raise SystemExit(1) # Get the requested output fields as a list - fields = self.get_output_fields(opts) + fields = self.get_output_fields(db, opts) # If connected device, add 'On Device' values to data if opts.connected_device['is_device_connected'] and 'ondevice' in fields: @@ -116,7 +119,10 @@ class CSV_XML(CatalogPlugin): for entry in data: outstr = [] for field in fields: - item = entry[field] + if field.startswith('#'): + item = db.get_field(entry['id'],field,index_is_id=True) + else: + item = entry[field] if item is None: outstr.append('""') continue @@ -141,7 +147,7 @@ class CSV_XML(CatalogPlugin): outfile.close() elif self.fmt == 'xml': - from lxml import etree + #from lxml import etree from lxml.builder import E root = E.calibredb() @@ -149,6 +155,14 @@ class CSV_XML(CatalogPlugin): record = E.record() root.append(record) + for field in fields: + if field.startswith('#'): + val = db.get_field(r['id'],field,index_is_id=True) + if not isinstance(val, (str, unicode)): + val = unicode(val) + item = getattr(E, field.replace('#','_'))(val) + record.append(item) + for field in ('id', 'uuid', 'title', 'publisher', 'rating', 'size', 'isbn','ondevice'): if field in fields: @@ -470,7 +484,7 @@ class BIBTEX(CatalogPlugin): log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text) # Get the requested output fields as a list - fields = self.get_output_fields(opts) + fields = self.get_output_fields(db, opts) if not len(data): log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text) @@ -918,7 +932,8 @@ class EPUB_MOBI(CatalogPlugin): self.__genres = None self.genres = [] self.__genre_tags_dict = None - self.__htmlFileList = [] + self.__htmlFileList_1 = [] + self.__htmlFileList_2 = [] self.__markerTags = self.getMarkerTags() self.__ncxSoup = None self.__output_profile = None @@ -947,33 +962,34 @@ class EPUB_MOBI(CatalogPlugin): break # Confirm/create thumbs archive. - if not os.path.exists(self.__cache_dir): - self.opts.log.info(" creating new thumb cache '%s'" % self.__cache_dir) - os.makedirs(self.__cache_dir) - if not os.path.exists(self.__archive_path): - self.opts.log.info(' creating thumbnail archive, thumb_width: %1.2f"' % - float(self.opts.thumb_width)) - zfw = ZipFile(self.__archive_path, mode='w') - zfw.writestr("Catalog Thumbs Archive",'') - #zfw.comment = "thumb_width: %1.2f" % float(self.opts.thumb_width) - zfw.close() - else: - with closing(ZipFile(self.__archive_path, mode='r')) as zfr: - try: - cached_thumb_width = zfr.read('thumb_width') - except: - cached_thumb_width = "-1" - - if float(cached_thumb_width) != float(self.opts.thumb_width): - self.opts.log.info(" invalidating cache at '%s'" % self.__archive_path) - self.opts.log.info(' thumb_width: %1.2f" => %1.2f"' % - (float(cached_thumb_width),float(self.opts.thumb_width))) - os.remove(self.__archive_path) - with closing(ZipFile(self.__archive_path, mode='w')) as zfw: - zfw.writestr("Catalog Thumbs Archive",'') + if self.opts.generate_descriptions: + if not os.path.exists(self.__cache_dir): + self.opts.log.info(" creating new thumb cache '%s'" % self.__cache_dir) + os.makedirs(self.__cache_dir) + if not os.path.exists(self.__archive_path): + self.opts.log.info(' creating thumbnail archive, thumb_width: %1.2f"' % + float(self.opts.thumb_width)) + zfw = ZipFile(self.__archive_path, mode='w') + zfw.writestr("Catalog Thumbs Archive",'') + #zfw.comment = "thumb_width: %1.2f" % float(self.opts.thumb_width) + zfw.close() else: - self.opts.log.info(' existing thumb cache at %s, cached_thumb_width: %1.2f"' % - (self.__archive_path, float(cached_thumb_width))) + with closing(ZipFile(self.__archive_path, mode='r')) as zfr: + try: + cached_thumb_width = zfr.read('thumb_width') + except: + cached_thumb_width = "-1" + + if float(cached_thumb_width) != float(self.opts.thumb_width): + self.opts.log.info(" refreshing cache at '%s'" % self.__archive_path) + self.opts.log.info(' thumb_width: %1.2f" => %1.2f"' % + (float(cached_thumb_width),float(self.opts.thumb_width))) + os.remove(self.__archive_path) + with closing(ZipFile(self.__archive_path, mode='w')) as zfw: + zfw.writestr("Catalog Thumbs Archive",'') + else: + self.opts.log.info(' existing thumb cache at %s, cached_thumb_width: %1.2f"' % + (self.__archive_path, float(cached_thumb_width))) # Tweak build steps based on optional sections: 1 call for HTML, 1 for NCX if self.opts.generate_titles: @@ -1129,11 +1145,18 @@ class EPUB_MOBI(CatalogPlugin): self.__genre_tags_dict = val return property(fget=fget, fset=fset) @dynamic_property - def htmlFileList(self): + def htmlFileList_1(self): def fget(self): - return self.__htmlFileList + return self.__htmlFileList_1 def fset(self, val): - self.__htmlFileList = val + self.__htmlFileList_1 = val + return property(fget=fget, fset=fset) + @dynamic_property + def htmlFileList_2(self): + def fget(self): + return self.__htmlFileList_2 + def fset(self, val): + self.__htmlFileList_2 = val return property(fget=fget, fset=fset) @dynamic_property def libraryPath(self): @@ -1303,12 +1326,12 @@ class EPUB_MOBI(CatalogPlugin): self.generateHTMLByTitle() if self.opts.generate_series: self.generateHTMLBySeries() + if self.opts.generate_genres: + self.generateHTMLByTags() if self.opts.generate_recently_added: self.generateHTMLByDateAdded() if self.generateRecentlyRead: self.generateHTMLByDateRead() - if self.opts.generate_genres: - self.generateHTMLByTags() if self.opts.generate_descriptions: self.generateThumbnails() self.generateOPF() @@ -1318,12 +1341,12 @@ class EPUB_MOBI(CatalogPlugin): self.generateNCXByTitle("Titles") if self.opts.generate_series: self.generateNCXBySeries("Series") + if self.opts.generate_genres: + self.generateNCXByGenre("Genres") if self.opts.generate_recently_added: self.generateNCXByDateAdded("Recently Added") if self.generateRecentlyRead: self.generateNCXByDateRead("Recently Read") - if self.opts.generate_genres: - self.generateNCXByGenre("Genres") if self.opts.generate_descriptions: self.generateNCXDescriptions("Descriptions") @@ -1475,12 +1498,19 @@ class EPUB_MOBI(CatalogPlugin): this_title['formats'] = formats # Add user notes to be displayed in header + # Special case handling for datetime fields if self.opts.header_note_source_field: + field_md = self.__db.metadata_for_field(self.opts.header_note_source_field) notes = self.__db.get_field(record['id'], - self.opts.header_note_source_field, - index_is_id=True) + self.opts.header_note_source_field, + index_is_id=True) + if field_md['datatype'] == 'datetime': + # Reformat date fields to match UI presentation: dd MMM YYYY + notes = format_date(notes,'dd MMM yyyy') + if notes: - this_title['notes'] = notes + this_title['notes'] = {'source':field_md['name'], + 'content':notes} titles.append(this_title) @@ -1679,183 +1709,10 @@ class EPUB_MOBI(CatalogPlugin): (title_num, len(self.booksByTitle)), float(title_num*100/len(self.booksByTitle))/100) - # Generate the header - soup = self.generateHTMLDescriptionHeader("%s" % title['title']) - body = soup.find('body') + # Generate the header from user-customizable template + soup = self.generateHTMLDescriptionHeader(title) - btc = 0 - # Insert the anchor - aTag = Tag(soup, "a") - aTag['name'] = "book%d" % int(title['id']) - body.insert(btc, aTag) - btc += 1 - - # Insert the book title - #

Book Title

- emTag = Tag(soup, "em") - if title['series']: - # title
series series_index - if self.opts.generate_series: - brTag = Tag(soup,'br') - title_tokens = list(title['title'].partition(':')) - emTag.insert(0, escape(NavigableString(title_tokens[2].strip()))) - emTag.insert(1, brTag) - smallTag = Tag(soup,'small') - aTag = Tag(soup,'a') - aTag['href'] = "%s.html#%s_series" % ('BySeries', - re.sub('\W','',title['series']).lower()) - aTag.insert(0, title_tokens[0]) - smallTag.insert(0, aTag) - emTag.insert(2, smallTag) - else: - brTag = Tag(soup,'br') - title_tokens = list(title['title'].partition(':')) - emTag.insert(0, escape(NavigableString(title_tokens[2].strip()))) - emTag.insert(1, brTag) - smallTag = Tag(soup,'small') - smallTag.insert(0, escape(NavigableString(title_tokens[0]))) - emTag.insert(2, smallTag) - else: - emTag.insert(0, NavigableString(escape(title['title']))) - titleTag = body.find(attrs={'class':'title'}) - titleTag.insert(0,emTag) - - # Create the author anchor - authorTag = body.find(attrs={'class':'author'}) - aTag = Tag(soup, "a") - aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", - self.generateAuthorAnchor(title['author'])) - aTag.insert(0, title['author']) - - # Prefix author with read|reading|none symbol or missing symbol - if self.opts.wishlist_tag in title.get('tags', []): - authorTag.insert(0, NavigableString(self.MISSING_SYMBOL + " by ")) - else: - if title['read']: - authorTag.insert(0, NavigableString(self.READ_SYMBOL + " by ")) - elif self.opts.connected_kindle and title['id'] in self.bookmarked_books: - authorTag.insert(0, NavigableString(self.READING_SYMBOL + " by ")) - else: - #authorTag.insert(0, NavigableString(self.NOT_READ_SYMBOL + " by ")) - authorTag.insert(0, NavigableString("by ")) - authorTag.insert(1, aTag) - - ''' - # Insert Series info or remove. - seriesTag = body.find(attrs={'class':'series'}) - if title['series']: - # Insert a spacer to match the author indent - stc = 0 - fontTag = Tag(soup,"font") - fontTag['style'] = 'color:white;font-size:large' - if self.opts.fmt == 'epub': - fontTag['style'] += ';opacity: 0.0' - fontTag.insert(0, NavigableString("by ")) - seriesTag.insert(stc, fontTag) - stc += 1 - if float(title['series_index']) - int(title['series_index']): - series_str = 'Series: %s [%4.2f]' % (title['series'], title['series_index']) - else: - series_str = '%s [%d]' % (title['series'], title['series_index']) - seriesTag.insert(stc,NavigableString(series_str)) - else: - seriesTag.extract() - ''' - # Insert linked genres - if 'tags' in title: - tagsTag = body.find(attrs={'class':'tags'}) - ttc = 0 - - ''' - # Insert a spacer to match the author indent - fontTag = Tag(soup,"font") - fontTag['style'] = 'color:white;font-size:large' - if self.opts.fmt == 'epub': - fontTag['style'] += ';opacity: 0.0' - fontTag.insert(0, NavigableString(" by ")) - tagsTag.insert(ttc, fontTag) - ttc += 1 - ''' - - for tag in title.get('tags', []): - aTag = Tag(soup,'a') - #print "aTag: %s" % "Genre_%s.html" % re.sub("\W","",tag.lower()) - if self.opts.generate_genres: - aTag['href'] = "Genre_%s.html" % re.sub("\W","",tag.lower()) - aTag.insert(0,escape(NavigableString(tag))) - emTag = Tag(soup, "em") - emTag.insert(0, aTag) - if ttc < len(title['tags'])-1: - emTag.insert(1, NavigableString(' · ')) - tagsTag.insert(ttc, emTag) - ttc += 1 - - # Insert formats - if 'formats' in title: - formatsTag = body.find(attrs={'class':'formats'}) - formats = [] - for format in sorted(title['formats']): - formats.append(format.rpartition('.')[2].upper()) - formatsTag.insert(0, NavigableString(' · '.join(formats))) - - # Insert the cover if available - imgTag = Tag(soup,"img") - if 'cover' in title: - imgTag['src'] = "../images/thumbnail_%d.jpg" % int(title['id']) - else: - imgTag['src'] = "../images/thumbnail_default.jpg" - imgTag['alt'] = "cover" - - ''' - if self.opts.fmt == 'mobi': - imgTag['style'] = 'width: %dpx; height:%dpx;' % (self.thumbWidth, self.thumbHeight) - ''' - - thumbnailTag = body.find(attrs={'class':'thumbnail'}) - thumbnailTag.insert(0,imgTag) - - # Insert the publisher - publisherTag = body.find(attrs={'class':'publisher'}) - if 'publisher' in title: - publisherTag.insert(0,NavigableString(title['publisher'] + '
' )) - else: - publisherTag.insert(0,NavigableString('
')) - - # Insert the publication date - pubdateTag = body.find(attrs={'class':'date'}) - if title['date'] is not None: - pubdateTag.insert(0,NavigableString(title['date'] + '
')) - else: - pubdateTag.insert(0,NavigableString('
')) - - # Insert the rating, remove if unrated - # Render different ratings chars for epub/mobi - stars = int(title['rating']) / 2 - ratingTag = body.find(attrs={'class':'rating'}) - if stars: - star_string = self.FULL_RATING_SYMBOL * stars - empty_stars = self.EMPTY_RATING_SYMBOL * (5 - stars) - ratingTag.insert(0,NavigableString('%s%s
' % (star_string,empty_stars))) - else: - #ratingLabel = body.find('td',text="Rating").replaceWith("Unrated") - ratingTag.insert(0,NavigableString('
')) - - # Insert user notes or remove Notes label. Notes > 1 line will push formatting down - if 'notes' in title: - notesTag = body.find(attrs={'class':'notes'}) - notesTag.insert(0,NavigableString(title['notes'] + '
')) - else: - pass -# notes_labelTag = body.find(attrs={'class':'notes_label'}) -# empty_labelTag = Tag(soup, "td") -# empty_labelTag.insert(0,NavigableString('
')) -# notes_labelTag.replaceWith(empty_labelTag) - - # Insert the blurb - if 'description' in title and title['description'] > '': - blurbTag = body.find(attrs={'class':'description'}) - blurbTag.insert(0,NavigableString(title['description'])) # Write the book entry to contentdir outfile = open("%s/book_%d.html" % (self.contentDir, int(title['id'])), 'w') @@ -1994,7 +1851,7 @@ class EPUB_MOBI(CatalogPlugin): outfile = open(outfile_spec, 'w') outfile.write(soup.prettify()) outfile.close() - self.htmlFileList.append("content/ByAlphaTitle.html") + self.htmlFileList_1.append("content/ByAlphaTitle.html") def generateHTMLByAuthor(self): # Write books by author A-Z @@ -2176,7 +2033,7 @@ class EPUB_MOBI(CatalogPlugin): outfile = open(outfile_spec, 'w') outfile.write(soup.prettify()) outfile.close() - self.htmlFileList.append("content/ByAlphaAuthor.html") + self.htmlFileList_1.append("content/ByAlphaAuthor.html") def generateHTMLByDateAdded(self): # Write books by reverse chronological order @@ -2451,7 +2308,7 @@ class EPUB_MOBI(CatalogPlugin): outfile = open(outfile_spec, 'w') outfile.write(soup.prettify()) outfile.close() - self.htmlFileList.append("content/ByDateAdded.html") + self.htmlFileList_2.append("content/ByDateAdded.html") def generateHTMLByDateRead(self): # Write books by active bookmarks @@ -2642,7 +2499,7 @@ class EPUB_MOBI(CatalogPlugin): outfile = open(outfile_spec, 'w') outfile.write(soup.prettify()) outfile.close() - self.htmlFileList.append("content/ByDateRead.html") + self.htmlFileList_2.append("content/ByDateRead.html") def generateHTMLBySeries(self): ''' @@ -2673,7 +2530,9 @@ class EPUB_MOBI(CatalogPlugin): self.opts.search_text = search_phrase # Fetch the database as a dictionary - self.booksBySeries = self.plugin.search_sort_db(self.db, self.opts) + data = self.plugin.search_sort_db(self.db, self.opts) + self.booksBySeries = self.processExclusions(data) + if not self.booksBySeries: self.opts.generate_series = False self.opts.log(" no series found in selected books, cancelling series generation") @@ -2822,7 +2681,7 @@ class EPUB_MOBI(CatalogPlugin): outfile = open(outfile_spec, 'w') outfile.write(soup.prettify()) outfile.close() - self.htmlFileList.append("content/BySeries.html") + self.htmlFileList_1.append("content/BySeries.html") def generateHTMLByTags(self): # Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ... @@ -3080,7 +2939,8 @@ class EPUB_MOBI(CatalogPlugin): else self.booksByTitle # Add html_files to manifest and spine - for file in self.htmlFileList: + for file in self.htmlFileList_1: + # By Author, By Title, By Series, itemTag = Tag(soup, "item") start = file.find('/') + 1 end = file.find('.') @@ -3114,6 +2974,23 @@ class EPUB_MOBI(CatalogPlugin): spine.insert(stc, itemrefTag) stc += 1 + for file in self.htmlFileList_2: + # By Date Added, By Date Read + itemTag = Tag(soup, "item") + start = file.find('/') + 1 + end = file.find('.') + itemTag['href'] = file + itemTag['id'] = file[start:end].lower() + itemTag['media-type'] = "application/xhtml+xml" + manifest.insert(mtc, itemTag) + mtc += 1 + + # spine + itemrefTag = Tag(soup, "itemref") + itemrefTag['idref'] = file[start:end].lower() + spine.insert(stc, itemrefTag) + stc += 1 + for book in sort_descriptions_by: # manifest itemTag = Tag(soup, "item") @@ -4346,59 +4223,199 @@ class EPUB_MOBI(CatalogPlugin): return titles_spanned - def generateHTMLDescriptionHeader(self, title): + def generateHTMLDescriptionHeader(self, book): + ''' + Generate description header from template + ''' + NBSP = ' ' + MIDDOT = '·' + def generate_html(): + args = dict( + author=author, + author_prefix=author_prefix, + css=css, + formats=formats, + genres=genres, + note_content=note_content, + note_source=note_source, + pubdate=pubdate, + publisher=publisher, + pubmonth=pubmonth, + pubyear=pubyear, + rating=rating, + series=series, + series_index=series_index, + title=title, + title_str=title_str, + xmlns=XHTML_NS, + ) - title_border = '' if self.opts.fmt == 'epub' else \ - '
' - header = ''' - - - - - - - - -

- {0} -

- -

 

-

 

- - - - - - - - - - - - - - - - - - - - - - - -
 
 
 
-
-
- - - '''.format(title_border) + generated_html = P('catalog/template.xhtml', + data=True).decode('utf-8').format(**args) - # Insert the supplied title + soup = BeautifulSoup(generated_html) + return soup.renderContents(None) + + if False: + print "title metadata:\n%s" % ', '.join(sorted(book.keys())) + for item in sorted(book.keys()): + try: + print "%s: %s%s" % (item, book[item][:50], '...' if len(book[item])>50 else '') + except: + print "%s: %s" % (item, book[item]) + + # Generate the template arguments + css = P('catalog/stylesheet.css', data=True).decode('utf-8') + title_str = escape(book['title']) + + # Title/series + if book['series']: + series_id, _, title = book['title'].partition(':') + title = escape(title.strip()) + series = escape(book['series']) + series_index = str(book['series_index']) + if series_index.endswith('.0'): + series_index = series_index[:-2] + else: + title = escape(book['title']) + series = '' + series_index = '' + + # Author, author_prefix (read|reading|none symbol or missing symbol) + author = book['author'] + if self.opts.wishlist_tag in book.get('tags', []): + author_prefix = self.MISSING_SYMBOL + " by " + else: + if book['read']: + author_prefix = self.READ_SYMBOL + " by " + elif self.opts.connected_kindle and book['id'] in self.bookmarked_books: + author_prefix = self.READING_SYMBOL + " by " + else: + author_prefix = "by " + + # Genres + genres = '' + if 'tags' in book: + _soup = BeautifulSoup('') + genresTag = Tag(_soup,'p') + gtc = 0 + for tag in book.get('tags', []): + aTag = Tag(_soup,'a') + if self.opts.generate_genres: + aTag['href'] = "Genre_%s.html" % re.sub("\W","",tag.lower()) + aTag.insert(0,escape(NavigableString(tag))) + genresTag.insert(gtc, aTag) + gtc += 1 + if gtc < len(book['tags']): + genresTag.insert(gtc, NavigableString(' %s ' % MIDDOT)) + gtc += 1 + genres = genresTag.renderContents() + + # Formats + formats = [] + if 'formats' in book: + for format in sorted(book['formats']): + formats.append(format.rpartition('.')[2].upper()) + formats = ' %s ' % MIDDOT.join(formats) + + pubdate = book['date'] + pubmonth, pubyear = pubdate.split(' ') + + ''' + # Thumb + # This doesn't make it through the etree.fromstring parsing + _soup = BeautifulSoup('') + imgTag = Tag(_soup,"img") + if 'cover' in book: + imgTag['src'] = "../images/thumbnail_%d.jpg" % int(book['id']) + else: + imgTag['src'] = "../images/thumbnail_default.jpg" + imgTag['alt'] = "cover thumbnail" + thumb = imgTag.renderContents() + ''' + + # Publisher + publisher = NBSP + if 'publisher' in book: + publisher = book['publisher'] + + # Rating + stars = int(book['rating']) / 2 + rating = NBSP + if stars: + star_string = self.FULL_RATING_SYMBOL * stars + empty_stars = self.EMPTY_RATING_SYMBOL * (5 - stars) + rating = '%s%s
' % (star_string,empty_stars) + + # Notes + note_source = NBSP + note_content = NBSP + if 'notes' in book: + note_source = book['notes']['source'] + note_content = book['notes']['content'] + + # >>>> Populate the template <<<< + if True: + root = etree.fromstring(generate_html(), parser=RECOVER_PARSER) + else: + root = etree.fromstring(generate_html()) + header = etree.tostring(root, pretty_print=True, encoding='utf-8') soup = BeautifulSoup(header, selfClosingTags=['mbp:pagebreak']) - titleTag = soup.find('title') - titleTag.insert(0,NavigableString(escape(title))) + + + + # >>>> Post-process the template <<<< + body = soup.find('body') + btc = 0 + # Insert the title anchor for inbound links + aTag = Tag(soup, "a") + aTag['name'] = "book%d" % int(book['id']) + body.insert(btc, aTag) + btc += 1 + + # Insert the link to the series or remove + aTag = body.find('a', attrs={'class':'series_id'}) + if book['series']: + if self.opts.generate_series: + aTag['href'] = "%s.html#%s_series" % ('BySeries', + re.sub('\W','',book['series']).lower()) + else: + aTag.extract() + + # Insert the author link (always) + aTag = body.find('a', attrs={'class':'author'}) + aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", + self.generateAuthorAnchor(book['author'])) + + if not genres: + genresTag = body.find('p',attrs={'class':'genres'}) + genresTag.extract() + + if not formats: + formatsTag = body.find('p',attrs={'class':'formats'}) + formatsTag.extract() + + if note_content == NBSP: + tdTag = body.find('td', attrs={'class':'notes'}) + tdTag.contents[0].replaceWith(NBSP) + + # Cover thumb + tdTag = body.find('td', attrs={'class':'thumbnail'}) + imgTag = Tag(soup,"img") + if 'cover' in book: + imgTag['src'] = "../images/thumbnail_%d.jpg" % int(book['id']) + else: + imgTag['src'] = "../images/thumbnail_default.jpg" + imgTag['alt'] = "cover thumbnail" + tdTag.insert(0,imgTag) + + # The Blurb + if 'description' in book and book['description'] > '': + blurbTag = body.find(attrs={'class':'description'}) + blurbTag.insert(0,NavigableString(book['description'])) + + if False: + print soup.prettify() return soup def generateHTMLEmptyHeader(self, title): From 5f677e1ed63054e1bfb164899925607ab16584a8 Mon Sep 17 00:00:00 2001 From: GRiker Date: Sun, 26 Dec 2010 06:05:25 -0700 Subject: [PATCH 4/8] GwR catalog revisions wip --- src/calibre/library/catalog.py | 135 +++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 59 deletions(-) diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 168f4968e2..0fb3f7138d 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -52,7 +52,8 @@ class CSV_XML(CatalogPlugin): action = None, help = _('The fields to output when cataloging books in the ' 'database. Should be a comma-separated list of fields.\n' - 'Available fields: %s.\n' + 'Available fields: %s,\n' + 'plus user-created custom fields.\n' "Default: '%%default'\n" "Applies to: CSV, XML output formats")%', '.join(FIELDS)), @@ -549,6 +550,17 @@ class EPUB_MOBI(CatalogPlugin): version = (0, 0, 1) file_types = set(['epub','mobi']) + ''' + # Deprecated, keeping this just in case there are complaints + Option('--numbers-as-text', + default=False, + dest='numbers_as_text', + action = None, + help=_("Sort titles with leading numbers as text, e.g.,\n'2001: A Space Odyssey' sorts as \n'Two Thousand One: A Space Odyssey'.\n" + "Default: '%default'\n" + "Applies to: ePub, MOBI output formats")), + ''' + cli_options = [Option('--catalog-title', default = 'My Books', dest = 'catalog_title', @@ -625,20 +637,6 @@ class EPUB_MOBI(CatalogPlugin): " [True|False] - A horizontal rule is inserted between notes and Comments\n" "Default: '%default'\n" "Applies to ePub, MOBI output formats")), - Option('--note-tag', - default='*', - dest='note_tag', - action = None, - help=_("Tag prefix for user notes, e.g. '*Jeff might enjoy reading this'.\n" - "Default: '%default'\n" - "Applies to: ePub, MOBI output formats")), - Option('--numbers-as-text', - default=False, - dest='numbers_as_text', - action = None, - help=_("Sort titles with leading numbers as text, e.g.,\n'2001: A Space Odyssey' sorts as \n'Two Thousand One: A Space Odyssey'.\n" - "Default: '%default'\n" - "Applies to: ePub, MOBI output formats")), Option('--output-profile', default=None, dest='output_profile', @@ -1422,6 +1420,8 @@ class EPUB_MOBI(CatalogPlugin): # Populate this_title{} from data[{},{}] titles = [] for record in data: + if False: + print "available record metadata:\n%s" % sorted(record.keys()) this_title = {} this_title['id'] = record['id'] @@ -1438,6 +1438,9 @@ class EPUB_MOBI(CatalogPlugin): this_title['title_sort'] = self.generateSortTitle(this_title['title']) if 'authors' in record: + # from calibre.ebooks.metadata import authors_to_string + # return authors_to_string(self.authors) + this_title['authors'] = record['authors'] if record['authors']: this_title['author'] = " & ".join(record['authors']) @@ -1780,9 +1783,16 @@ class EPUB_MOBI(CatalogPlugin): title_list = self.booksByTitle if not self.useSeriesPrefixInTitlesSection: title_list = self.booksByTitle_noSeriesPrefix + drtc = 0 for book in title_list: if self.letter_or_symbol(book['title_sort'][0]) != current_letter : # Start a new letter + if drtc: + divTag.insert(dtc, divRunningTag) + dtc += 1 + divRunningTag = Tag(soup, 'div') + divRunningTag['style'] = 'display:inline-block;width:100%' + drtc = 0 current_letter = self.letter_or_symbol(book['title_sort'][0]) pIndexTag = Tag(soup, "p") pIndexTag['class'] = "letter_index" @@ -1790,8 +1800,8 @@ class EPUB_MOBI(CatalogPlugin): aTag['name'] = "%s" % self.letter_or_symbol(book['title_sort'][0]) pIndexTag.insert(0,aTag) pIndexTag.insert(1,NavigableString(self.letter_or_symbol(book['title_sort'][0]))) - divTag.insert(dtc,pIndexTag) - dtc += 1 + divRunningTag.insert(dtc,pIndexTag) + drtc += 1 # Add books pBookTag = Tag(soup, "p") @@ -1839,8 +1849,12 @@ class EPUB_MOBI(CatalogPlugin): pBookTag.insert(ptc, emTag) ptc += 1 - divTag.insert(dtc, pBookTag) - dtc += 1 + divRunningTag.insert(drtc, pBookTag) + drtc += 1 + + # Add the last divRunningTag to divTag + divTag.insert(dtc, divRunningTag) + dtc += 1 # Add the divTag to the body body.insert(btc, divTag) @@ -1902,28 +1916,37 @@ class EPUB_MOBI(CatalogPlugin): for book in self.booksByAuthor: book_count += 1 if self.letter_or_symbol(book['author_sort'][0].upper()) != current_letter : - ''' - # Start a new letter - anchor only, hidden - current_letter = book['author_sort'][0].upper() - aTag = Tag(soup, "a") - aTag['name'] = "%sauthors" % current_letter - divTag.insert(dtc, aTag) - dtc += 1 - ''' # Start a new letter with Index letter current_letter = self.letter_or_symbol(book['author_sort'][0].upper()) + author_count = 0 + divOpeningTag = Tag(soup, 'div') + divOpeningTag['style'] = 'display:inline-block;width:100%' + dotc = 0 pIndexTag = Tag(soup, "p") pIndexTag['class'] = "letter_index" aTag = Tag(soup, "a") aTag['name'] = "%sauthors" % self.letter_or_symbol(current_letter) pIndexTag.insert(0,aTag) pIndexTag.insert(1,NavigableString(self.letter_or_symbol(book['author_sort'][0].upper()))) - divTag.insert(dtc,pIndexTag) - dtc += 1 + divOpeningTag.insert(dotc,pIndexTag) + dotc += 1 if book['author'] != current_author: # Start a new author current_author = book['author'] + author_count += 1 + if author_count == 2: + # Add divOpeningTag to divTag + divTag.insert(dtc, divOpeningTag) + dtc += 1 + elif author_count > 2: + divTag.insert(dtc, divRunningTag) + dtc += 1 + + divRunningTag = Tag(soup, 'div') + divRunningTag['style'] = 'display:inline-block;width:100%' + drtc = 0 + non_series_books = 0 current_series = None pAuthorTag = Tag(soup, "p") @@ -1932,18 +1955,12 @@ class EPUB_MOBI(CatalogPlugin): aTag['name'] = "%s" % self.generateAuthorAnchor(current_author) aTag.insert(0,NavigableString(current_author)) pAuthorTag.insert(0,aTag) - divTag.insert(dtc,pAuthorTag) - dtc += 1 - - ''' - # Insert an
between non-series and series - if not current_series and non_series_books and book['series']: - # Insert an
- hrTag = Tag(soup,'hr') - hrTag['class'] = "series_divider" - divTag.insert(dtc,hrTag) - dtc += 1 - ''' + if author_count == 1: + divOpeningTag.insert(dotc, pAuthorTag) + dotc += 1 + else: + divRunningTag.insert(drtc,pAuthorTag) + drtc += 1 # Check for series if book['series'] and book['series'] != current_series: @@ -1963,8 +1980,12 @@ class EPUB_MOBI(CatalogPlugin): #pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + '%s' % book['series'])) pSeriesTag.insert(0,NavigableString('%s' % book['series'])) - divTag.insert(dtc,pSeriesTag) - dtc += 1 + if author_count == 1: + divOpeningTag.insert(dotc, pSeriesTag) + dotc += 1 + else: + divRunningTag.insert(drtc,pSeriesTag) + drtc += 1 if current_series and not book['series']: current_series = None @@ -2007,8 +2028,12 @@ class EPUB_MOBI(CatalogPlugin): pBookTag.insert(ptc, aTag) ptc += 1 - divTag.insert(dtc, pBookTag) - dtc += 1 + if author_count == 1: + divOpeningTag.insert(dotc, pBookTag) + dotc += 1 + else: + divRunningTag.insert(drtc,pBookTag) + drtc += 1 if not self.__generateForKindle: # Insert the

tag with book_count at the head @@ -2024,6 +2049,10 @@ class EPUB_MOBI(CatalogPlugin): body.insert(btc,pTag) btc += 1 + if author_count == 1: + divTag.insert(dtc, divOpeningTag) + dtc += 1 + # Add the divTag to the body body.insert(btc, divTag) @@ -2571,14 +2600,6 @@ class EPUB_MOBI(CatalogPlugin): # Check for initial letter change sort_title = self.generateSortTitle(book['series']) if self.letter_or_symbol(sort_title[0].upper()) != current_letter : - ''' - # Start a new letter - anchor only, hidden - current_letter = book['author_sort'][0].upper() - aTag = Tag(soup, "a") - aTag['name'] = "%sseries" % current_letter - divTag.insert(dtc, aTag) - dtc += 1 - ''' # Start a new letter with Index letter current_letter = self.letter_or_symbol(sort_title[0].upper()) pIndexTag = Tag(soup, "p") @@ -4258,6 +4279,7 @@ class EPUB_MOBI(CatalogPlugin): if False: print "title metadata:\n%s" % ', '.join(sorted(book.keys())) + if False: for item in sorted(book.keys()): try: print "%s: %s%s" % (item, book[item][:50], '...' if len(book[item])>50 else '') @@ -4355,15 +4377,11 @@ class EPUB_MOBI(CatalogPlugin): note_content = book['notes']['content'] # >>>> Populate the template <<<< - if True: - root = etree.fromstring(generate_html(), parser=RECOVER_PARSER) - else: - root = etree.fromstring(generate_html()) + root = etree.fromstring(generate_html(), parser=RECOVER_PARSER) header = etree.tostring(root, pretty_print=True, encoding='utf-8') soup = BeautifulSoup(header, selfClosingTags=['mbp:pagebreak']) - # >>>> Post-process the template <<<< body = soup.find('body') btc = 0 @@ -4448,7 +4466,6 @@ class EPUB_MOBI(CatalogPlugin):

-
From 966a9c4ffbd372027ad1b057e14dbff35890595b Mon Sep 17 00:00:00 2001 From: GRiker Date: Sun, 26 Dec 2010 07:25:06 -0700 Subject: [PATCH 5/8] GwR catalog revisions wip --- src/calibre/customize/profiles.py | 2 ++ src/calibre/library/catalog.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py index 54c4259678..017bf86efb 100644 --- a/src/calibre/customize/profiles.py +++ b/src/calibre/customize/profiles.py @@ -617,6 +617,8 @@ class KindleDXOutput(OutputProfile): #comic_screen_size = (741, 1022) supports_mobi_indexing = True periodical_date_in_title = False + missing_char = u'x\u2009' + empty_ratings_char = u'\u2606' ratings_char = u'\u2605' read_char = u'\u2713' mobi_ems_per_blockquote = 2.0 diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 0fb3f7138d..7d1dd47b08 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -1507,7 +1507,7 @@ class EPUB_MOBI(CatalogPlugin): notes = self.__db.get_field(record['id'], self.opts.header_note_source_field, index_is_id=True) - if field_md['datatype'] == 'datetime': + if notes and field_md['datatype'] == 'datetime': # Reformat date fields to match UI presentation: dd MMM YYYY notes = format_date(notes,'dd MMM yyyy') From 463a95cb26430ba27fc18a3e0da4b4a00dcf1e88 Mon Sep 17 00:00:00 2001 From: GRiker Date: Sun, 26 Dec 2010 07:33:38 -0700 Subject: [PATCH 6/8] GwR catalog revisions wip --- src/calibre/library/catalog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 7d1dd47b08..28161249a5 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -4321,14 +4321,14 @@ class EPUB_MOBI(CatalogPlugin): _soup = BeautifulSoup('') genresTag = Tag(_soup,'p') gtc = 0 - for tag in book.get('tags', []): + for (i, tag) in enumerate(book.get('tags', [])): aTag = Tag(_soup,'a') if self.opts.generate_genres: aTag['href'] = "Genre_%s.html" % re.sub("\W","",tag.lower()) aTag.insert(0,escape(NavigableString(tag))) genresTag.insert(gtc, aTag) gtc += 1 - if gtc < len(book['tags']): + if i < len(book['tags'])-1: genresTag.insert(gtc, NavigableString(' %s ' % MIDDOT)) gtc += 1 genres = genresTag.renderContents() From 74315b9736742702e4f9f2e7b1534ed746d14362 Mon Sep 17 00:00:00 2001 From: GRiker Date: Sun, 26 Dec 2010 09:05:48 -0700 Subject: [PATCH 7/8] GwR catalog revisions wip --- resources/catalog/stylesheet.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/catalog/stylesheet.css b/resources/catalog/stylesheet.css index dd25566b22..bf83a4c60b 100644 --- a/resources/catalog/stylesheet.css +++ b/resources/catalog/stylesheet.css @@ -152,18 +152,18 @@ hr.merged_comments_divider { border-left: solid white 0px; } -td { - text-align:center; - } td.publisher, td.date { font-weight:bold; + text-align:center; } td.rating{ + text-align:center; } td.notes { font-size: 100%; + text-align:center; } td.thumbnail img { From 8e4c90468b19ecbb1f4bc8bf85e9fc9ce1024611 Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 31 Dec 2010 04:17:48 -0700 Subject: [PATCH 8/8] GwR catalog CLI fixes --- src/calibre/library/catalog.py | 48 ++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 28161249a5..02789c32de 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -550,6 +550,9 @@ class EPUB_MOBI(CatalogPlugin): version = (0, 0, 1) file_types = set(['epub','mobi']) + THUMB_SMALLEST = "1.0" + THUMB_LARGEST = "2.0" + ''' # Deprecated, keeping this just in case there are complaints Option('--numbers-as-text', @@ -599,22 +602,36 @@ class EPUB_MOBI(CatalogPlugin): "--exclude-tags=skip will match 'skip this book' and 'Skip will like this'.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), + Option('--generate-descriptions', + default=True, + dest='generate_descriptions', + action = 'store_true', + help=_("Include book descriptions in catalog.\n" + "Default: '%default'\n" + "Applies to: ePub, MOBI output formats")), + Option('--generate-genres', + default=True, + dest='generate_genres', + action = 'store_true', + help=_("Include 'Genres' section in catalog.\n" + "Default: '%default'\n" + "Applies to: ePub, MOBI output formats")), Option('--generate-titles', - default=False, + default=True, dest='generate_titles', action = 'store_true', help=_("Include 'Titles' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), Option('--generate-series', - default=False, + default=True, dest='generate_series', action = 'store_true', help=_("Include 'Series' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), Option('--generate-recently-added', - default=False, + default=True, dest='generate_recently_added', action = 'store_true', help=_("Include 'Recently Added' section in catalog.\n" @@ -650,6 +667,14 @@ class EPUB_MOBI(CatalogPlugin): action = None, help=_("field:pattern indicating book has been read.\n" "Default: '%default'\n" "Applies to ePub, MOBI output formats")), + Option('--thumb-width', + default='1.0', + dest='thumb_width', + action = None, + help=_("Size hint (in inches) for book covers in catalog.\n" + "Range: 1.0 - 2.0\n" + "Default: '%default'\n" + "Applies to ePub, MOBI output formats")), Option('--wishlist-tag', default='Wishlist', dest='wishlist_tag', @@ -979,8 +1004,8 @@ class EPUB_MOBI(CatalogPlugin): cached_thumb_width = "-1" if float(cached_thumb_width) != float(self.opts.thumb_width): - self.opts.log.info(" refreshing cache at '%s'" % self.__archive_path) - self.opts.log.info(' thumb_width: %1.2f" => %1.2f"' % + self.opts.log.warning(" invalidating cache at '%s'" % self.__archive_path) + self.opts.log.warning(' thumb_width changed: %1.2f" => %1.2f"' % (float(cached_thumb_width),float(self.opts.thumb_width))) os.remove(self.__archive_path) with closing(ZipFile(self.__archive_path, mode='w')) as zfw: @@ -4947,6 +4972,19 @@ class EPUB_MOBI(CatalogPlugin): build_log.append(u" Sections: %s" % ', '.join(sections_list)) + # Limit thumb_width to 1.0" - 2.0" + try: + if float(opts.thumb_width) < float(self.THUMB_SMALLEST): + log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_SMALLEST)) + opts.thumb_width = self.THUMB_SMALLEST + if float(opts.thumb_width) > float(self.THUMB_LARGEST): + log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_LARGEST)) + opts.thumb_width = self.THUMB_LARGEST + opts.thumb_width = "%.2f" % float(opts.thumb_width) + except: + log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_SMALLEST)) + opts.thumb_width = "1.0" + # Display opts keys = opts_dict.keys() keys.sort()