From a1b04a765c8586015324ce008f546e7b5319fd96 Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 11 Jan 2013 14:23:54 -0700 Subject: [PATCH 1/5] 1) Added uuid to OPF with id="uuid_id", matching style when converting epubs. 2) Fixed bug lp:1098325, improperly formatted ISBN field when exporting CSV. --- src/calibre/ebooks/metadata/opf2.py | 9 +++------ src/calibre/library/catalogs/csv_xml.py | 4 ++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 3e5d95f1ce..248c60e3a9 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -458,7 +458,6 @@ def serialize_user_metadata(metadata_elem, all_user_metadata, tail='\n'+(' '*8)) from calibre.utils.config import to_json from calibre.ebooks.metadata.book.json_codec import (object_to_unicode, encode_is_multiple) - for name, fm in all_user_metadata.items(): try: fm = copy.copy(fm) @@ -960,14 +959,13 @@ class OPF(object): # {{{ def fset(self, val): matches = self.uuid_id_path(self.metadata) if not matches: - attrib = {'{%s}scheme'%self.NAMESPACES['opf']: 'uuid'} + attrib = {'{%s}scheme'%self.NAMESPACES['opf']: 'uuid', 'id':'uuid_id'} matches = [self.create_metadata_element('identifier', attrib=attrib)] self.set_text(matches[0], unicode(val)) return property(fget=fget, fset=fset) - @dynamic_property def language(self): @@ -981,7 +979,6 @@ class OPF(object): # {{{ return property(fget=fget, fset=fset) - @dynamic_property def languages(self): @@ -1006,7 +1003,6 @@ class OPF(object): # {{{ return property(fget=fget, fset=fset) - @dynamic_property def book_producer(self): @@ -1152,13 +1148,14 @@ class OPF(object): # {{{ for attr in ('title', 'authors', 'author_sort', 'title_sort', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'category', 'comments', 'book_producer', - 'pubdate', 'user_categories', 'author_link_map'): + 'pubdate', 'user_categories', 'author_link_map','uuid'): val = getattr(mi, attr, None) if val is not None and val != [] and val != (None, None): setattr(self, attr, val) langs = getattr(mi, 'languages', []) if langs and langs != ['und']: self.languages = langs + self.get_identifiers = mi.get_identifiers temp = self.to_book_metadata() temp.smart_update(mi, replace_metadata=replace_metadata) self._user_metadata_ = temp.get_all_user_metadata(True) diff --git a/src/calibre/library/catalogs/csv_xml.py b/src/calibre/library/catalogs/csv_xml.py index fd2bb5113b..c594a346d4 100644 --- a/src/calibre/library/catalogs/csv_xml.py +++ b/src/calibre/library/catalogs/csv_xml.py @@ -133,8 +133,8 @@ class CSV_XML(CatalogPlugin): elif field in ['authors', 'tags']: item = ', '.join(item) elif field == 'isbn': - # Could be 9, 10 or 13 digits - item = u'%s' % re.sub(r'[\D]', '', item) + # Could be 9, 10 or 13 digits, with hyphens, possibly ending in 'X' + item = u'%s' % re.sub(r'[^\dX-]', '', item) elif field in ['pubdate', 'timestamp']: item = isoformat(item) elif field == 'comments': From 63a274dc32e4580eb46dbfd495d595826dbbd9da Mon Sep 17 00:00:00 2001 From: GRiker Date: Sat, 12 Jan 2013 05:00:55 -0700 Subject: [PATCH 2/5] Revised method adding with calibre uuid to ePub OPF files. --- src/calibre/ebooks/metadata/epub.py | 3 ++- src/calibre/ebooks/metadata/opf2.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index bc81df5a79..5b8f75c3b1 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -289,8 +289,9 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False): langs.append(lc) mi.languages = langs - reader.opf.smart_update(mi) + reader.opf.add_uuid_identifier(mi.uuid) + if apply_null: if not getattr(mi, 'series', None): reader.opf.series = None diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 248c60e3a9..5d853288de 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -458,6 +458,7 @@ def serialize_user_metadata(metadata_elem, all_user_metadata, tail='\n'+(' '*8)) from calibre.utils.config import to_json from calibre.ebooks.metadata.book.json_codec import (object_to_unicode, encode_is_multiple) + for name, fm in all_user_metadata.items(): try: fm = copy.copy(fm) @@ -959,13 +960,14 @@ class OPF(object): # {{{ def fset(self, val): matches = self.uuid_id_path(self.metadata) if not matches: - attrib = {'{%s}scheme'%self.NAMESPACES['opf']: 'uuid', 'id':'uuid_id'} + attrib = {'{%s}scheme'%self.NAMESPACES['opf']: 'uuid'} matches = [self.create_metadata_element('identifier', attrib=attrib)] self.set_text(matches[0], unicode(val)) return property(fget=fget, fset=fset) + @dynamic_property def language(self): @@ -979,6 +981,7 @@ class OPF(object): # {{{ return property(fget=fget, fset=fset) + @dynamic_property def languages(self): @@ -1003,6 +1006,7 @@ class OPF(object): # {{{ return property(fget=fget, fset=fset) + @dynamic_property def book_producer(self): @@ -1148,18 +1152,20 @@ class OPF(object): # {{{ for attr in ('title', 'authors', 'author_sort', 'title_sort', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'category', 'comments', 'book_producer', - 'pubdate', 'user_categories', 'author_link_map','uuid'): + 'pubdate', 'user_categories', 'author_link_map'): val = getattr(mi, attr, None) if val is not None and val != [] and val != (None, None): setattr(self, attr, val) langs = getattr(mi, 'languages', []) if langs and langs != ['und']: self.languages = langs - self.get_identifiers = mi.get_identifiers temp = self.to_book_metadata() temp.smart_update(mi, replace_metadata=replace_metadata) self._user_metadata_ = temp.get_all_user_metadata(True) + def add_uuid_identifier(self,uuid): + setattr(self,'uuid',uuid) + # }}} class OPFCreator(Metadata): From 498e090b1f5411b6b74a1b78365b756394d23b1f Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 12 Jan 2013 14:24:18 +0100 Subject: [PATCH 3/5] Allow the smart device client to ask that the UUID be used for file names instead of the template. --- src/calibre/devices/smart_device_app/driver.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index a9df1c0d94..e22a0142be 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -385,6 +385,14 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): fname = sanitize(fname) ext = os.path.splitext(fname)[1] + try: + # If the device asked for it, try to use the UUID as the file name. + # Fall back to the template if the UUID doesn't exist. + if self.client_wants_uuid_file_names and mdata.uuid: + return (mdata.uuid + ext) + except: + pass + maxlen = (self.MAX_PATH_LEN - (self.PATH_FUDGE_FACTOR + self.exts_path_lengths.get(ext, self.PATH_FUDGE_FACTOR))) @@ -845,6 +853,10 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self._close_device_socket() return False + self.client_wants_uuid_file_names = result.get('useUuidFileNames', False) + self._debug('Device wants UUID file names', self.client_wants_uuid_file_names) + + config = self._configProxy() config['format_map'] = exts self._debug('selected formats', config['format_map']) @@ -1253,6 +1265,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): self.connection_attempts = {} self.client_can_stream_books = False self.client_can_stream_metadata = False + self.client_wants_uuid_file_names = False self._debug("All IP addresses", get_all_ips()) From e25cb884fa4140d206d48c30ad9c6ab49868e59c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 13 Jan 2013 11:38:39 +0530 Subject: [PATCH 4/5] EPUB: Ensure that the calibre book uuid is present in the opf of EPUB files converted/exported from the GUI --- src/calibre/ebooks/metadata/epub.py | 2 ++ src/calibre/ebooks/metadata/opf2.py | 11 +++++------ src/calibre/ebooks/oeb/transforms/metadata.py | 5 ++++- src/calibre/gui2/convert/metadata.py | 1 + 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index bc81df5a79..e216610ad5 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -291,6 +291,8 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False): reader.opf.smart_update(mi) + if getattr(mi, 'uuid', None): + reader.opf.application_id = mi.uuid if apply_null: if not getattr(mi, 'series', None): reader.opf.series = None diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 3e5d95f1ce..92287589eb 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -941,12 +941,11 @@ class OPF(object): # {{{ return self.get_text(match) or None def fset(self, val): - matches = self.application_id_path(self.metadata) - if not matches: - attrib = {'{%s}scheme'%self.NAMESPACES['opf']: 'calibre'} - matches = [self.create_metadata_element('identifier', - attrib=attrib)] - self.set_text(matches[0], unicode(val)) + for x in tuple(self.application_id_path(self.metadata)): + x.getparent().remove(x) + attrib = {'{%s}scheme'%self.NAMESPACES['opf']: 'calibre'} + self.set_text(self.create_metadata_element( + 'identifier', attrib=attrib), unicode(val)) return property(fget=fget, fset=fset) diff --git a/src/calibre/ebooks/oeb/transforms/metadata.py b/src/calibre/ebooks/oeb/transforms/metadata.py index bfa52368a4..6da1e4c529 100644 --- a/src/calibre/ebooks/oeb/transforms/metadata.py +++ b/src/calibre/ebooks/oeb/transforms/metadata.py @@ -115,8 +115,11 @@ class MergeMetadata(object): if mi.uuid is not None: m.filter('identifier', lambda x:x.id=='uuid_id') self.oeb.metadata.add('identifier', mi.uuid, id='uuid_id', - scheme='uuid') + scheme='uuid') self.oeb.uid = self.oeb.metadata.identifier[-1] + if mi.application_id is not None: + m.filter('identifier', lambda x:x.scheme=='calibre') + self.oeb.metadata.add('identifier', mi.application_id, scheme='calibre') def set_cover(self, mi, prefer_metadata_cover): cdata, ext = '', 'jpg' diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index a2c1427b91..9af287f641 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -26,6 +26,7 @@ def create_opf_file(db, book_id): mi.application_id = uuid.uuid4() old_cover = mi.cover mi.cover = None + mi.application_id = mi.uuid raw = metadata_to_opf(mi) mi.cover = old_cover opf_file = PersistentTemporaryFile('.opf') From 1bc7ddfdbfbfd9dc96aa853c66265b2b2cc2527e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 13 Jan 2013 14:14:00 +0530 Subject: [PATCH 5/5] Content server: Allow picking a random book by clicking the 'Random book' link on the start page. You can also refresh the random book page to get a new random book --- src/calibre/library/server/browse.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/calibre/library/server/browse.py b/src/calibre/library/server/browse.py index 576f8801da..e315bb6f3e 100644 --- a/src/calibre/library/server/browse.py +++ b/src/calibre/library/server/browse.py @@ -239,6 +239,8 @@ class BrowseServer(object): self.browse_details) connect('browse_book', base_href+'/book/{id}', self.browse_book) + connect('browse_random', base_href+'/random', + self.browse_random) connect('browse_category_icon', base_href+'/icon/{name}', self.browse_icon) @@ -351,6 +353,7 @@ class BrowseServer(object): cats = [ (_('Newest'), 'newest', 'forward.png'), (_('All books'), 'allbooks', 'book.png'), + (_('Random book'), 'randombook', 'random.png'), ] def getter(x): @@ -599,6 +602,9 @@ class BrowseServer(object): elif category == 'allbooks': raise cherrypy.InternalRedirect(prefix + '/browse/matches/allbooks/dummy') + elif category == 'randombook': + raise cherrypy.InternalRedirect(prefix + + '/browse/random') else: ans = self.browse_category(category, category_sort) @@ -885,6 +891,13 @@ class BrowseServer(object): return json.dumps(ans, ensure_ascii=False) + @Endpoint() + def browse_random(self, *args, **kwargs): + import random + book_id = random.choice(tuple(self.db.all_ids())) + ans = self.browse_render_details(book_id) + return self.browse_template('').format( + title='', script='book();', main=ans) @Endpoint() def browse_book(self, id=None, category_sort=None):