diff --git a/resources/catalog/stylesheet.css b/resources/catalog/stylesheet.css index 4f9ca9ac41..4c5045d378 100644 --- a/resources/catalog/stylesheet.css +++ b/resources/catalog/stylesheet.css @@ -6,7 +6,7 @@ p.title { text-align:center; font-style:italic; font-size:xx-large; - border-bottom: solid black 4px; + border-bottom: solid black 2px; } p.author { @@ -17,6 +17,15 @@ p.author { font-size:large; } +p.author_index { + font-size:large; + font-weight:bold; + text-align:left; + margin-top:0px; + margin-bottom:-2px; + text-indent: 0em; + } + p.tags { margin-top:0em; margin-bottom:0em; @@ -47,19 +56,12 @@ p.letter_index { margin-bottom:0px; } -p.author_index { - font-size:large; - text-align:left; - margin-top:0px; - margin-bottom:0px; - text-indent: 0em; - } - p.series { - text-align: left; - margin-top:0px; + font-style:italic; + margin-top:2px; margin-bottom:0px; margin-left:2em; + text-align:left; text-indent:-2em; } @@ -87,11 +89,13 @@ p.date_read { text-indent:-6em; } -hr.series_divider { - width:50%; - margin-left:1em; - margin-top:0em; - margin-bottom:0em; +hr.description_divider { + width:90%; + margin-left:5%; + border-top: solid white 0px; + border-right: solid white 0px; + border-bottom: solid black 1px; + border-left: solid white 0px; } hr.annotations_divider { diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 238c491158..d89a7c45a3 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -294,7 +294,7 @@ class CatalogPlugin(Plugin): # {{{ # Return a list of requested fields, with opts.sort_by first all_fields = set( ['author_sort','authors','comments','cover','formats', - 'id','isbn','pubdate','publisher','rating', + 'id','isbn','ondevice','pubdate','publisher','rating', 'series_index','series','size','tags','timestamp', 'title','uuid']) @@ -306,6 +306,9 @@ class CatalogPlugin(Plugin): # {{{ else: fields = list(all_fields) + if not opts.connected_device['is_device_connected'] and 'ondevice' in fields: + fields.pop(int(fields.index('ondevice'))) + fields.sort() if opts.sort_by and opts.sort_by in fields: fields.insert(0,fields.pop(int(fields.index(opts.sort_by)))) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 916c88f203..afd7958265 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -2303,9 +2303,9 @@ class ITUNES(DriverBase): # Delete existing from Device|Books, add to self.update_list # for deletion from booklist[0] during add_books_to_metadata for book in self.cached_books: - if self.cached_books[book]['uuid'] == metadata.uuid and \ - self.cached_books[book]['title'] == metadata.title and \ - self.cached_books[book]['author'] == metadata.authors[0]: + if self.cached_books[book]['uuid'] == metadata.uuid or \ + (self.cached_books[book]['title'] == metadata.title and \ + self.cached_books[book]['author'] == metadata.authors[0]): self.update_list.append(self.cached_books[book]) self._remove_from_device(self.cached_books[book]) if DEBUG: @@ -2322,9 +2322,9 @@ class ITUNES(DriverBase): # Delete existing from Library|Books, add to self.update_list # for deletion from booklist[0] during add_books_to_metadata for book in self.cached_books: - if self.cached_books[book]['uuid'] == metadata.uuid and \ - self.cached_books[book]['title'] == metadata.title and \ - self.cached_books[book]['author'] == metadata.authors[0]: + if self.cached_books[book]['uuid'] == metadata.uuid or \ + (self.cached_books[book]['title'] == metadata.title and \ + self.cached_books[book]['author'] == metadata.authors[0]): self.update_list.append(self.cached_books[book]) self._remove_from_iTunes(self.cached_books[book]) if DEBUG: @@ -2488,7 +2488,7 @@ class ITUNES(DriverBase): zf_opf.close() # If 'News' in tags, tweak the title/author for friendlier display in iBooks - if _('News') in metadata.tags: + if _('News') or _('Catalog') in metadata.tags: if metadata.title.find('[') > 0: metadata.title = metadata.title[:metadata.title.find('[')-1] date_as_author = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) diff --git a/src/calibre/gui2/actions/catalog.py b/src/calibre/gui2/actions/catalog.py index 6feaec978d..77fa4755c1 100644 --- a/src/calibre/gui2/actions/catalog.py +++ b/src/calibre/gui2/actions/catalog.py @@ -26,14 +26,18 @@ class GenerateCatalogAction(InterfaceAction): rows = xrange(self.gui.library_view.model().rowCount(QModelIndex())) ids = map(self.gui.library_view.model().id, rows) - dbspec = None if not ids: return error_dialog(self.gui, _('No books selected'), _('No books selected to generate catalog for'), show=True) + db = self.gui.library_view.model().db + dbspec = {} + for id in ids: + dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)} + # Calling gui2.tools:generate_catalog() - ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager.device) + ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager) if ret is None: return diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py index 5acda0948c..1f0fdae7e2 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.py +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py @@ -19,6 +19,7 @@ class PluginWidget(QWidget,Ui_Form): OPTION_FIELDS = [('exclude_genre','\[.+\]'), ('exclude_tags','~,'+_('Catalog')), ('generate_titles', True), + ('generate_series', True), ('generate_recently_added', True), ('note_tag','*'), ('numbers_as_text', False), @@ -40,7 +41,7 @@ class PluginWidget(QWidget,Ui_Form): # 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 ['numbers_as_text','generate_titles','generate_recently_added']: + if opt[0] in ['numbers_as_text','generate_titles','generate_series','generate_recently_added']: getattr(self, opt[0]).setChecked(opt_value) else: getattr(self, opt[0]).setText(opt_value) @@ -52,13 +53,13 @@ class PluginWidget(QWidget,Ui_Form): # others store as lists opts_dict = {} for opt in self.OPTION_FIELDS: - if opt[0] in ['numbers_as_text','generate_titles','generate_recently_added']: + if opt[0] in ['numbers_as_text','generate_titles','generate_series','generate_recently_added']: opt_value = getattr(self,opt[0]).isChecked() else: opt_value = unicode(getattr(self, opt[0]).text()) gprefs.set(self.name + '_' + opt[0], opt_value) - if opt[0] in ['exclude_genre','numbers_as_text','generate_titles','generate_recently_added']: + if opt[0] in ['exclude_genre','numbers_as_text','generate_titles','generate_series','generate_recently_added']: opts_dict[opt[0]] = opt_value else: opts_dict[opt[0]] = opt_value.split(',') diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.ui b/src/calibre/gui2/catalog/catalog_epub_mobi.ui index cdf91eed6f..fa6b53e3a4 100644 --- a/src/calibre/gui2/catalog/catalog_epub_mobi.ui +++ b/src/calibre/gui2/catalog/catalog_epub_mobi.ui @@ -108,20 +108,27 @@ - + Include 'Recently Added' Section - + Sort numbers as text + + + + Include 'Series' Section + + + diff --git a/src/calibre/gui2/convert/gui_conversion.py b/src/calibre/gui2/convert/gui_conversion.py index 3072b8757a..69dabe28b8 100644 --- a/src/calibre/gui2/convert/gui_conversion.py +++ b/src/calibre/gui2/convert/gui_conversion.py @@ -29,6 +29,7 @@ def gui_catalog(fmt, title, dbspec, ids, out_file_name, sync, fmt_options, conne log = Log() from calibre.library import db db = db() + db.catalog_plugin_on_device_temp_mapping = dbspec # Create a minimal OptionParser that we can append to parser = OptionParser() diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index caef82ab81..7a516bb4ff 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -238,7 +238,7 @@ def fetch_scheduled_recipe(arg): return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt] -def generate_catalog(parent, dbspec, ids, device): +def generate_catalog(parent, dbspec, ids, device_manager): from calibre.gui2.dialogs.catalog import Catalog # Build the Catalog dialog in gui2.dialogs.catalog @@ -252,9 +252,18 @@ def generate_catalog(parent, dbspec, ids, device): # Profile the connected device # Parallel initialization in calibre.library.cli:command_catalog() - connected_device = { 'storage':None,'serial':None,'save_template':None,'name':None} + connected_device = { + 'is_device_connected': device_manager.is_device_connected, + 'kind': device_manager.connected_device_kind, + 'name': None, + 'save_template': None, + 'serial': None, + 'storage': None + } - if device: + if device_manager.is_device_connected: + device = device_manager.device + connected_device['name'] = device.gui_name try: storage = [] if device._main_prefix: @@ -263,11 +272,10 @@ def generate_catalog(parent, dbspec, ids, device): storage.append(os.path.join(device._card_a_prefix, device.EBOOK_DIR_CARD_A)) if device._card_b_prefix: storage.append(os.path.join(device._card_b_prefix, device.EBOOK_DIR_CARD_B)) - connected_device = { 'storage': storage, - 'serial': device.detected_device.serial if \ - hasattr(device.detected_device,'serial') else None, - 'save_template': device.save_template(), - 'name': device.gui_name} + connected_device['storage'] = storage + connected_device['serial'] = device.detected_device.serial if \ + hasattr(device.detected_device,'serial') else None + connected_device['save_template'] = device.save_template() except: pass diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 4637730116..bd2160aff1 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -20,7 +20,7 @@ from calibre.utils.date import isoformat, now as nowf from calibre.utils.logging import default_log as log FIELDS = ['all', 'author_sort', 'authors', 'comments', - 'cover', 'formats', 'id', 'isbn', 'pubdate', 'publisher', 'rating', + 'cover', 'formats', 'id', 'isbn', 'ondevice', 'pubdate', 'publisher', 'rating', 'series_index', 'series', 'size', 'tags', 'timestamp', 'title', 'uuid'] @@ -67,6 +67,8 @@ class CSV_XML(CatalogPlugin): if opts.verbose: opts_dict = vars(opts) log("%s(): Generating %s" % (self.name,self.fmt)) + if opts.connected_device['is_device_connected']: + log(" connected_device: %s" % opts.connected_device['name']) if opts_dict['search_text']: log(" --search='%s'" % opts_dict['search_text']) @@ -81,7 +83,6 @@ class CSV_XML(CatalogPlugin): else: log(" Fields: %s" % opts_dict['fields']) - # If a list of ids are provided, don't use search_text if opts.ids: opts.search_text = None @@ -95,6 +96,11 @@ class CSV_XML(CatalogPlugin): # Get the requested output fields as a list fields = self.get_output_fields(opts) + # If connected device, add 'On Device' values to data + if opts.connected_device['is_device_connected'] and 'ondevice' in fields: + for entry in data: + entry['ondevice'] = db.catalog_plugin_on_device_temp_mapping[entry['id']]['ondevice'] + if self.fmt == 'csv': outfile = codecs.open(path_to_output, 'w', 'utf8') @@ -140,10 +146,10 @@ class CSV_XML(CatalogPlugin): root.append(record) for field in ('id', 'uuid', 'title', 'publisher', 'rating', 'size', - 'isbn'): + 'isbn','ondevice'): if field in fields: val = r[field] - if val is None: + if not val: continue if not isinstance(val, (str, unicode)): val = unicode(val) @@ -561,6 +567,13 @@ class EPUB_MOBI(CatalogPlugin): help=_("Include 'Titles' section in catalog.\n" "Default: '%default'\n" "Applies to: ePub, MOBI output formats")), + Option('--generate-series', + 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=False, dest='generate_recently_added', @@ -886,304 +899,307 @@ class EPUB_MOBI(CatalogPlugin): self.__totalSteps += 2 if self.generateRecentlyRead: self.__totalSteps += 2 + if self.opts.generate_series: + self.__totalSteps += 2 + # Accessors - ''' - @dynamic_property - def xxxx(self): - def fget(self): - return self.__ - def fset(self, val): - self.__ = val - return property(fget=fget, fset=fset) - ''' + if True: + ''' + @dynamic_property + def xxxx(self): + def fget(self): + return self.__ + def fset(self, val): + self.__ = val + return property(fget=fget, fset=fset) + ''' + @dynamic_property + def authorClip(self): + def fget(self): + return self.__authorClip + def fset(self, val): + self.__authorClip = val + return property(fget=fget, fset=fset) + @dynamic_property + def authors(self): + def fget(self): + return self.__authors + def fset(self, val): + self.__authors = val + return property(fget=fget, fset=fset) + @dynamic_property + def basename(self): + def fget(self): + return self.__basename + def fset(self, val): + self.__basename = val + return property(fget=fget, fset=fset) + @dynamic_property + def bookmarked_books(self): + def fget(self): + return self.__bookmarked_books + def fset(self, val): + self.__bookmarked_books = val + return property(fget=fget, fset=fset) + @dynamic_property + def booksByAuthor(self): + def fget(self): + return self.__booksByAuthor + def fset(self, val): + self.__booksByAuthor = val + return property(fget=fget, fset=fset) + @dynamic_property + def booksByDateRead(self): + def fget(self): + return self.__booksByDateRead + def fset(self, val): + self.__booksByDateRead = val + return property(fget=fget, fset=fset) + @dynamic_property + def booksByTitle(self): + def fget(self): + return self.__booksByTitle + def fset(self, val): + self.__booksByTitle = val + return property(fget=fget, fset=fset) + @dynamic_property + def booksByTitle_noSeriesPrefix(self): + def fget(self): + return self.__booksByTitle_noSeriesPrefix + def fset(self, val): + self.__booksByTitle_noSeriesPrefix = val + return property(fget=fget, fset=fset) + @dynamic_property + def catalogPath(self): + def fget(self): + return self.__catalogPath + def fset(self, val): + self.__catalogPath = val + return property(fget=fget, fset=fset) + @dynamic_property + def contentDir(self): + def fget(self): + return self.__contentDir + def fset(self, val): + self.__contentDir = val + return property(fget=fget, fset=fset) + @dynamic_property + def currentStep(self): + def fget(self): + return self.__currentStep + def fset(self, val): + self.__currentStep = val + return property(fget=fget, fset=fset) + @dynamic_property + def creator(self): + def fget(self): + return self.__creator + def fset(self, val): + self.__creator = val + return property(fget=fget, fset=fset) + @dynamic_property + def db(self): + def fget(self): + return self.__db + return property(fget=fget) + @dynamic_property + def descriptionClip(self): + def fget(self): + return self.__descriptionClip + def fset(self, val): + self.__descriptionClip = val + return property(fget=fget, fset=fset) + @dynamic_property + def error(self): + def fget(self): + return self.__error + return property(fget=fget) + @dynamic_property + def generateForKindle(self): + def fget(self): + return self.__generateForKindle + def fset(self, val): + self.__generateForKindle = val + return property(fget=fget, fset=fset) + @dynamic_property + def generateRecentlyRead(self): + def fget(self): + return self.__generateRecentlyRead + def fset(self, val): + self.__generateRecentlyRead = val + return property(fget=fget, fset=fset) + @dynamic_property + def genres(self): + def fget(self): + return self.__genres + def fset(self, val): + self.__genres = val + return property(fget=fget, fset=fset) + @dynamic_property + def genre_tags_dict(self): + def fget(self): + return self.__genre_tags_dict + def fset(self, val): + self.__genre_tags_dict = val + return property(fget=fget, fset=fset) + @dynamic_property + def htmlFileList(self): + def fget(self): + return self.__htmlFileList + def fset(self, val): + self.__htmlFileList = val + return property(fget=fget, fset=fset) + @dynamic_property + def libraryPath(self): + def fget(self): + return self.__libraryPath + def fset(self, val): + self.__libraryPath = val + return property(fget=fget, fset=fset) + @dynamic_property + def markerTags(self): + def fget(self): + return self.__markerTags + def fset(self, val): + self.__markerTags = val + return property(fget=fget, fset=fset) + @dynamic_property + def ncxSoup(self): + def fget(self): + return self.__ncxSoup + def fset(self, val): + self.__ncxSoup = val + return property(fget=fget, fset=fset) + @dynamic_property + def opts(self): + def fget(self): + return self.__opts + return property(fget=fget) + @dynamic_property + def playOrder(self): + def fget(self): + return self.__playOrder + def fset(self,val): + self.__playOrder = val + return property(fget=fget, fset=fset) + @dynamic_property + def plugin(self): + def fget(self): + return self.__plugin + return property(fget=fget) + @dynamic_property + def progressInt(self): + def fget(self): + return self.__progressInt + def fset(self, val): + self.__progressInt = val + return property(fget=fget, fset=fset) + @dynamic_property + def progressString(self): + def fget(self): + return self.__progressString + def fset(self, val): + self.__progressString = val + return property(fget=fget, fset=fset) + @dynamic_property + def reporter(self): + def fget(self): + return self.__reporter + def fset(self, val): + self.__reporter = val + return property(fget=fget, fset=fset) + @dynamic_property + def stylesheet(self): + def fget(self): + return self.__stylesheet + def fset(self, val): + self.__stylesheet = val + return property(fget=fget, fset=fset) + @dynamic_property + def thumbs(self): + def fget(self): + return self.__thumbs + def fset(self, val): + self.__thumbs = val + return property(fget=fget, fset=fset) + def thumbWidth(self): + def fget(self): + return self.__thumbWidth + def fset(self, val): + self.__thumbWidth = val + return property(fget=fget, fset=fset) + def thumbHeight(self): + def fget(self): + return self.__thumbHeight + def fset(self, val): + self.__thumbHeight = val + return property(fget=fget, fset=fset) + @dynamic_property + def title(self): + def fget(self): + return self.__title + def fset(self, val): + self.__title = val + return property(fget=fget, fset=fset) + @dynamic_property + def totalSteps(self): + def fget(self): + return self.__totalSteps + return property(fget=fget) + @dynamic_property + def useSeriesPrefixInTitlesSection(self): + def fget(self): + return self.__useSeriesPrefixInTitlesSection + def fset(self, val): + self.__useSeriesPrefixInTitlesSection = val + return property(fget=fget, fset=fset) + @dynamic_property + def verbose(self): + def fget(self): + return self.__verbose + def fset(self, val): + self.__verbose = val + return property(fget=fget, fset=fset) - @dynamic_property - def authorClip(self): - def fget(self): - return self.__authorClip - def fset(self, val): - self.__authorClip = val - return property(fget=fget, fset=fset) - @dynamic_property - def authors(self): - def fget(self): - return self.__authors - def fset(self, val): - self.__authors = val - return property(fget=fget, fset=fset) - @dynamic_property - def basename(self): - def fget(self): - return self.__basename - def fset(self, val): - self.__basename = val - return property(fget=fget, fset=fset) - @dynamic_property - def bookmarked_books(self): - def fget(self): - return self.__bookmarked_books - def fset(self, val): - self.__bookmarked_books = val - return property(fget=fget, fset=fset) - @dynamic_property - def booksByAuthor(self): - def fget(self): - return self.__booksByAuthor - def fset(self, val): - self.__booksByAuthor = val - return property(fget=fget, fset=fset) - @dynamic_property - def booksByDateRead(self): - def fget(self): - return self.__booksByDateRead - def fset(self, val): - self.__booksByDateRead = val - return property(fget=fget, fset=fset) - @dynamic_property - def booksByTitle(self): - def fget(self): - return self.__booksByTitle - def fset(self, val): - self.__booksByTitle = val - return property(fget=fget, fset=fset) - @dynamic_property - def booksByTitle_noSeriesPrefix(self): - def fget(self): - return self.__booksByTitle_noSeriesPrefix - def fset(self, val): - self.__booksByTitle_noSeriesPrefix = val - return property(fget=fget, fset=fset) - @dynamic_property - def catalogPath(self): - def fget(self): - return self.__catalogPath - def fset(self, val): - self.__catalogPath = val - return property(fget=fget, fset=fset) - @dynamic_property - def contentDir(self): - def fget(self): - return self.__contentDir - def fset(self, val): - self.__contentDir = val - return property(fget=fget, fset=fset) - @dynamic_property - def currentStep(self): - def fget(self): - return self.__currentStep - def fset(self, val): - self.__currentStep = val - return property(fget=fget, fset=fset) - @dynamic_property - def creator(self): - def fget(self): - return self.__creator - def fset(self, val): - self.__creator = val - return property(fget=fget, fset=fset) - @dynamic_property - def db(self): - def fget(self): - return self.__db - return property(fget=fget) - @dynamic_property - def descriptionClip(self): - def fget(self): - return self.__descriptionClip - def fset(self, val): - self.__descriptionClip = val - return property(fget=fget, fset=fset) - @dynamic_property - def error(self): - def fget(self): - return self.__error - return property(fget=fget) - @dynamic_property - def generateForKindle(self): - def fget(self): - return self.__generateForKindle - def fset(self, val): - self.__generateForKindle = val - return property(fget=fget, fset=fset) - @dynamic_property - def generateRecentlyRead(self): - def fget(self): - return self.__generateRecentlyRead - def fset(self, val): - self.__generateRecentlyRead = val - return property(fget=fget, fset=fset) - @dynamic_property - def genres(self): - def fget(self): - return self.__genres - def fset(self, val): - self.__genres = val - return property(fget=fget, fset=fset) - @dynamic_property - def genre_tags_dict(self): - def fget(self): - return self.__genre_tags_dict - def fset(self, val): - self.__genre_tags_dict = val - return property(fget=fget, fset=fset) - @dynamic_property - def htmlFileList(self): - def fget(self): - return self.__htmlFileList - def fset(self, val): - self.__htmlFileList = val - return property(fget=fget, fset=fset) - @dynamic_property - def libraryPath(self): - def fget(self): - return self.__libraryPath - def fset(self, val): - self.__libraryPath = val - return property(fget=fget, fset=fset) - @dynamic_property - def markerTags(self): - def fget(self): - return self.__markerTags - def fset(self, val): - self.__markerTags = val - return property(fget=fget, fset=fset) - @dynamic_property - def ncxSoup(self): - def fget(self): - return self.__ncxSoup - def fset(self, val): - self.__ncxSoup = val - return property(fget=fget, fset=fset) - @dynamic_property - def opts(self): - def fget(self): - return self.__opts - return property(fget=fget) - @dynamic_property - def playOrder(self): - def fget(self): - return self.__playOrder - def fset(self,val): - self.__playOrder = val - return property(fget=fget, fset=fset) - @dynamic_property - def plugin(self): - def fget(self): - return self.__plugin - return property(fget=fget) - @dynamic_property - def progressInt(self): - def fget(self): - return self.__progressInt - def fset(self, val): - self.__progressInt = val - return property(fget=fget, fset=fset) - @dynamic_property - def progressString(self): - def fget(self): - return self.__progressString - def fset(self, val): - self.__progressString = val - return property(fget=fget, fset=fset) - @dynamic_property - def reporter(self): - def fget(self): - return self.__reporter - def fset(self, val): - self.__reporter = val - return property(fget=fget, fset=fset) - @dynamic_property - def stylesheet(self): - def fget(self): - return self.__stylesheet - def fset(self, val): - self.__stylesheet = val - return property(fget=fget, fset=fset) - @dynamic_property - def thumbs(self): - def fget(self): - return self.__thumbs - def fset(self, val): - self.__thumbs = val - return property(fget=fget, fset=fset) - def thumbWidth(self): - def fget(self): - return self.__thumbWidth - def fset(self, val): - self.__thumbWidth = val - return property(fget=fget, fset=fset) - def thumbHeight(self): - def fget(self): - return self.__thumbHeight - def fset(self, val): - self.__thumbHeight = val - return property(fget=fget, fset=fset) - @dynamic_property - def title(self): - def fget(self): - return self.__title - def fset(self, val): - self.__title = val - return property(fget=fget, fset=fset) - @dynamic_property - def totalSteps(self): - def fget(self): - return self.__totalSteps - return property(fget=fget) - @dynamic_property - def useSeriesPrefixInTitlesSection(self): - def fget(self): - return self.__useSeriesPrefixInTitlesSection - def fset(self, val): - self.__useSeriesPrefixInTitlesSection = val - return property(fget=fget, fset=fset) - @dynamic_property - def verbose(self): - def fget(self): - return self.__verbose - def fset(self, val): - self.__verbose = val - return property(fget=fget, fset=fset) - - @dynamic_property - def NOT_READ_SYMBOL(self): - def fget(self): - return '' if self.generateForKindle else \ - '%s' % self.opts.read_tag - return property(fget=fget) - @dynamic_property - def READING_SYMBOL(self): - def fget(self): - return '' if self.generateForKindle else \ - '%s' % self.opts.read_tag - return property(fget=fget) - @dynamic_property - def READ_SYMBOL(self): - def fget(self): - return '' if self.generateForKindle else \ - '%s' % self.opts.read_tag - return property(fget=fget) - @dynamic_property - def FULL_RATING_SYMBOL(self): - def fget(self): - return "★" if self.generateForKindle else "*" - return property(fget=fget) - @dynamic_property - def EMPTY_RATING_SYMBOL(self): - def fget(self): - return "☆" if self.generateForKindle else ' ' - return property(fget=fget) - @dynamic_property - def READ_PROGRESS_SYMBOL(self): - def fget(self): - return "▪" if self.generateForKindle else '+' - return property(fget=fget) - @dynamic_property - def UNREAD_PROGRESS_SYMBOL(self): - def fget(self): - return "▫" if self.generateForKindle else '-' - return property(fget=fget) + @dynamic_property + def NOT_READ_SYMBOL(self): + def fget(self): + return '' if self.generateForKindle else \ + '%s' % self.opts.read_tag + return property(fget=fget) + @dynamic_property + def READING_SYMBOL(self): + def fget(self): + return '' if self.generateForKindle else \ + '%s' % self.opts.read_tag + return property(fget=fget) + @dynamic_property + def READ_SYMBOL(self): + def fget(self): + return '' if self.generateForKindle else \ + '%s' % self.opts.read_tag + return property(fget=fget) + @dynamic_property + def FULL_RATING_SYMBOL(self): + def fget(self): + return "★" if self.generateForKindle else "*" + return property(fget=fget) + @dynamic_property + def EMPTY_RATING_SYMBOL(self): + def fget(self): + return "☆" if self.generateForKindle else ' ' + return property(fget=fget) + @dynamic_property + def READ_PROGRESS_SYMBOL(self): + def fget(self): + return "▪" if self.generateForKindle else '+' + return property(fget=fget) + @dynamic_property + def UNREAD_PROGRESS_SYMBOL(self): + def fget(self): + return "▫" if self.generateForKindle else '-' + return property(fget=fget) # Methods def buildSources(self): @@ -1196,6 +1212,8 @@ class EPUB_MOBI(CatalogPlugin): self.generateHTMLByAuthor() if self.opts.generate_titles: self.generateHTMLByTitle() + if self.opts.generate_series: + self.generateHTMLBySeries() if self.opts.generate_recently_added: self.generateHTMLByDateAdded() if self.generateRecentlyRead: @@ -1206,15 +1224,17 @@ class EPUB_MOBI(CatalogPlugin): self.generateOPF() self.generateNCXHeader() - self.generateNCXDescriptions("Descriptions") self.generateNCXByAuthor("Authors") if self.opts.generate_titles: self.generateNCXByTitle("Titles") + if self.opts.generate_series: + self.generateNCXBySeries("Series") if self.opts.generate_recently_added: self.generateNCXByDateAdded("Recently Added") if self.generateRecentlyRead: self.generateNCXByDateRead("Recently Read") self.generateNCXByGenre("Genres") + self.generateNCXDescriptions("Descriptions") self.writeNCX() return True @@ -1569,13 +1589,26 @@ class EPUB_MOBI(CatalogPlugin): emTag = Tag(soup, "em") if title['series']: # title
series series_index - brTag = Tag(soup,'br') - title_tokens = title['title'].split(': ') - emTag.insert(0, escape(NavigableString(title_tokens[1]))) - emTag.insert(1, brTag) - smallTag = Tag(soup,'small') - smallTag.insert(0, escape(NavigableString(title_tokens[0]))) - emTag.insert(2, smallTag) + 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'}) @@ -1724,17 +1757,17 @@ class EPUB_MOBI(CatalogPlugin): body.insert(btc, aTag) btc += 1 - ''' - # We don't need this because the Kindle shows section titles - #

By Title

- h2Tag = Tag(soup, "h2") - aTag = Tag(soup, "a") - aTag['name'] = "bytitle" - h2Tag.insert(0,aTag) - h2Tag.insert(1,NavigableString('By Title (%d)' % len(self.booksByTitle))) - body.insert(btc,h2Tag) - btc += 1 - ''' + if not self.__generateForKindle: + # We don't need this because the Kindle shows section titles + #

By Title

+ pTag = Tag(soup, "p") + pTag['class'] = 'title' + aTag = Tag(soup, "a") + aTag['name'] = "bytitle" + pTag.insert(0,aTag) + pTag.insert(1,NavigableString('Titles')) + body.insert(btc,pTag) + btc += 1 #

#

@@ -1742,13 +1775,13 @@ class EPUB_MOBI(CatalogPlugin): dtc = 0 current_letter = "" - # 2/14/10 7:11 AM Experimental: re-sort title list without leading series/series_index + # Re-sort title list without leading series/series_index if not self.useSeriesPrefixInTitlesSection: nspt = deepcopy(self.booksByTitle) for book in nspt: if book['series']: - tokens = book['title'].split(': ') - book['title'] = '%s (%s)' % (tokens[1], tokens[0]) + tokens = book['title'].partition(':') + book['title'] = '%s (%s)' % (tokens[2].strip(), tokens[0]) book['title_sort'] = self.generateSortTitle(book['title']) nspt = sorted(nspt, key=lambda x:(x['title_sort'].upper(), x['title_sort'].upper())) @@ -1835,7 +1868,7 @@ class EPUB_MOBI(CatalogPlugin): # Write books by author A-Z self.updateProgressFullStep("'Authors'") - friendly_name = "By Author" + friendly_name = "Authors" soup = self.generateHTMLEmptyHeader(friendly_name) body = soup.find('body') @@ -1906,15 +1939,14 @@ class EPUB_MOBI(CatalogPlugin): current_series = None pAuthorTag = Tag(soup, "p") pAuthorTag['class'] = "author_index" - emTag = Tag(soup, "em") aTag = Tag(soup, "a") aTag['name'] = "%s" % self.generateAuthorAnchor(current_author) aTag.insert(0,NavigableString(current_author)) - emTag.insert(0,aTag) - pAuthorTag.insert(0,emTag) + 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
@@ -1922,6 +1954,7 @@ class EPUB_MOBI(CatalogPlugin): hrTag['class'] = "series_divider" divTag.insert(dtc,hrTag) dtc += 1 + ''' # Check for series if book['series'] and book['series'] != current_series: @@ -1929,7 +1962,18 @@ class EPUB_MOBI(CatalogPlugin): current_series = book['series'] pSeriesTag = Tag(soup,'p') pSeriesTag['class'] = "series" - pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + book['series'])) + + if self.opts.generate_series: + aTag = Tag(soup,'a') + aTag['href'] = "%s.html#%s_series" % ('BySeries', + re.sub('\W','',book['series']).lower()) + aTag.insert(0, book['series']) + #pSeriesTag.insert(0, NavigableString(self.NOT_READ_SYMBOL)) + pSeriesTag.insert(0, aTag) + else: + #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 current_series and not book['series']: @@ -1957,30 +2001,34 @@ class EPUB_MOBI(CatalogPlugin): aTag = Tag(soup, "a") aTag['href'] = "book_%d.html" % (int(float(book['id']))) - # Use series, series index if avail else just title + # Use series, series index if avail else just title, + year of publication if current_series: - aTag.insert(0,escape(book['title'][len(book['series'])+1:])) + aTag.insert(0,'%s (%s)' % (escape(book['title'][len(book['series'])+1:]), + book['date'].split()[1])) else: - aTag.insert(0,escape(book['title'])) + aTag.insert(0,'%s (%s)' % (escape(book['title']), + book['date'].split()[1])) non_series_books += 1 pBookTag.insert(ptc, aTag) ptc += 1 + divTag.insert(dtc, pBookTag) dtc += 1 - ''' - # Insert the

tag with book_count at the head - #

By Author

- h2Tag = Tag(soup, "h2") - aTag = Tag(soup, "a") - anchor_name = friendly_name.lower() - aTag['name'] = anchor_name.replace(" ","") - h2Tag.insert(0,aTag) - h2Tag.insert(1,NavigableString('%s (%d)' % (friendly_name, book_count))) - body.insert(btc,h2Tag) - btc += 1 - ''' + if not self.__generateForKindle: + # Insert the

tag with book_count at the head + #

By Author

+ pTag = Tag(soup, "p") + pTag['class'] = 'title' + aTag = Tag(soup, "a") + anchor_name = friendly_name.lower() + aTag['name'] = anchor_name.replace(" ","") + pTag.insert(0,aTag) + #h2Tag.insert(1,NavigableString('%s (%d)' % (friendly_name, book_count))) + pTag.insert(1,NavigableString('%s' % (friendly_name))) + body.insert(btc,pTag) + btc += 1 # Add the divTag to the body body.insert(btc, divTag) @@ -2023,15 +2071,14 @@ class EPUB_MOBI(CatalogPlugin): current_series = None pAuthorTag = Tag(soup, "p") pAuthorTag['class'] = "author_index" - emTag = Tag(soup, "em") aTag = Tag(soup, "a") aTag['name'] = "%s" % self.generateAuthorAnchor(current_author) aTag.insert(0,NavigableString(current_author)) - emTag.insert(0,aTag) - pAuthorTag.insert(0,emTag) + 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 new_entry['series']: # Insert an
@@ -2039,6 +2086,7 @@ class EPUB_MOBI(CatalogPlugin): hrTag['class'] = "series_divider" divTag.insert(dtc,hrTag) dtc += 1 + ''' # Check for series if new_entry['series'] and new_entry['series'] != current_series: @@ -2046,7 +2094,14 @@ class EPUB_MOBI(CatalogPlugin): current_series = new_entry['series'] pSeriesTag = Tag(soup,'p') pSeriesTag['class'] = "series" - pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + new_entry['series'])) + if self.opts.generate_series: + aTag = Tag(soup,'a') + aTag['href'] = "%s.html#%s_series" % ('BySeries', + re.sub('\W','',new_entry['series']).lower()) + aTag.insert(0, new_entry['series']) + pSeriesTag.insert(0, aTag) + else: + pSeriesTag.insert(0,NavigableString('%s' % new_entry['series'])) divTag.insert(dtc,pSeriesTag) dtc += 1 if current_series and not new_entry['series']: @@ -2160,18 +2215,18 @@ class EPUB_MOBI(CatalogPlugin): aTag['name'] = anchor_name.replace(" ","") body.insert(btc, aTag) btc += 1 - ''' - # We don't need this because the kindle inserts section titles - #

By Author

- h2Tag = Tag(soup, "h2") - aTag = Tag(soup, "a") - anchor_name = friendly_name.lower() - aTag['name'] = anchor_name.replace(" ","") - h2Tag.insert(0,aTag) - h2Tag.insert(1,NavigableString('%s' % friendly_name)) - body.insert(btc,h2Tag) - btc += 1 - ''' + + if not self.__generateForKindle: + #

By Author

+ pTag = Tag(soup, "p") + pTag['class'] = 'title' + aTag = Tag(soup, "a") + anchor_name = friendly_name.lower() + aTag['name'] = anchor_name.replace(" ","") + pTag.insert(0,aTag) + pTag.insert(1,NavigableString('%s' % friendly_name)) + body.insert(btc,pTag) + btc += 1 #

#

@@ -2186,14 +2241,13 @@ class EPUB_MOBI(CatalogPlugin): nspt = deepcopy(self.booksByTitle) for book in nspt: if book['series']: - tokens = book['title'].split(': ') - book['title'] = '%s (%s)' % (tokens[1], tokens[0]) + tokens = book['title'].partition(':') + book['title'] = '%s (%s)' % (tokens[2].strip(), tokens[0]) book['title_sort'] = self.generateSortTitle(book['title']) self.booksByDateRange = sorted(nspt, key=lambda x:(x['timestamp'], x['timestamp']),reverse=True) date_range_list = [] today_time = nowf().replace(hour=23, minute=59, second=59) - books_added_in_date_range = False for (i, date) in enumerate(self.DATE_RANGE): date_range_limit = self.DATE_RANGE[i] if i: @@ -2206,18 +2260,20 @@ class EPUB_MOBI(CatalogPlugin): delta = today_time-book_time if delta.days <= date_range_limit: date_range_list.append(book) - books_added_in_date_range = True else: break dtc = add_books_to_HTML_by_date_range(date_range_list, date_range, dtc) date_range_list = [book] + ''' if books_added_in_date_range: # Add an


separating date ranges from months hrTag = Tag(soup,'hr') + hrTag['class'] = "description_divider" divTag.insert(dtc,hrTag) dtc += 1 + ''' # >>>> Books by month <<<< # Sort titles case-insensitive for by month using series prefix @@ -2437,6 +2493,174 @@ class EPUB_MOBI(CatalogPlugin): outfile.close() self.htmlFileList.append("content/ByDateRead.html") + def generateHTMLBySeries(self): + ''' + Generate a list of series + ''' + self.updateProgressFullStep("Fetching series") + + self.opts.sort_by = 'series' + + # Merge opts.exclude_tags with opts.search_text + # Updated to use exact match syntax + empty_exclude_tags = False if len(self.opts.exclude_tags) else True + search_phrase = 'series:true ' + if not empty_exclude_tags: + exclude_tags = self.opts.exclude_tags.split(',') + search_terms = [] + 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 + else: + if self.opts.search_text: + self.opts.search_text += " " + search_phrase + else: + self.opts.search_text = search_phrase + + # Fetch the database as a dictionary + self.booksBySeries = self.plugin.search_sort_db(self.db, self.opts) + + friendly_name = "Series" + + soup = self.generateHTMLEmptyHeader(friendly_name) + body = soup.find('body') + + btc = 0 + + # Insert section tag + aTag = Tag(soup,'a') + aTag['name'] = 'section_start' + body.insert(btc, aTag) + btc += 1 + + # Insert the anchor + aTag = Tag(soup, "a") + anchor_name = friendly_name.lower() + aTag['name'] = anchor_name.replace(" ","") + body.insert(btc, aTag) + btc += 1 + + #

+ #

+ divTag = Tag(soup, "div") + dtc = 0 + current_letter = "" + current_series = None + + # Loop through booksBySeries + series_count = 0 + for book in self.booksBySeries: + # 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") + pIndexTag['class'] = "letter_index" + aTag = Tag(soup, "a") + aTag['name'] = "%s_series" % self.letter_or_symbol(current_letter) + pIndexTag.insert(0,aTag) + pIndexTag.insert(1,NavigableString(self.letter_or_symbol(sort_title[0].upper()))) + divTag.insert(dtc,pIndexTag) + dtc += 1 + + # Check for series change + if book['series'] != current_series: + # Start a new series + series_count += 1 + current_series = book['series'] + pSeriesTag = Tag(soup,'p') + pSeriesTag['class'] = "series" + aTag = Tag(soup, 'a') + aTag['name'] = "%s_series" % re.sub('\W','',book['series']).lower() + pSeriesTag.insert(0,aTag) + pSeriesTag.insert(1,NavigableString(self.NOT_READ_SYMBOL + '%s' % book['series'])) + divTag.insert(dtc,pSeriesTag) + dtc += 1 + + # Add books + pBookTag = Tag(soup, "p") + ptc = 0 + + # book with read/reading/unread symbol + if 'read' in book and book['read']: + # check mark + pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL)) + pBookTag['class'] = "read_book" + ptc += 1 + elif book['id'] in self.bookmarked_books: + pBookTag.insert(ptc,NavigableString(self.READING_SYMBOL)) + pBookTag['class'] = "read_book" + ptc += 1 + else: + # hidden check mark + pBookTag['class'] = "unread_book" + pBookTag.insert(ptc,NavigableString(self.NOT_READ_SYMBOL)) + ptc += 1 + + aTag = Tag(soup, "a") + 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']))) + + # Link to book + aTag.insert(0,'%d. %s (%s)' % (book['series_index'], + escape(book['title']), + strftime(u'%Y', book['pubdate'].timetuple()))) + pBookTag.insert(ptc, aTag) + ptc += 1 + + # · + pBookTag.insert(ptc, NavigableString(' · ')) + ptc += 1 + + # Link to author + aTag = Tag(soup, "a") + aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", + self.generateAuthorAnchor(escape(' & '.join(book['authors'])))) + aTag.insert(0, NavigableString(' & '.join(book['authors']))) + pBookTag.insert(ptc, aTag) + ptc += 1 + + divTag.insert(dtc, pBookTag) + dtc += 1 + + if not self.__generateForKindle: + # Insert the

tag with book_count at the head + #

By Series

+ pTag = Tag(soup, "p") + pTag['class'] = 'title' + aTag = Tag(soup, "a") + anchor_name = friendly_name.lower() + aTag['name'] = anchor_name.replace(" ","") + pTag.insert(0,aTag) + #h2Tag.insert(1,NavigableString('%s (%d)' % (friendly_name, series_count))) + pTag.insert(1,NavigableString('%s' % friendly_name)) + body.insert(btc,pTag) + btc += 1 + + # Add the divTag to the body + body.insert(btc, divTag) + + # Write the generated file to contentdir + outfile_spec = "%s/BySeries.html" % (self.contentDir) + outfile = open(outfile_spec, 'w') + outfile.write(soup.prettify()) + outfile.close() + self.htmlFileList.append("content/BySeries.html") + def generateHTMLByTags(self): # Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ... # Note that special tags - ~+*[] - have already been filtered from books[] @@ -2683,22 +2907,7 @@ class EPUB_MOBI(CatalogPlugin): # HTML files - add books to manifest and spine sort_descriptions_by = self.booksByAuthor if self.opts.sort_descriptions_by_author \ else self.booksByTitle - for book in sort_descriptions_by: - # manifest - itemTag = Tag(soup, "item") - itemTag['href'] = "content/book_%d.html" % int(book['id']) - itemTag['id'] = "book%d" % int(book['id']) - itemTag['media-type'] = "application/xhtml+xml" - manifest.insert(mtc, itemTag) - mtc += 1 - - # spine - itemrefTag = Tag(soup, "itemref") - itemrefTag['idref'] = "book%d" % int(book['id']) - spine.insert(stc, itemrefTag) - stc += 1 - - # Add other html_files to manifest and spine + # Add html_files to manifest and spine for file in self.htmlFileList: itemTag = Tag(soup, "item") @@ -2734,6 +2943,21 @@ class EPUB_MOBI(CatalogPlugin): spine.insert(stc, itemrefTag) stc += 1 + for book in sort_descriptions_by: + # manifest + itemTag = Tag(soup, "item") + itemTag['href'] = "content/book_%d.html" % int(book['id']) + itemTag['id'] = "book%d" % int(book['id']) + itemTag['media-type'] = "application/xhtml+xml" + manifest.insert(mtc, itemTag) + mtc += 1 + + # spine + itemrefTag = Tag(soup, "itemref") + itemrefTag['idref'] = "book%d" % int(book['id']) + spine.insert(stc, itemrefTag) + stc += 1 + # Guide referenceTag = Tag(soup, "reference") referenceTag['type'] = 'masthead' @@ -2769,7 +2993,8 @@ class EPUB_MOBI(CatalogPlugin): 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/book_%d.html" % int(self.booksByTitle[0]['id']) + contentTag['src'] = "content/ByAlphaAuthor.html" navPointTag.insert(1, contentTag) cmiTag = Tag(soup, '%s' % 'calibre:meta-img') cmiTag['name'] = "mastheadImage" @@ -2821,15 +3046,15 @@ class EPUB_MOBI(CatalogPlugin): navLabelTag = Tag(ncx_soup, "navLabel") textTag = Tag(ncx_soup, "text") if book['series']: - tokens = book['title'].split(': ') + tokens = list(book['title'].partition(':')) if self.generateForKindle: # Don't include Author for Kindle textTag.insert(0, NavigableString(self.formatNCXText('%s (%s)' % \ - (tokens[1], tokens[0]), dest='title'))) + (tokens[2].strip(), tokens[0]), dest='title'))) else: # Include Author for non-Kindle textTag.insert(0, NavigableString(self.formatNCXText('%s · %s (%s)' % \ - (tokens[1], book['author'], tokens[0]), dest='title'))) + (tokens[2].strip(), book['author'], tokens[0]), dest='title'))) else: if self.generateForKindle: # Don't include Author for Kindle @@ -2882,6 +3107,98 @@ class EPUB_MOBI(CatalogPlugin): self.ncxSoup = ncx_soup + def generateNCXBySeries(self, tocTitle): + self.updateProgressFullStep("NCX 'Series'") + + def add_to_series_by_letter(current_series_list): + current_series_list = " • ".join(current_series_list) + current_series_list = self.formatNCXText(current_series_list, dest="description") + series_by_letter.append(current_series_list) + + soup = self.ncxSoup + output = "BySeries" + body = soup.find("navPoint") + btc = len(body.contents) + + # --- Construct the 'Books By Series' section --- + navPointTag = Tag(soup, 'navPoint') + navPointTag['class'] = "section" + navPointTag['id'] = "byseries-ID" + navPointTag['playOrder'] = self.playOrder + self.playOrder += 1 + navLabelTag = Tag(soup, 'navLabel') + textTag = Tag(soup, 'text') + textTag.insert(0, NavigableString(tocTitle)) + navLabelTag.insert(0, textTag) + nptc = 0 + navPointTag.insert(nptc, navLabelTag) + nptc += 1 + contentTag = Tag(soup,"content") + contentTag['src'] = "content/%s.html#section_start" % (output) + navPointTag.insert(nptc, contentTag) + nptc += 1 + + series_by_letter = [] + + # Loop over the series titles, find start of each letter, add description_preview_count books + # Special switch for using different title list + title_list = self.booksBySeries + current_letter = self.letter_or_symbol(title_list[0]['series'][0]) + title_letters = [current_letter] + current_series_list = [] + current_series = "" + for book in title_list: + sort_title = self.generateSortTitle(book['series']) + if self.letter_or_symbol(sort_title[0]) != current_letter: + # Save the old list + add_to_series_by_letter(current_series_list) + + # Start the new list + current_letter = self.letter_or_symbol(sort_title[0]) + title_letters.append(current_letter) + current_series = book['series'] + current_series_list = [book['series']] + else: + if len(current_series_list) < self.descriptionClip and \ + book['series'] != current_series : + current_series = book['series'] + current_series_list.append(book['series']) + + # Add the last book list + add_to_series_by_letter(current_series_list) + + # Add *article* entries for each populated series title letter + for (i,books) in enumerate(series_by_letter): + navPointByLetterTag = Tag(soup, 'navPoint') + navPointByLetterTag['class'] = "article" + navPointByLetterTag['id'] = "%sSeries-ID" % (title_letters[i].upper()) + navPointTag['playOrder'] = self.playOrder + self.playOrder += 1 + navLabelTag = Tag(soup, 'navLabel') + textTag = Tag(soup, 'text') + textTag.insert(0, NavigableString(u"Series beginning with %s" % \ + (title_letters[i] if len(title_letters[i])>1 else "'" + title_letters[i] + "'"))) + navLabelTag.insert(0, textTag) + navPointByLetterTag.insert(0,navLabelTag) + contentTag = Tag(soup, 'content') + contentTag['src'] = "content/%s.html#%s_series" % (output, title_letters[i]) + navPointByLetterTag.insert(1,contentTag) + + if self.generateForKindle: + cmTag = Tag(soup, '%s' % 'calibre:meta') + cmTag['name'] = "description" + cmTag.insert(0, NavigableString(self.formatNCXText(books, dest='description'))) + navPointByLetterTag.insert(2, cmTag) + + navPointTag.insert(nptc, navPointByLetterTag) + nptc += 1 + + # Add this section to the body + body.insert(btc, navPointTag) + btc += 1 + + self.ncxSoup = soup + def generateNCXByTitle(self, tocTitle): self.updateProgressFullStep("NCX 'Titles'") @@ -3713,7 +4030,7 @@ class EPUB_MOBI(CatalogPlugin): btc += 1 titleTag = body.find(attrs={'class':'title'}) - titleTag.insert(0,NavigableString('%s' % escape(self.getFriendlyGenreTag(genre)))) + titleTag.insert(0,NavigableString('%s' % escape(self.getFriendlyGenreTag(genre)))) # Insert the books by author list divTag = body.find(attrs={'class':'authors'}) @@ -3729,15 +4046,14 @@ class EPUB_MOBI(CatalogPlugin): current_series = None pAuthorTag = Tag(soup, "p") pAuthorTag['class'] = "author_index" - emTag = Tag(soup, "em") aTag = Tag(soup, "a") aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author'])) aTag.insert(0, book['author']) - emTag.insert(0,aTag) - pAuthorTag.insert(0,emTag) + 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
@@ -3745,6 +4061,7 @@ class EPUB_MOBI(CatalogPlugin): hrTag['class'] = "series_divider" divTag.insert(dtc,hrTag) dtc += 1 + ''' # Check for series if book['series'] and book['series'] != current_series: @@ -3752,7 +4069,14 @@ class EPUB_MOBI(CatalogPlugin): current_series = book['series'] pSeriesTag = Tag(soup,'p') pSeriesTag['class'] = "series" - pSeriesTag.insert(0,NavigableString(self.NOT_READ_SYMBOL + book['series'])) + if self.opts.generate_series: + aTag = Tag(soup,'a') + aTag['href'] = "%s.html#%s_series" % ('BySeries', + re.sub('\W','',book['series']).lower()) + aTag.insert(0, book['series']) + pSeriesTag.insert(0, aTag) + else: + pSeriesTag.insert(0,NavigableString('%s' % book['series'])) divTag.insert(dtc,pSeriesTag) dtc += 1 @@ -3809,7 +4133,7 @@ class EPUB_MOBI(CatalogPlugin): def generateHTMLDescriptionHeader(self, title): title_border = '' if self.opts.fmt == 'epub' else \ - '

' + '
' header = ''' @@ -3855,7 +4179,7 @@ class EPUB_MOBI(CatalogPlugin):   -

+
@@ -3897,7 +4221,7 @@ class EPUB_MOBI(CatalogPlugin):

-

+
@@ -4049,6 +4373,12 @@ class EPUB_MOBI(CatalogPlugin): except: self.opts.log.error("generateThumbnail(): Error with %s" % title['title']) + def getFriendlyGenreTag(self, genre): + # Find the first instance of friendly_tag matching genre + for friendly_tag in self.genre_tags_dict: + if self.genre_tags_dict[friendly_tag] == genre: + return friendly_tag + def getMarkerTags(self): ''' Return a list of special marker tags to be excluded from genre list ''' markerTags = [] @@ -4063,12 +4393,6 @@ class EPUB_MOBI(CatalogPlugin): else: return char - def getFriendlyGenreTag(self, genre): - # Find the first instance of friendly_tag matching genre - for friendly_tag in self.genre_tags_dict: - if self.genre_tags_dict[friendly_tag] == genre: - return friendly_tag - def markdownComments(self, comments): ''' Convert random comment text to normalized, xml-legal block of

s @@ -4224,13 +4548,15 @@ class EPUB_MOBI(CatalogPlugin): opts.fmt = self.fmt = path_to_output.rpartition('.')[2] # Add local options - opts.creator = "calibre" + opts.creator = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) + opts.creator_sort_as = '%s %s' % ('calibre', strftime('%Y-%m-%d')) opts.connected_kindle = False # Finalize output_profile op = opts.output_profile if op is None: op = 'default' + if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower(): opts.connected_kindle = True if opts.connected_device['serial'] and opts.connected_device['serial'][:4] in ['B004','B005']: @@ -4256,7 +4582,8 @@ class EPUB_MOBI(CatalogPlugin): opts.exclude_genre = '\[^.\]' build_log.append(" converting empty exclude_genre to '\[^.\]'") - if opts.connected_device['name']: + if opts.connected_device['is_device_connected'] and \ + opts.connected_device['kind'] == 'device': if opts.connected_device['serial']: build_log.append(u" connected_device: '%s' #%s%s " % \ (opts.connected_device['name'], @@ -4267,9 +4594,14 @@ class EPUB_MOBI(CatalogPlugin): build_log.append(u" mount point: %s" % storage) else: build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) - for storage in opts.connected_device['storage']: - if storage: - build_log.append(u" mount point: %s" % storage) + try: + for storage in opts.connected_device['storage']: + if storage: + build_log.append(u" mount point: %s" % storage) + except: + build_log.append(u" (no mount points)") + else: + build_log.append(u" connected_device: '%s'" % opts.connected_device['name']) opts_dict = vars(opts) if opts_dict['ids']: diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index c17911cc3a..0bd0610ab0 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -674,7 +674,14 @@ def command_catalog(args, dbpath): # No support for connected device in CLI environment # Parallel initialization in calibre.gui2.tools:generate_catalog() - opts.connected_device = { 'storage':None,'serial':None,'save_template':None,'name':None} + opts.connected_device = { + 'is_device_connected': False, + 'kind': None, + 'name': None, + 'save_template': None, + 'serial': None, + 'storage': None, + } with plugin: plugin.run(args[1], opts, get_db(dbpath, opts))