diff --git a/resources/recipes/gulfnews.recipe b/resources/recipes/gulfnews.recipe new file mode 100644 index 0000000000..4c40aa3f37 --- /dev/null +++ b/resources/recipes/gulfnews.recipe @@ -0,0 +1,64 @@ +__license__ = 'GPL v3' +__copyright__ = '2011, Darko Miletic ' +''' +gulfnews.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class GulfNews(BasicNewsRecipe): + title = 'Gulf News' + __author__ = 'Darko Miletic' + description = 'News from United Arab Emirrates, persian gulf and rest of the world' + publisher = 'Al Nisr Publishing LLC' + category = 'news, politics, UAE, world' + oldest_article = 2 + max_articles_per_feed = 200 + no_stylesheets = True + encoding = 'utf8' + use_embedded_content = False + language = 'en' + remove_empty_feeds = True + publication_type = 'newsportal' + masthead_url = 'http://gulfnews.com/media/img/gulf_news_logo.jpg' + extra_css = """ + body{font-family: Arial,Helvetica,sans-serif } + img{margin-bottom: 0.4em; display:block} + h1{font-family: Georgia, 'Times New Roman', Times, serif} + ol,ul{list-style: none} + .synopsis{font-size: small} + .details{font-size: x-small} + .image{font-size: xx-small} + """ + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + } + + remove_tags = [ + dict(name=['meta','link','object','embed']) + ,dict(attrs={'class':['quickLinks','ratings']}) + ,dict(attrs={'id':'imageSelector'}) + ] + remove_attributes=['lang'] + keep_only_tags=[ + dict(name='h1') + ,dict(attrs={'class':['synopsis','details','image','article']}) + ] + + + feeds = [ + (u'UAE News' , u'http://gulfnews.com/cmlink/1.446094') + ,(u'Business' , u'http://gulfnews.com/cmlink/1.446098') + ,(u'Entertainment' , u'http://gulfnews.com/cmlink/1.446095') + ,(u'Sport' , u'http://gulfnews.com/cmlink/1.446096') + ,(u'Life' , u'http://gulfnews.com/cmlink/1.446097') + ] + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + return soup diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index 9f8dbcb379..e549a4a9fd 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -106,7 +106,7 @@ class PDNOVEL(USBMS): WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE' THUMBNAIL_HEIGHT = 130 - EBOOK_DIR_MAIN = 'eBooks' + EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'eBooks' SUPPORTS_SUB_DIRS = False DELETE_EXTS = ['.jpg', '.jpeg', '.png'] diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.ui b/src/calibre/gui2/catalog/catalog_epub_mobi.ui index 6e057a6481..5136f86fae 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.ui +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.ui @@ -35,7 +35,7 @@ - Sections to include in catalog. All catalogs include 'Books by Author'. + Sections to include in catalog. Included sections @@ -79,13 +79,13 @@ - false + true Books by Author - true + false diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py new file mode 100644 index 0000000000..eff6f97e7d --- /dev/null +++ b/src/calibre/gui2/metadata/single.py @@ -0,0 +1,535 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import textwrap, re + +from PyQt4.Qt import QDialogButtonBox, Qt, QTabWidget, QScrollArea, \ + QVBoxLayout, QIcon, QToolButton, QWidget, QLabel, QGridLayout, \ + QDoubleSpinBox + +from calibre.gui2 import ResizableDialog +from calibre.utils.icu import sort_key +from calibre.utils.config import tweaks +from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \ + EnComboBox +from calibre.ebooks.metadata import title_sort, authors_to_string, \ + string_to_authors + +''' +The interface common to all widgets used to set basic metadata +class BasicMetadataWidget(object): + + LABEL = "label text" + + def initialize(self, db, id_): + pass + + def commit(self, db, id_): + return True + + @dynamic_property + def current_val(self): + def fget(self): + return None + def fset(self, val): + pass + return property(fget=fget, fset=fset) +''' + +# Title {{{ +class TitleEdit(EnLineEdit): + + TITLE_ATTR = 'title' + COMMIT = True + TOOLTIP = _('Change the title of this book') + LABEL = _('&Title:') + + def __init__(self, parent): + self.dialog = parent + EnLineEdit.__init__(self, parent) + self.setToolTip(self.TOOLTIP) + self.setWhatsThis(self.TOOLTIP) + + def get_default(self): + return _('Unknown') + + def initialize(self, db, id_): + title = getattr(db, self.TITLE_ATTR)(id_, index_is_id=True) + self.current_val = title + self.original_val = self.current_val + + def commit(self, db, id_): + title = self.current_val + if self.COMMIT: + getattr(db, 'set_', self.TITLE_ATTR)(id_, title, notify=False) + else: + getattr(db, 'set_', self.TITLE_ATTR)(id_, title, notify=False, + commit=False) + return True + + @dynamic_property + def current_val(self): + + def fget(self): + title = unicode(self.text()).strip() + if not title: + title = self.get_default() + return title + + def fset(self, val): + if hasattr(val, 'strip'): + val = val.strip() + if not val: + val = self.get_default() + self.setText(val) + self.setCursorPosition(0) + + return property(fget=fget, fset=fset) + +class TitleSortEdit(TitleEdit): + + TITLE_ATTR = 'title_sort' + COMMIT = False + TOOLTIP = _('Specify how this book should be sorted when by title.' + ' For example, The Exorcist might be sorted as Exorcist, The.') + LABEL = _('Title &sort:') + + def __init__(self, parent, title_edit, autogen_button): + TitleEdit.__init__(self, parent) + self.title_edit = title_edit + + base = self.TOOLTIP + ok_tooltip = '

' + textwrap.fill(base+'

'+ + _(' The green color indicates that the current ' + 'title sort matches the current title')) + bad_tooltip = '

'+textwrap.fill(base + '

'+ + _(' The red color warns that the current ' + 'title sort does not match the current title. ' + 'No action is required if this is what you want.')) + self.tooltips = (ok_tooltip, bad_tooltip) + + self.title_edit.textChanged.connect(self.update_state) + self.textChanged.connect(self.update_state) + + autogen_button.clicked.connect(self.auto_generate) + self.update_state() + + def update_state(self, *args): + ts = title_sort(self.title_edit.current_val) + normal = ts == self.current_val + if normal: + col = 'rgb(0, 255, 0, 20%)' + else: + col = 'rgb(255, 0, 0, 20%)' + self.setStyleSheet('QLineEdit { color: black; ' + 'background-color: %s; }'%col) + tt = self.tooltips[0 if normal else 1] + self.setToolTip(tt) + self.setWhatsThis(tt) + + def auto_generate(self, *args): + self.current_val = title_sort(self.title_edit.current_val) + +# }}} + +# Authors {{{ +class AuthorsEdit(CompleteComboBox): + + TOOLTIP = '' + LABEL = _('&Author(s):') + + def __init__(self, parent): + self.dialog = parent + CompleteComboBox.__init__(self, parent) + self.setToolTip(self.TOOLTIP) + self.setWhatsThis(self.TOOLTIP) + self.setEditable(True) + self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon) + + def get_default(self): + return _('Unknown') + + def initialize(self, db, id_): + all_authors = db.all_authors() + all_authors.sort(key=lambda x : sort_key(x[1])) + for i in all_authors: + id, name = i + name = [name.strip().replace('|', ',') for n in name.split(',')] + self.addItem(authors_to_string(name)) + + self.set_separator('&') + self.set_space_before_sep(True) + self.update_items_cache(db.all_author_names()) + + au = db.authors(id_, index_is_id=True) + if not au: + au = _('Unknown') + self.current_val = [a.strip().replace('|', ',') for a in au.split(',')] + self.original_val = self.current_val + + def commit(self, db, id_): + authors = self.current_val + db.set_authors(id_, authors, notify=False) + return True + + @dynamic_property + def current_val(self): + + def fget(self): + au = unicode(self.text()).strip() + if not au: + au = self.get_default() + return string_to_authors(au) + + def fset(self, val): + if not val: + val = [self.get_default()] + self.setEditText(' & '.join([x.strip() for x in val])) + self.lineEdit().setCursorPosition(0) + + + return property(fget=fget, fset=fset) + +class AuthorSortEdit(EnLineEdit): + + TOOLTIP = _('Specify how the author(s) of this book should be sorted. ' + 'For example Charles Dickens should be sorted as Dickens, ' + 'Charles.\nIf the box is colored green, then text matches ' + 'the individual author\'s sort strings. If it is colored ' + 'red, then the authors and this text do not match.') + LABEL = _('Author s&ort:') + + def __init__(self, parent, authors_edit, autogen_button, db): + EnLineEdit.__init__(self, parent) + self.authors_edit = authors_edit + self.db = db + + base = self.TOOLTIP + ok_tooltip = '

' + textwrap.fill(base+'

'+ + _(' The green color indicates that the current ' + 'author sort matches the current author')) + bad_tooltip = '

'+textwrap.fill(base + '

'+ + _(' The red color indicates that the current ' + 'author sort does not match the current author. ' + 'No action is required if this is what you want.')) + self.tooltips = (ok_tooltip, bad_tooltip) + + self.authors_edit.editTextChanged.connect(self.update_state) + self.textChanged.connect(self.update_state) + + autogen_button.clicked.connect(self.auto_generate) + self.update_state() + + @dynamic_property + def current_val(self): + + def fget(self): + return unicode(self.text()).strip() + + def fset(self, val): + if not val: + val = '' + self.setText(val.strip()) + self.setCursorPosition(0) + + return property(fget=fget, fset=fset) + + def update_state(self, *args): + au = unicode(self.authors_edit.text()) + au = re.sub(r'\s+et al\.$', '', au) + au = self.db.author_sort_from_authors(string_to_authors(au)) + + normal = au == self.current_val + if normal: + col = 'rgb(0, 255, 0, 20%)' + else: + col = 'rgb(255, 0, 0, 20%)' + self.setStyleSheet('QLineEdit { color: black; ' + 'background-color: %s; }'%col) + tt = self.tooltips[0 if normal else 1] + self.setToolTip(tt) + self.setWhatsThis(tt) + + def auto_generate(self, *args): + au = unicode(self.authors_edit.text()) + au = re.sub(r'\s+et al\.$', '', au) + authors = string_to_authors(au) + self.current_val = self.db.author_sort_from_authors(authors) + + def initialize(self, db, id_): + self.current_val = db.author_sort(id_, index_is_id=True) + + def commit(self, db, id_): + aus = self.current_val + db.set_author_sort(id_, aus, notify=False, commit=False) + return True + +# }}} + +# Series {{{ +class SeriesEdit(EnComboBox): + + TOOLTIP = _('List of known series. You can add new series.') + LABEL = _('&Series:') + + def __init__(self, parent): + EnComboBox.__init__(self, parent) + self.dialog = parent + self.setSizeAdjustPolicy( + self.AdjustToMinimumContentsLengthWithIcon) + self.setToolTip(self.TOOLTIP) + self.setWhatsThis(self.TOOLTIP) + self.setEditable(True) + + @dynamic_property + def current_val(self): + + def fget(self): + return unicode(self.currentText()).strip() + + def fset(self, val): + if not val: + val = '' + self.setEditText(val.strip()) + self.setCursorPosition(0) + + return property(fget=fget, fset=fset) + + def initialize(self, db, id_): + all_series = db.all_series() + all_series.sort(key=lambda x : sort_key(x[1])) + series_id = db.series_id(id_, index_is_id=True) + idx, c = None, 0 + for i in all_series: + id, name = i + if id == series_id: + idx = c + self.addItem(name) + c += 1 + + self.lineEdit().setText('') + if idx is not None: + self.setCurrentIndex(idx) + self.original_val = self.current_val + + def commit(self, db, id_): + series = self.current_val + db.set_series(id_, series, notify=False, commit=True) + return True + +class SeriesIndexEdit(QDoubleSpinBox): + + TOOLTIP = '' + LABEL = _('&Number:') + + def __init__(self, parent, series_edit): + QDoubleSpinBox.__init__(self, parent) + self.dialog = parent + self.db = self.original_series_name = None + self.setMaximum(1000000) + self.series_edit = series_edit + series_edit.currentIndexChanged.connect(self.enable) + series_edit.editTextChanged.connect(self.enable) + series_edit.lineEdit().editingFinished.connect(self.increment) + self.enable() + + def enable(self, *args): + self.setEnabled(bool(self.series_edit.current_val)) + + @dynamic_property + def current_val(self): + + def fget(self): + return self.value() + + def fset(self, val): + if val is None: + val = 1.0 + val = float(val) + self.setValue(val) + + return property(fget=fget, fset=fset) + + def initialize(self, db, id_): + self.db = db + if self.series_edit.current_val: + val = db.series_index(id_, index_is_id=True) + else: + val = 1.0 + self.current_val = val + self.original_val = self.current_val + self.original_series_name = self.series_edit.original_val + + def commit(self, db, id_): + db.set_series_index(id_, self.current_val, notify=False, commit=False) + return True + + def increment(self): + if self.db is not None: + try: + series = self.series_edit.current_val + if series and series != self.original_series_name: + ns = 1.0 + if tweaks['series_index_auto_increment'] != 'const': + ns = self.db.get_next_series_num_for(series) + self.current_val = ns + self.original_series_name = series + except: + import traceback + traceback.print_exc() + + +# }}} + +class BuddyLabel(QLabel): + + def __init__(self, buddy): + QLabel.__init__(self, buddy.LABEL) + self.setBuddy(buddy) + self.setAlignment(Qt.AlignRight|Qt.AlignVCenter) + +class MetadataSingleDialog(ResizableDialog): + + def __init__(self, db, parent=None): + self.db = db + ResizableDialog.__init__(self, parent) + + def setupUi(self, *args): # {{{ + self.resize(990, 650) + + self.button_box = QDialogButtonBox( + QDialogButtonBox.Ok|QDialogButtonBox.Cancel, Qt.Horizontal, + self) + self.button_box.accepted.connect(self.accept) + self.button_box.rejected.connect(self.reject) + + self.scroll_area = QScrollArea(self) + self.scroll_area.setFrameShape(QScrollArea.NoFrame) + self.scroll_area.setWidgetResizable(True) + self.central_widget = QTabWidget(self) + self.scroll_area.setWidget(self.central_widget) + + self.l = QVBoxLayout(self) + self.setLayout(self.l) + self.l.setMargin(0) + self.l.addWidget(self.scroll_area) + self.l.addWidget(self.button_box) + + self.setWindowIcon(QIcon(I('edit_input.png'))) + self.setWindowTitle(_('Edit Meta Information')) + + self.create_basic_metadata_widgets() + + self.do_layout() + # }}} + + def create_basic_metadata_widgets(self): + self.basic_metadata_widgets = [] + # Title + self.title = TitleEdit(self) + self.deduce_title_sort_button = QToolButton(self) + self.deduce_title_sort_button.setToolTip( + _('Automatically create the title sort entry based on the current ' + 'title entry.\nUsing this button to create title sort will ' + 'change title sort from red to green.')) + self.deduce_title_sort_button.setWhatsThis( + self.deduce_title_sort_button.toolTip()) + self.title_sort = TitleSortEdit(self, self.title, + self.deduce_title_sort_button) + self.basic_metadata_widgets.extend([self.title, self.title_sort]) + + # Authors + self.authors = AuthorsEdit(self) + self.deduce_author_sort_button = QToolButton(self) + self.deduce_author_sort_button.setToolTip(_( + 'Automatically create the author sort entry based on the current' + ' author entry.\n' + 'Using this button to create author sort will change author sort from' + ' red to green.')) + self.author_sort = AuthorSortEdit(self, self.authors, + self.deduce_author_sort_button, db) + self.basic_metadata_widgets.extend([self.authors, self.author_sort]) + + self.swap_title_author_button = QToolButton(self) + self.swap_title_author_button.setIcon(QIcon(I('swap.png'))) + self.swap_title_author_button.setToolTip(_( + 'Swap the author and title')) + self.swap_title_author_button.clicked.connect(self.swap_title_author) + + self.series = SeriesEdit(self) + self.remove_unused_series_button = QToolButton(self) + self.remove_unused_series_button.setToolTip( + _('Remove unused series (Series that have no books)') ) + self.remove_unused_series_button.clicked.connect(self.remove_unused_series) + self.series_index = SeriesIndexEdit(self, self.series) + self.basic_metadata_widgets.extend([self.series, self.series_index]) + + def do_layout(self): + self.central_widget.clear() + self.tabs = [] + self.labels = [] + self.tabs.append(QWidget(self)) + self.central_widget.addTab(self.tabs[0], _("&Basic metadata")) + self.tabs[0].l = l = QVBoxLayout() + self.tabs[0].tl = tl = QGridLayout() + self.tabs[0].setLayout(l) + l.addLayout(tl) + + def create_row(row, one, two, three, col=1, icon='forward.png'): + ql = BuddyLabel(one) + tl.addWidget(ql, row, col+0, 1, 1) + self.labels.append(ql) + tl.addWidget(one, row, col+1, 1, 1) + if two is not None: + tl.addWidget(two, row, col+2, 1, 1) + two.setIcon(QIcon(I(icon))) + ql = BuddyLabel(three) + tl.addWidget(ql, row, col+3, 1, 1) + self.labels.append(ql) + tl.addWidget(three, row, col+4, 1, 1) + + tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1) + + create_row(0, self.title, self.deduce_title_sort_button, self.title_sort) + create_row(1, self.authors, self.deduce_author_sort_button, self.author_sort) + create_row(2, self.series, self.remove_unused_series_button, + self.series_index, icon='trash.png') + + + def __call__(self, id_, has_next=False, has_previous=False): + self.book_id = id_ + for widget in self.basic_metadata_widgets: + widget.initialize(self.db, id_) + + def swap_title_author(self, *args): + title = self.title.current_val + self.title.current_val = authors_to_string(self.authors.current_val) + self.authors.current_val = string_to_authors(title) + self.title_sort.auto_generate() + self.author_sort.auto_generate() + + def remove_unused_series(self, *args): + self.db.remove_unused_series() + idx = self.series.current_val + self.series.clear() + self.series.initialize(self.db, self.book_id) + if idx: + for i in range(self.series.count()): + if unicode(self.series.itemText(i)) == idx: + self.series.setCurrentIndex(i) + break + + +if __name__ == '__main__': + from PyQt4.Qt import QApplication + app = QApplication([]) + from calibre.library import db + db = db() + d = MetadataSingleDialog(db) + d(db.data[0][0]) + d.exec_() + diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index dd1121c725..fc21d9a3b3 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -123,6 +123,8 @@ IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'gif', 'png', 'bmp'] class FormatList(QListWidget): DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS + formats_dropped = pyqtSignal(object, object) + delete_format = pyqtSignal() @classmethod def paths_from_event(cls, event): @@ -146,15 +148,14 @@ class FormatList(QListWidget): def dropEvent(self, event): paths = self.paths_from_event(event) event.setDropAction(Qt.CopyAction) - self.emit(SIGNAL('formats_dropped(PyQt_PyObject,PyQt_PyObject)'), - event, paths) + self.formats_dropped.emit(event, paths) def dragMoveEvent(self, event): event.acceptProposedAction() def keyPressEvent(self, event): if event.key() == Qt.Key_Delete: - self.emit(SIGNAL('delete_format()')) + self.delete_format.emit() else: return QListWidget.keyPressEvent(self, event) diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index ae600a29f9..b52e3785bd 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -29,7 +29,6 @@ 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' ] @@ -605,43 +604,42 @@ class EPUB_MOBI(CatalogPlugin): "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), Option('--generate-authors', - default=True, + default=False, dest='generate_authors', action = 'store_true', - help=_("Include 'Authors' section in catalog." - "This switch is ignored - Books By Author section is always generated." + help=_("Include 'Authors' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), Option('--generate-descriptions', - default=True, + default=False, dest='generate_descriptions', action = 'store_true', - help=_("Include book descriptions in catalog.\n" + help=_("Include 'Descriptions' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), Option('--generate-genres', - default=True, + default=False, 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=True, + default=False, 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=True, + default=False, 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=True, + default=False, dest='generate_recently_added', action = 'store_true', help=_("Include 'Recently Added' section in catalog.\n" @@ -976,7 +974,7 @@ class EPUB_MOBI(CatalogPlugin): self.__thumbWidth = 0 self.__thumbHeight = 0 self.__title = opts.catalog_title - self.__totalSteps = 8.0 + self.__totalSteps = 6.0 self.__useSeriesPrefixInTitlesSection = False self.__verbose = opts.verbose @@ -1014,17 +1012,21 @@ class EPUB_MOBI(CatalogPlugin): (self.__archive_path, float(cached_thumb_width))) # Tweak build steps based on optional sections: 1 call for HTML, 1 for NCX + incremental_jobs = 0 + if self.opts.generate_authors: + incremental_jobs += 2 if self.opts.generate_titles: - self.__totalSteps += 2 + incremental_jobs += 2 if self.opts.generate_recently_added: - self.__totalSteps += 2 + incremental_jobs += 2 if self.generateRecentlyRead: - self.__totalSteps += 2 + incremental_jobs += 2 if self.opts.generate_series: - self.__totalSteps += 2 + incremental_jobs += 2 if self.opts.generate_descriptions: # +1 thumbs - self.__totalSteps += 3 + incremental_jobs += 3 + self.__totalSteps += incremental_jobs # Load section list templates templates = [] @@ -1358,13 +1360,21 @@ class EPUB_MOBI(CatalogPlugin): if self.opts.generate_descriptions: self.generateThumbnails() self.generateHTMLDescriptions() - self.generateHTMLByAuthor() + if self.opts.generate_authors: + self.generateHTMLByAuthor() if self.opts.generate_titles: self.generateHTMLByTitle() if self.opts.generate_series: self.generateHTMLBySeries() if self.opts.generate_genres: self.generateHTMLByTags() + # If this is the only Section, and there are no genres, bail + if self.opts.section_list == ['Genres'] and not self.genres: + error_msg = _("No Genres found to catalog.\nCheck 'Excluded genres'\nin E-book options.\n") + self.opts.log.error(error_msg) + self.error.append(_('No books available to catalog')) + self.error.append(error_msg) + return False if self.opts.generate_recently_added: self.generateHTMLByDateAdded() if self.generateRecentlyRead: @@ -1372,7 +1382,8 @@ class EPUB_MOBI(CatalogPlugin): self.generateOPF() self.generateNCXHeader() - self.generateNCXByAuthor("Authors") + if self.opts.generate_authors: + self.generateNCXByAuthor("Authors") if self.opts.generate_titles: self.generateNCXByTitle("Titles") if self.opts.generate_series: @@ -1508,7 +1519,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) for tag in exclude_tags: search_terms.append("tag:=%s" % tag) search_phrase = "not (%s)" % " or ".join(search_terms) - # If a list of ids are provided, don't use search_text if self.opts.ids: self.opts.search_text = search_phrase @@ -1879,7 +1889,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) # Link to author emTag = Tag(soup, "em") aTag = Tag(soup, "a") - aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author'])) + if self.opts.generate_authors: + aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author'])) aTag.insert(0, NavigableString(book['author'])) emTag.insert(0,aTag) pBookTag.insert(ptc, emTag) @@ -2149,7 +2160,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) pAuthorTag = Tag(soup, "p") pAuthorTag['class'] = "author_index" aTag = Tag(soup, "a") - aTag['name'] = "%s" % self.generateAuthorAnchor(current_author) + if self.opts.generate_authors: + aTag['name'] = "%s" % self.generateAuthorAnchor(current_author) aTag.insert(0,NavigableString(current_author)) pAuthorTag.insert(0,aTag) divTag.insert(dtc,pAuthorTag) @@ -2276,7 +2288,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) # Link to author emTag = Tag(soup, "em") aTag = Tag(soup, "a") - aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author'])) + if self.opts.generate_authors: + aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author'])) aTag.insert(0, NavigableString(new_entry['author'])) emTag.insert(0,aTag) pBookTag.insert(ptc, emTag) @@ -2425,7 +2438,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) # Link to author emTag = Tag(soup, "em") aTag = Tag(soup, "a") - aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author'])) + if self.opts.generate_authors: + aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author'])) aTag.insert(0, NavigableString(new_entry['author'])) emTag.insert(0,aTag) pBookTag.insert(ptc, emTag) @@ -2473,7 +2487,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) # Link to author emTag = Tag(soup, "em") aTag = Tag(soup, "a") - aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author'])) + if self.opts.generate_authors: + aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author'])) aTag.insert(0, NavigableString(new_entry['author'])) emTag.insert(0,aTag) pBookTag.insert(ptc, emTag) @@ -2692,7 +2707,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) # Link to author aTag = Tag(soup, "a") - aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", + if self.opts.generate_authors: + aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(escape(' & '.join(book['authors'])))) aTag.insert(0, NavigableString(' & '.join(book['authors']))) pBookTag.insert(ptc, aTag) @@ -3074,10 +3090,34 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) textTag.insert(0, NavigableString(self.title)) navLabelTag.insert(0, textTag) navPointTag.insert(0, navLabelTag) - contentTag = Tag(soup, 'content') - #contentTag['src'] = "content/book_%d.html" % int(self.booksByTitle[0]['id']) - contentTag['src'] = "content/ByAlphaAuthor.html" - navPointTag.insert(1, contentTag) + + if self.opts.generate_authors: + contentTag = Tag(soup, 'content') + contentTag['src'] = "content/ByAlphaAuthor.html" + navPointTag.insert(1, contentTag) + elif self.opts.generate_titles: + contentTag = Tag(soup, 'content') + contentTag['src'] = "content/ByAlphaTitle.html" + navPointTag.insert(1, contentTag) + elif self.opts.generate_series: + contentTag = Tag(soup, 'content') + contentTag['src'] = "content/BySeries.html" + navPointTag.insert(1, contentTag) + elif self.opts.generate_genres: + contentTag = Tag(soup, 'content') + contentTag['src'] = "content/ByGenres.html" + navPointTag.insert(1, contentTag) + elif self.opts.generate_recently_added: + contentTag = Tag(soup, 'content') + contentTag['src'] = "content/ByDateAdded.html" + navPointTag.insert(1, contentTag) + else: + sort_descriptions_by = self.booksByAuthor if self.opts.sort_descriptions_by_author \ + else self.booksByTitle + contentTag = Tag(soup, 'content') + contentTag['src'] = "content/book_%d.html" % int(sort_descriptions_by[0]['id']) + navPointTag.insert(1, contentTag) + cmiTag = Tag(soup, '%s' % 'calibre:meta-img') cmiTag['name'] = "mastheadImage" cmiTag['src'] = "images/mastheadImage.gif" @@ -4140,7 +4180,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) pAuthorTag = Tag(soup, "p") pAuthorTag['class'] = "author_index" aTag = Tag(soup, "a") - aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author'])) + if self.opts.generate_authors: + aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author'])) aTag.insert(0, book['author']) pAuthorTag.insert(0,aTag) divTag.insert(dtc,pAuthorTag) @@ -4371,7 +4412,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) # Insert the author link (always) aTag = body.find('a', attrs={'class':'author'}) - aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", + if self.opts.generate_authors: + aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author'])) if publisher == ' ': @@ -4860,6 +4902,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) opts.basename = "Catalog" opts.cli_environment = not hasattr(opts,'sync') + + # Hard-wired to always sort descriptions by author, with series after non-series opts.sort_descriptions_by_author = True build_log = [] @@ -4898,14 +4942,13 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) if opts_dict['ids']: build_log.append(" book count: %d" % len(opts_dict['ids'])) - ''' sections_list = [] if opts.generate_authors: sections_list.append('Authors') - ''' - sections_list = ['Authors'] if opts.generate_titles: sections_list.append('Titles') + if opts.generate_series: + sections_list.append('Series') if opts.generate_genres: sections_list.append('Genres') if opts.generate_recently_added: @@ -4913,7 +4956,21 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) if opts.generate_descriptions: sections_list.append('Descriptions') + if not sections_list: + if opts.cli_environment: + opts.log.warn('*** No Section switches specified, enabling all Sections ***') + opts.generate_authors = True + opts.generate_titles = True + opts.generate_series = True + opts.generate_genres = True + opts.generate_recently_added = True + opts.generate_descriptions = True + sections_list = ['Authors','Titles','Series','Genres','Recently Added','Descriptions'] + else: + opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***') + return ["No Included Sections","No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"] build_log.append(u" Sections: %s" % ', '.join(sections_list)) + opts.section_list = sections_list # Limit thumb_width to 1.0" - 2.0" try: @@ -4948,6 +5005,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1]) # Launch the Catalog builder catalog = self.CatalogBuilder(db, opts, self, report_progress=notification) + if opts.verbose: log.info(" Begin catalog source generation") catalog.createDirectoryStructure() diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 37d18ea329..3e382c8f10 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -108,8 +108,8 @@ Follow these steps to find the problem: * Make sure that you are connecting only a single device to your computer at a time. Do not have another |app| supported device like an iPhone/iPad etc. at the same time. * Make sure you are running the latest version of |app|. The latest version can always be downloaded from `the calibre website `_. - * Ensure your operating system is seeing the device. That is, the device should be mounted as a disk that you can access using Windows explorer or whatever the file management program on your computer is - * In calibre, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled. + * Ensure your operating system is seeing the device. That is, the device should be mounted as a disk that you can access using Windows explorer or whatever the file management program on your computer is. + * In calibre, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled, the plugin icon next to it should be green when it is enabled. * If all the above steps fail, go to Preferences->Miscellaneous and click debug device detection with your device attached and post the output as a ticket on `the calibre bug tracker `_. How does |app| manage collections on my SONY reader?