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()) 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') 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': 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):