From 730bbb2d09980fe8a835ad360eced33a3d31ea10 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Oct 2010 08:45:14 -0600 Subject: [PATCH 01/10] EPUB Input: Handle EPUB files with multiple OPF files. Fixes #7229 (E-book Viewer crash: "IndexError:list index out of range") --- src/calibre/ebooks/epub/input.py | 34 ++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/epub/input.py b/src/calibre/ebooks/epub/input.py index 30a3327b63..ec2004d81c 100644 --- a/src/calibre/ebooks/epub/input.py +++ b/src/calibre/ebooks/epub/input.py @@ -108,6 +108,27 @@ class EPUBInput(InputFormatPlugin): open('calibre_raster_cover.jpg', 'wb').write( renderer) + def find_opf(self): + def attr(n, attr): + for k, v in n.attrib.items(): + if k.endswith(attr): + return v + try: + with open('META-INF/container.xml') as f: + root = etree.fromstring(f.read()) + for r in root.xpath('//*[local-name()="rootfile"]'): + if attr(r, 'media-type') != "application/oebps-package+xml": + continue + path = attr(r, 'full-path') + if not path: + continue + path = os.path.join(os.getcwdu(), *path.split('/')) + if os.path.exists(path): + return path + except: + import traceback + traceback.print_exc() + def convert(self, stream, options, file_ext, log, accelerators): from calibre.utils.zipfile import ZipFile from calibre import walk @@ -116,12 +137,13 @@ class EPUBInput(InputFormatPlugin): zf = ZipFile(stream) zf.extractall(os.getcwd()) encfile = os.path.abspath(os.path.join('META-INF', 'encryption.xml')) - opf = None - for f in walk(u'.'): - if f.lower().endswith('.opf') and '__MACOSX' not in f and \ - not os.path.basename(f).startswith('.'): - opf = os.path.abspath(f) - break + opf = self.find_opf() + if opf is None: + for f in walk(u'.'): + if f.lower().endswith('.opf') and '__MACOSX' not in f and \ + not os.path.basename(f).startswith('.'): + opf = os.path.abspath(f) + break path = getattr(stream, 'name', 'stream') if opf is None: From 973afc073333c6cccbb10284cc688319dce68c38 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Oct 2010 11:34:46 -0600 Subject: [PATCH 02/10] Content server: Make /mobile a little prettier --- resources/content_server/mobile.css | 17 +++++++++++++++++ src/calibre/library/server/mobile.py | 21 +++++++++++---------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/resources/content_server/mobile.css b/resources/content_server/mobile.css index a887684841..28d12bb6db 100644 --- a/resources/content_server/mobile.css +++ b/resources/content_server/mobile.css @@ -1,5 +1,9 @@ /* CSS for the mobile version of the content server webpage */ +.body { + font-family: sans-serif; +} + .navigation table.buttons { width: 100%; } @@ -85,4 +89,17 @@ div.navigation { clear: both; } +.data-container { + display: inline-block; + vertical-align: middle; +} +.first-line { + font-size: larger; + font-weight: bold; +} + +.second-line { + margin-top: 0.75ex; + display: block; +} diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py index b9ca24a823..7c2f959131 100644 --- a/src/calibre/library/server/mobile.py +++ b/src/calibre/library/server/mobile.py @@ -112,7 +112,6 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS): CLASS('thumbnail')) data = TD() - last = None for fmt in book['formats'].split(','): a = ascii_filename(book['authors']) t = ascii_filename(book['title']) @@ -124,9 +123,11 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS): ), CLASS('button')) s.tail = u'' - last = s data.append(s) + div = DIV(CLASS('data-container')) + data.append(div) + series = u'[%s - %s]'%(book['series'], book['series_index']) \ if book['series'] else '' tags = u'Tags=[%s]'%book['tags'] if book['tags'] else '' @@ -137,13 +138,13 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS): if val: ctext += '%s=[%s] '%tuple(val.split(':#:')) - text = u'\u202f%s %s by %s - %s - %s %s %s' % (book['title'], series, - book['authors'], book['size'], book['timestamp'], tags, ctext) - - if last is None: - data.text = text - else: - last.tail += text + first = SPAN(u'\u202f%s %s by %s' % (book['title'], series, + book['authors']), CLASS('first-line')) + div.append(first) + second = SPAN(u'%s - %s %s %s' % ( book['size'], + book['timestamp'], + tags, ctext), CLASS('second-line')) + div.append(second) bookt.append(TR(thumbnail, data)) # }}} @@ -229,7 +230,7 @@ class MobileServer(object): no_tag_count=True) book['title'] = record[FM['title']] for x in ('timestamp', 'pubdate'): - book[x] = strftime('%Y/%m/%d %H:%M:%S', record[FM[x]]) + book[x] = strftime('%b, %Y', record[FM[x]]) book['id'] = record[FM['id']] books.append(book) for key in CKEYS: From 7dc8e70b711e5bf2a1fdbbdbe522b88ac31c2b9d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Oct 2010 14:33:19 -0600 Subject: [PATCH 03/10] /browse: Fix sorting on custom cols. Also specify sort order explicitly when sorting on boolean columns --- src/calibre/library/caches.py | 4 ++++ src/calibre/library/server/browse.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index c22f9e00b0..300ddbac0b 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -816,6 +816,10 @@ class SortKeyGenerator(object): if val is None: val = '' val = val.lower() + + elif dt == 'bool': + val = {True: 1, False: 2, None: 3}.get(val, 3) + yield val # }}} diff --git a/src/calibre/library/server/browse.py b/src/calibre/library/server/browse.py index ea86de4c1b..d8d67c3824 100644 --- a/src/calibre/library/server/browse.py +++ b/src/calibre/library/server/browse.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' import operator, os, json from binascii import hexlify, unhexlify -from urllib import quote +from urllib import quote, unquote import cherrypy @@ -482,6 +482,8 @@ class BrowseServer(object): @Endpoint(sort_type='list') def browse_matches(self, category=None, cid=None, list_sort=None): + if list_sort: + list_sort = unquote(list_sort) if not cid: raise cherrypy.HTTPError(404, 'invalid category id: %r'%cid) categories = self.categories_cache() From 1dd72e682c9825f57331bfb511674d66184d821b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Oct 2010 15:12:18 -0600 Subject: [PATCH 04/10] USBMS drivers: Pass full filepath of ebook file to the upload_cover function --- src/calibre/devices/cybook/driver.py | 2 +- src/calibre/devices/hanvon/driver.py | 4 ++-- src/calibre/devices/misc.py | 2 +- src/calibre/devices/nook/driver.py | 2 +- src/calibre/devices/usbms/driver.py | 10 ++++++---- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/calibre/devices/cybook/driver.py b/src/calibre/devices/cybook/driver.py index d314646a87..7c436a7d0e 100644 --- a/src/calibre/devices/cybook/driver.py +++ b/src/calibre/devices/cybook/driver.py @@ -42,7 +42,7 @@ class CYBOOK(USBMS): DELETE_EXTS = ['.mbp', '.dat', '.bin', '_6090.t2b', '.thn'] SUPPORTS_SUB_DIRS = True - def upload_cover(self, path, filename, metadata): + def upload_cover(self, path, filename, metadata, filepath): coverdata = getattr(metadata, 'thumbnail', None) if coverdata and coverdata[2]: coverdata = coverdata[2] diff --git a/src/calibre/devices/hanvon/driver.py b/src/calibre/devices/hanvon/driver.py index 7933b9885d..1fe18afc58 100644 --- a/src/calibre/devices/hanvon/driver.py +++ b/src/calibre/devices/hanvon/driver.py @@ -77,7 +77,7 @@ class ALEX(N516): name = os.path.splitext(os.path.basename(file_abspath))[0] + '.png' return os.path.join(base, 'covers', name) - def upload_cover(self, path, filename, metadata): + def upload_cover(self, path, filename, metadata, filepath): from calibre.ebooks import calibre_cover from calibre.utils.magick.draw import thumbnail coverdata = getattr(metadata, 'thumbnail', None) @@ -129,7 +129,7 @@ class AZBOOKA(ALEX): def can_handle(self, device_info, debug=False): return not is_alex(device_info) - def upload_cover(self, path, filename, metadata): + def upload_cover(self, path, filename, metadata, filepath): pass class EB511(USBMS): diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index a1c9b790e4..bca4e8ec52 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -102,7 +102,7 @@ class PDNOVEL(USBMS): DELETE_EXTS = ['.jpg', '.jpeg', '.png'] - def upload_cover(self, path, filename, metadata): + def upload_cover(self, path, filename, metadata, filepath): coverdata = getattr(metadata, 'thumbnail', None) if coverdata and coverdata[2]: with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py index f697ee5202..a809b2c08a 100644 --- a/src/calibre/devices/nook/driver.py +++ b/src/calibre/devices/nook/driver.py @@ -45,7 +45,7 @@ class NOOK(USBMS): DELETE_EXTS = ['.jpg'] SUPPORTS_SUB_DIRS = True - def upload_cover(self, path, filename, metadata): + def upload_cover(self, path, filename, metadata, filepath): try: from PIL import Image, ImageDraw Image, ImageDraw diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index a83a8eb0ea..2f26c4a353 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -186,7 +186,8 @@ class USBMS(CLI, Device): self.put_file(infile, filepath, replace_file=True) try: self.upload_cover(os.path.dirname(filepath), - os.path.splitext(os.path.basename(filepath))[0], mdata) + os.path.splitext(os.path.basename(filepath))[0], + mdata, filepath) except: # Failure to upload cover is not catastrophic import traceback traceback.print_exc() @@ -197,14 +198,15 @@ class USBMS(CLI, Device): debug_print('USBMS: finished uploading %d books'%(len(files))) return zip(paths, cycle([on_card])) - def upload_cover(self, path, filename, metadata): + def upload_cover(self, path, filename, metadata, filepath): ''' Upload book cover to the device. Default implementation does nothing. - :param path: the full path were the associated book is located. - :param filename: the name of the book file without the extension. + :param path: The full path to the directory where the associated book is located. + :param filename: The name of the book file without the extension. :param metadata: metadata belonging to the book. Use metadata.thumbnail for cover + :param filepath: The full path to the ebook file ''' pass From 5fb294486babed1e2471940a4ebee97cc199601f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Oct 2010 16:40:39 -0600 Subject: [PATCH 05/10] SONY driver: Add support for uploading covers and periodicals --- src/calibre/devices/prs505/__init__.py | 6 ++ src/calibre/devices/prs505/driver.py | 49 +++++++-- src/calibre/devices/prs505/sony_cache.py | 128 ++++++++++++++++++++++- 3 files changed, 173 insertions(+), 10 deletions(-) diff --git a/src/calibre/devices/prs505/__init__.py b/src/calibre/devices/prs505/__init__.py index 20f3b8d49b..48b7d98123 100644 --- a/src/calibre/devices/prs505/__init__.py +++ b/src/calibre/devices/prs505/__init__.py @@ -2,5 +2,11 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' MEDIA_XML = 'database/cache/media.xml' +MEDIA_EXT = 'database/cache/cacheExt.xml' CACHE_XML = 'Sony Reader/database/cache.xml' +CACHE_EXT = 'Sony Reader/database/cacheExt.xml' + +MEDIA_THUMBNAIL = 'database/thumbnail' +CACHE_THUMBNAIL = 'Sony Reader/database/thumbnail' + diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index bb62e4dc76..3bcf7715a2 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -9,10 +9,10 @@ Device driver for the SONY devices import os, time, re from calibre.devices.usbms.driver import USBMS, debug_print -from calibre.devices.prs505 import MEDIA_XML -from calibre.devices.prs505 import CACHE_XML +from calibre.devices.prs505 import MEDIA_XML, MEDIA_EXT, CACHE_XML, CACHE_EXT, \ + MEDIA_THUMBNAIL, CACHE_THUMBNAIL from calibre.devices.prs505.sony_cache import XMLCache -from calibre import __appname__ +from calibre import __appname__, prints from calibre.devices.usbms.books import CollectionsBookList class PRS505(USBMS): @@ -66,6 +66,8 @@ class PRS505(USBMS): plugboard = None plugboard_func = None + THUMBNAIL_HEIGHT = 200 + def windows_filter_pnp_id(self, pnp_id): return '_LAUNCHER' in pnp_id @@ -116,20 +118,21 @@ class PRS505(USBMS): return fname def initialize_XML_cache(self): - paths, prefixes = {}, {} - for prefix, path, source_id in [ - ('main', MEDIA_XML, 0), - ('card_a', CACHE_XML, 1), - ('card_b', CACHE_XML, 2) + paths, prefixes, ext_paths = {}, {}, {} + for prefix, path, ext_path, source_id in [ + ('main', MEDIA_XML, MEDIA_EXT, 0), + ('card_a', CACHE_XML, CACHE_EXT, 1), + ('card_b', CACHE_XML, CACHE_EXT, 2) ]: prefix = getattr(self, '_%s_prefix'%prefix) if prefix is not None and os.path.exists(prefix): paths[source_id] = os.path.join(prefix, *(path.split('/'))) + ext_paths[source_id] = os.path.join(prefix, *(ext_path.split('/'))) prefixes[source_id] = prefix d = os.path.dirname(paths[source_id]) if not os.path.exists(d): os.makedirs(d) - return XMLCache(paths, prefixes, self.settings().use_author_sort) + return XMLCache(paths, ext_paths, prefixes, self.settings().use_author_sort) def books(self, oncard=None, end_session=True): debug_print('PRS505: starting fetching books for card', oncard) @@ -174,3 +177,31 @@ class PRS505(USBMS): def set_plugboards(self, plugboards, pb_func): self.plugboards = plugboards self.plugboard_func = pb_func + + def upload_cover(self, path, filename, metadata, filepath): + if metadata.thumbnail and metadata.thumbnail[-1]: + path = path.replace('/', os.sep) + is_main = path.startswith(self._main_prefix) + thumbnail_dir = MEDIA_THUMBNAIL if is_main else CACHE_THUMBNAIL + prefix = None + if is_main: + prefix = self._main_prefix + else: + if self._card_a_prefix and \ + path.startswith(self._card_a_prefix): + prefix = self._card_a_prefix + elif self._card_b_prefix and \ + path.startswith(self._card_b_prefix): + prefix = self._card_b_prefix + if prefix is None: + prints('WARNING: Failed to find prefix for:', filepath) + return + thumbnail_dir = os.path.join(prefix, *thumbnail_dir.split('/')) + + relpath = os.path.relpath(filepath, prefix) + thumbnail_dir = os.path.join(thumbnail_dir, relpath) + if not os.path.exists(thumbnail_dir): + os.makedirs(thumbnail_dir) + with open(os.path.join(thumbnail_dir, 'main_thumbnail.jpg'), 'wb') as f: + f.write(metadata.thumbnail[-1]) + diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index ce24dcd03f..e272cd6372 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -9,6 +9,7 @@ import os, time from base64 import b64decode from uuid import uuid4 from lxml import etree +from datetime import date from calibre import prints, guess_type, isbytestring from calibre.devices.errors import DeviceError @@ -18,6 +19,20 @@ from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.metadata import authors_to_string, title_sort, \ authors_to_sort_string +''' +cahceExt.xml + +Periodical identifier sample from a PRS-650: + + + + + main_thumbnail.jpg + + + +''' + # Utility functions {{{ EMPTY_CARD_CACHE = '''\ @@ -25,6 +40,12 @@ EMPTY_CARD_CACHE = '''\ ''' +EMPTY_EXT_CACHE = '''\ + + + +''' + MIME_MAP = { "lrf" : "application/x-sony-bbeb", 'lrx' : 'application/x-sony-bbeb', @@ -63,7 +84,7 @@ def uuid(): class XMLCache(object): - def __init__(self, paths, prefixes, use_author_sort): + def __init__(self, paths, ext_paths, prefixes, use_author_sort): if DEBUG: debug_print('Building XMLCache...', paths) self.paths = paths @@ -85,6 +106,7 @@ class XMLCache(object): if os.access(path, os.R_OK): with open(path, 'rb') as f: raw = f.read() + self.roots[source_id] = etree.fromstring(xml_to_unicode( raw, strip_encoding_pats=True, assume_utf8=True, verbose=DEBUG)[0], @@ -93,6 +115,25 @@ class XMLCache(object): raise Exception(('The SONY database at %s is corrupted. Try ' ' disconnecting and reconnecting your reader.')%path) + self.ext_paths, self.ext_roots = {}, {} + for source_id, path in ext_paths.items(): + if not os.path.exists(path): + try: + with open(path, 'wb') as f: + f.write(EMPTY_EXT_CACHE) + except: + pass + if os.access(path, os.W_OK): + try: + with open(path, 'rb') as f: + self.ext_roots[source_id] = etree.fromstring( + xml_to_unicode(f.read(), + strip_encoding_pats=True, assume_utf8=True, + verbose=DEBUG)[0], parser=parser) + self.ext_paths[source_id] = path + except: + pass + # }}} recs = self.roots[0].xpath('//*[local-name()="records"]') @@ -352,12 +393,18 @@ class XMLCache(object): debug_print('Updating XML Cache:', i) root = self.record_roots[i] lpath_map = self.build_lpath_map(root) + ext_root = self.ext_roots[i] if i in self.ext_roots else None + ext_lpath_map = None + if ext_root is not None: + ext_lpath_map = self.build_lpath_map(ext_root) gtz_count = ltz_count = 0 use_tz_var = False for book in booklist: path = os.path.join(self.prefixes[i], *(book.lpath.split('/'))) record = lpath_map.get(book.lpath, None) + created = False if record is None: + created = True record = self.create_text_record(root, i, book.lpath) if plugboard is not None: newmi = book.deepcopy_metadata() @@ -373,6 +420,13 @@ class XMLCache(object): if book.device_collections is None: book.device_collections = [] book.device_collections = playlist_map.get(book.lpath, []) + + if created and ext_root is not None and \ + ext_lpath_map.get(book.lpath, None) is None: + ext_record = self.create_ext_text_record(ext_root, i, + book.lpath, book.thumbnail) + self.periodicalize_book(book, ext_record) + debug_print('Timezone votes: %d GMT, %d LTZ, use_tz_var=%s'% (gtz_count, ltz_count, use_tz_var)) self.update_playlists(i, root, booklist, collections_attributes) @@ -386,6 +440,47 @@ class XMLCache(object): self.fix_ids() debug_print('Finished update') + def is_sony_periodical(self, book): + if _('News') not in book.tags: + return False + if not book.lpath.lower().endswith('.epub'): + return False + if book.pubdate.date() < date(2010, 10, 17): + return False + return True + + def periodicalize_book(self, book, record): + if not self.is_sony_periodical(book): + return + record.set('conformsTo', + "http://xmlns.sony.net/e-book/prs/periodicals/1.0/newspaper/1.0") + + record.set('description', '') + + name = None + if '[' in book.title: + name = book.title.split('[')[0].strip() + if len(name) < 4: + name = None + if not name: + try: + name = [t for t in book.tags if t != _('News')][0] + except: + name = None + + if not name: + name = book.title + + record.set('periodicalName', name) + + try: + pubdate = strftime(book.pubdate.utctimetuple(), + zone=lambda x : x) + record.set('publicationDate', pubdate) + except: + pass + + def rebuild_collections(self, booklist, bl_index): if bl_index not in self.record_roots: return @@ -472,6 +567,25 @@ class XMLCache(object): root.append(ans) return ans + def create_ext_text_record(self, root, bl_id, lpath, thumbnail): + namespace = root.nsmap[None] + attrib = { 'path': lpath } + ans = root.makeelement('{%s}text'%namespace, attrib=attrib, + nsmap=root.nsmap) + ans.tail = '\n' + root[-1].tail = '\n' + '\t' + root.append(ans) + if thumbnail and thumbnail[-1]: + ans.text = '\n' + '\t\t' + t = root.makeelement('{%s}thumbnail'%namespace, + attrib={'width':str(thumbnail[0]), 'height':str(thumbnail[1])}, + nsmap=root.nsmap) + t.text = 'main_thumbnail.jpg' + ans.append(t) + t.tail = '\n\t' + return ans + + def update_text_record(self, record, book, path, bl_index, gtz_count, ltz_count, use_tz_var): ''' @@ -589,6 +703,18 @@ class XMLCache(object): '') with open(path, 'wb') as f: f.write(raw) + + for i, path in self.ext_paths.items(): + try: + raw = etree.tostring(self.ext_roots[i], encoding='UTF-8', + xml_declaration=True) + except: + continue + raw = raw.replace("", + '') + with open(path, 'wb') as f: + f.write(raw) + # }}} # Utility methods {{{ From 2bd9f63f29248eba346c22eeb691977591d24069 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Oct 2010 16:53:51 -0600 Subject: [PATCH 06/10] Linux device mounting: Mount the drive with the lowest kernel name as main memory --- src/calibre/devices/prs505/sony_cache.py | 6 +++--- src/calibre/devices/usbms/device.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index e272cd6372..15245d3cd5 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -97,8 +97,8 @@ class XMLCache(object): for source_id, path in paths.items(): if source_id == 0: if not os.path.exists(path): - raise DeviceError('The SONY XML cache media.xml does not exist. Try' - ' disconnecting and reconnecting your reader.') + raise DeviceError(('The SONY XML cache %r does not exist. Try' + ' disconnecting and reconnecting your reader.')%repr(path)) with open(path, 'rb') as f: raw = f.read() else: @@ -112,7 +112,7 @@ class XMLCache(object): verbose=DEBUG)[0], parser=parser) if self.roots[source_id] is None: - raise Exception(('The SONY database at %s is corrupted. Try ' + raise Exception(('The SONY database at %r is corrupted. Try ' ' disconnecting and reconnecting your reader.')%path) self.ext_paths, self.ext_roots = {}, {} diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 6f938cbcbd..aa4f0d06f4 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -523,7 +523,8 @@ class Device(DeviceConfig, DevicePlugin): devnodes.append(node) devnodes += list(repeat(None, 3)) - ans = tuple(['/dev/'+x if ok.get(x, False) else None for x in devnodes[:3]]) + ans = ['/dev/'+x if ok.get(x, False) else None for x in devnodes[:3]] + ans.sort(key=lambda x: x[5:] if x else 'zzzzz') return self.linux_swap_drives(ans) def linux_swap_drives(self, drives): From 8b0b1312e311cc4bc2eaa4d65bee0f6c96aa2b13 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Oct 2010 17:19:54 -0600 Subject: [PATCH 07/10] Linux device drivers: Fix udisks based ejecting for devices with multiple nodes --- src/calibre/devices/udisks.py | 20 ++++++-------- src/calibre/devices/usbms/device.py | 41 +++++++++++++++++------------ 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/calibre/devices/udisks.py b/src/calibre/devices/udisks.py index ba26c2b56c..d79b626f36 100644 --- a/src/calibre/devices/udisks.py +++ b/src/calibre/devices/udisks.py @@ -5,8 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import dbus -import os +import dbus, os def node_mountpoint(node): @@ -56,15 +55,6 @@ class UDisks(object): parent = device_node_path while parent[-1] in '0123456789': parent = parent[:-1] - devices = [str(x) for x in self.main.EnumerateDeviceFiles()] - for d in devices: - if d.startswith(parent) and d != parent: - try: - self.unmount(d) - except: - import traceback - print 'Failed to unmount:', d - traceback.print_exc() d = self.device(parent) d.DriveEject([]) @@ -76,13 +66,19 @@ def eject(node_path): u = UDisks() u.eject(node_path) +def umount(node_path): + u = UDisks() + u.unmount(node_path) + if __name__ == '__main__': import sys dev = sys.argv[1] print 'Testing with node', dev u = UDisks() print 'Mounted at:', u.mount(dev) - print 'Ejecting' + print 'Unmounting' + u.unmount(dev) + print 'Ejecting:' u.eject(dev) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index aa4f0d06f4..94744c521f 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -733,24 +733,31 @@ class Device(DeviceConfig, DevicePlugin): pass def eject_linux(self): - try: - from calibre.devices.udisks import eject - return eject(self._linux_main_device_node) - except: - pass - drives = self.find_device_nodes() + from calibre.devices.udisks import eject, umount + drives = [d for d in self.find_device_nodes() if d] + for d in drives: + try: + umount(d) + except: + pass + for d in drives: + try: + eject(d) + except Exception, e: + print 'Udisks eject call for:', d, 'failed:' + print '\t', str(e) + for drive in drives: - if drive: - cmd = 'calibre-mount-helper' - if getattr(sys, 'frozen_path', False): - cmd = os.path.join(sys.frozen_path, cmd) - cmd = [cmd, 'eject'] - mp = getattr(self, "_linux_mount_map", {}).get(drive, - 'dummy/')[:-1] - try: - subprocess.Popen(cmd + [drive, mp]).wait() - except: - pass + cmd = 'calibre-mount-helper' + if getattr(sys, 'frozen_path', False): + cmd = os.path.join(sys.frozen_path, cmd) + cmd = [cmd, 'eject'] + mp = getattr(self, "_linux_mount_map", {}).get(drive, + 'dummy/')[:-1] + try: + subprocess.Popen(cmd + [drive, mp]).wait() + except: + pass def eject(self): if islinux: From 4906e1fc420fc160e90fc4e329feaf2c2ee9eb7f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Oct 2010 17:21:15 -0600 Subject: [PATCH 08/10] ... --- src/calibre/devices/usbms/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 94744c521f..f085ab8989 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -745,7 +745,7 @@ class Device(DeviceConfig, DevicePlugin): eject(d) except Exception, e: print 'Udisks eject call for:', d, 'failed:' - print '\t', str(e) + print '\t', e for drive in drives: cmd = 'calibre-mount-helper' From 5a8e4b2174e95527fa5b6a09c496a17015b4c222 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Oct 2010 18:26:37 -0600 Subject: [PATCH 09/10] ... --- resources/recipes/cacm.recipe | 74 +++++++++++++++++------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/resources/recipes/cacm.recipe b/resources/recipes/cacm.recipe index 1618bae742..e4af9d2024 100644 --- a/resources/recipes/cacm.recipe +++ b/resources/recipes/cacm.recipe @@ -1,37 +1,37 @@ -import datetime -from calibre.web.feeds.news import BasicNewsRecipe - -class AdvancedUserRecipe1286242553(BasicNewsRecipe): - title = u'CACM' - oldest_article = 7 - max_articles_per_feed = 100 - needs_subscription = True - feeds = [(u'CACM', u'http://cacm.acm.org/magazine.rss')] - language = 'en' - __author__ = 'jonmisurda' - no_stylesheets = True - remove_tags = [ - dict(name='div', attrs={'class':['FeatureBox', 'ArticleComments', 'SideColumn', \ - 'LeftColumn', 'RightColumn', 'SiteSearch', 'MainNavBar','more', 'SubMenu', 'inner']}) - ] - cover_url_pattern = 'http://cacm.acm.org/magazines/%d/%d' - - def get_browser(self): - br = BasicNewsRecipe.get_browser() - if self.username is not None and self.password is not None: - br.open('https://cacm.acm.org/login') - br.select_form(nr=1) - br['current_member[user]'] = self.username - br['current_member[passwd]'] = self.password - br.submit() - return br - - def get_cover_url(self): - now = datetime.datetime.now() - - cover_url = None - soup = self.index_to_soup(self.cover_url_pattern % (now.year, now.month)) - cover_item = soup.find('img',attrs={'alt':'magazine cover image'}) - if cover_item: - cover_url = cover_item['src'] - return cover_url +import datetime +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1286242553(BasicNewsRecipe): + title = u'CACM' + oldest_article = 7 + max_articles_per_feed = 100 + needs_subscription = True + feeds = [(u'CACM', u'http://cacm.acm.org/magazine.rss')] + language = 'en' + __author__ = 'jonmisurda' + no_stylesheets = True + remove_tags = [ + dict(name='div', attrs={'class':['FeatureBox', 'ArticleComments', 'SideColumn', \ + 'LeftColumn', 'RightColumn', 'SiteSearch', 'MainNavBar','more', 'SubMenu', 'inner']}) + ] + cover_url_pattern = 'http://cacm.acm.org/magazines/%d/%d' + + def get_browser(self): + br = BasicNewsRecipe.get_browser() + if self.username is not None and self.password is not None: + br.open('https://cacm.acm.org/login') + br.select_form(nr=1) + br['current_member[user]'] = self.username + br['current_member[passwd]'] = self.password + br.submit() + return br + + def get_cover_url(self): + now = datetime.datetime.now() + + cover_url = None + soup = self.index_to_soup(self.cover_url_pattern % (now.year, now.month)) + cover_item = soup.find('img',attrs={'alt':'magazine cover image'}) + if cover_item: + cover_url = cover_item['src'] + return cover_url From 1c2ff97a5d8b64edffc80d42a0e64ea152a94842 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 19 Oct 2010 19:06:31 -0600 Subject: [PATCH 10/10] ... --- src/calibre/devices/usbms/device.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index f085ab8989..f826167d16 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -740,12 +740,17 @@ class Device(DeviceConfig, DevicePlugin): umount(d) except: pass + failures = False for d in drives: try: eject(d) except Exception, e: print 'Udisks eject call for:', d, 'failed:' print '\t', e + failures = True + + if not failures: + return for drive in drives: cmd = 'calibre-mount-helper'