diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 2cc478603a..d7811f0a22 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -201,8 +201,9 @@ class ITUNES(DriverBase): # 0x1294 iPhone 3GS # 0x1297 iPhone 4 # 0x129a iPad + # 0x12a2 iPad2 VENDOR_ID = [0x05ac] - PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a] + PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a,0x12a2] BCD = [0x01] # Plugboard ID @@ -421,7 +422,7 @@ class ITUNES(DriverBase): cached_books[this_book.path] = { 'title':book.name(), - 'author':[book.artist()], + 'author':book.artist().split(' & '), 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, 'dev_book':book, 'uuid': book.composer() @@ -459,7 +460,7 @@ class ITUNES(DriverBase): cached_books[this_book.path] = { 'title':book.Name, - 'author':book.Artist, + 'author':book.artist().split(' & '), 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, 'uuid': book.Composer, 'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' @@ -1021,7 +1022,9 @@ class ITUNES(DriverBase): if isosx: for (i,file) in enumerate(files): format = file.rpartition('.')[2].lower() - path = self.path_template % (metadata[i].title, metadata[i].author[0],format) + path = self.path_template % (metadata[i].title, + authors_to_string(metadata[i].authors), + format) self._remove_existing_copy(path, metadata[i]) fpath = self._get_fpath(file, metadata[i], format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) @@ -1034,9 +1037,11 @@ class ITUNES(DriverBase): if DEBUG: self.log.info("ITUNES.upload_books()") self.log.info(" adding '%s' by '%s' uuid:%s to self.cached_books" % - ( metadata[i].title, metadata[i].author, metadata[i].uuid)) + (metadata[i].title, + authors_to_string(metadata[i].authors), + metadata[i].uuid)) self.cached_books[this_book.path] = { - 'author': metadata[i].author, + 'author': authors_to_string(metadata[i].authors), 'dev_book': db_added, 'format': format, 'lib_book': lb_added, @@ -1055,7 +1060,9 @@ class ITUNES(DriverBase): for (i,file) in enumerate(files): format = file.rpartition('.')[2].lower() - path = self.path_template % (metadata[i].title, metadata[i].author[0],format) + path = self.path_template % (metadata[i].title, + authors_to_string(metadata[i].authors), + format) self._remove_existing_copy(path, metadata[i]) fpath = self._get_fpath(file, metadata[i],format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) @@ -1075,9 +1082,11 @@ class ITUNES(DriverBase): if DEBUG: self.log.info("ITUNES.upload_books()") self.log.info(" adding '%s' by '%s' uuid:%s to self.cached_books" % - ( metadata[i].title, metadata[i].author, metadata[i].uuid)) + (metadata[i].title, + authors_to_string(metadata[i].authors), + metadata[i].uuid)) self.cached_books[this_book.path] = { - 'author': metadata[i].author[0], + 'author': authors_to_string(metadata[i].authors), 'dev_book': db_added, 'format': format, 'lib_book': lb_added, @@ -1190,7 +1199,7 @@ class ITUNES(DriverBase): base_fn = base_fn.rpartition('.')[0] db_added = self._find_device_book( { 'title': base_fn if format == 'pdf' else metadata.title, - 'author': metadata.authors[0], + 'author': authors_to_string(metadata.authors), 'uuid': metadata.uuid, 'format': format}) return db_added @@ -1255,7 +1264,7 @@ class ITUNES(DriverBase): base_fn = base_fn.rpartition('.')[0] added = self._find_library_book( { 'title': base_fn if format == 'pdf' else metadata.title, - 'author': metadata.author[0], + 'author': authors_to_string(metadata.authors), 'uuid': metadata.uuid, 'format': format}) return added @@ -1314,7 +1323,7 @@ class ITUNES(DriverBase): with open(metadata.cover,'r+b') as cd: cover_data = cd.read() except: - self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) + self.problem_titles.append("'%s' by %s" % (metadata.title, authors_to_string(metadata.authors))) self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title)) import traceback @@ -1389,7 +1398,7 @@ class ITUNES(DriverBase): thumb_path = path.rpartition('.')[0] + '.jpg' zfw.writestr(thumb_path, thumb) except: - self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) + self.problem_titles.append("'%s' by %s" % (metadata.title, authors_to_string(metadata.authors))) self.log.error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title)) finally: try: @@ -1407,7 +1416,7 @@ class ITUNES(DriverBase): if DEBUG: self.log.info(" ITUNES._create_new_book()") - this_book = Book(metadata.title, authors_to_string(metadata.author)) + this_book = Book(metadata.title, authors_to_string(metadata.authors)) this_book.datetime = time.gmtime() this_book.db_id = None this_book.device_collections = [] @@ -2451,7 +2460,7 @@ class ITUNES(DriverBase): for book in self.cached_books: 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.cached_books[book]['author'] == authors_to_string(metadata.authors)): self.update_list.append(self.cached_books[book]) self._remove_from_device(self.cached_books[book]) if DEBUG: @@ -2470,7 +2479,7 @@ class ITUNES(DriverBase): for book in self.cached_books: 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.cached_books[book]['author'] == authors_to_string(metadata.authors)): self.update_list.append(self.cached_books[book]) self._remove_from_iTunes(self.cached_books[book]) if DEBUG: @@ -2939,13 +2948,13 @@ class ITUNES(DriverBase): def _xform_metadata_via_plugboard(self, book, format): ''' Transform book metadata from plugboard templates ''' if DEBUG: - self.log.info(" ITUNES._xform_metadata_via_plugboard()") + self.log.info(" ITUNES._xform_metadata_via_plugboard()") if self.plugboard_func: pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards) newmi = book.deepcopy_metadata() newmi.template_to_attribute(book, pb) - if DEBUG: + if pb is not None and DEBUG: self.log.info(" transforming %s using %s:" % (format, pb)) self.log.info(" title: %s %s" % (book.title, ">>> %s" % newmi.title if book.title != newmi.title else '')) @@ -3062,7 +3071,7 @@ class ITUNES_ASYNC(ITUNES): cached_books[this_book.path] = { 'title':library_books[book].name(), - 'author':[library_books[book].artist()], + 'author':library_books[book].artist().split(' & '), 'lib_book':library_books[book], 'dev_book':None, 'uuid': library_books[book].composer(), @@ -3102,7 +3111,7 @@ class ITUNES_ASYNC(ITUNES): cached_books[this_book.path] = { 'title':library_books[book].Name, - 'author':library_books[book].Artist, + 'author':library_books[book].Artist.split(' & '), 'lib_book':library_books[book], 'uuid': library_books[book].Composer, 'format': format @@ -3288,7 +3297,7 @@ class Book(Metadata): See ebooks.metadata.book.base ''' def __init__(self,title,author): - Metadata.__init__(self, title, authors=[author]) + Metadata.__init__(self, title, authors=author.split(' & ')) @property def title_sorter(self): diff --git a/src/calibre/ebooks/chm/input.py b/src/calibre/ebooks/chm/input.py index 61160e8dac..fce07c2359 100644 --- a/src/calibre/ebooks/chm/input.py +++ b/src/calibre/ebooks/chm/input.py @@ -52,6 +52,9 @@ class CHMInput(InputFormatPlugin): metadata = get_metadata_from_reader(self._chm_reader) self._chm_reader.CloseCHM() + #print tdir + #from calibre import ipython + #ipython() odi = options.debug_pipeline options.debug_pipeline = None diff --git a/src/calibre/ebooks/chm/reader.py b/src/calibre/ebooks/chm/reader.py index 34d228ef3b..7c9a6bf48a 100644 --- a/src/calibre/ebooks/chm/reader.py +++ b/src/calibre/ebooks/chm/reader.py @@ -147,7 +147,8 @@ class CHMReader(CHMFile): if self.hhc_path == '.hhc' and self.hhc_path not in files: from calibre import walk for x in walk(output_dir): - if os.path.basename(x).lower() in ('index.htm', 'index.html'): + if os.path.basename(x).lower() in ('index.htm', 'index.html', + 'contents.htm', 'contents.html'): self.hhc_path = os.path.relpath(x, output_dir) break diff --git a/src/calibre/ebooks/compression/palmdoc.c b/src/calibre/ebooks/compression/palmdoc.c index 4d913dfd2b..6b07bb9cd5 100644 --- a/src/calibre/ebooks/compression/palmdoc.c +++ b/src/calibre/ebooks/compression/palmdoc.c @@ -17,6 +17,7 @@ #define BUFFER 6000 #define MIN(x, y) ( ((x) < (y)) ? (x) : (y) ) +#define MAX(x, y) ( ((x) > (y)) ? (x) : (y) ) typedef unsigned short int Byte; typedef struct { @@ -53,7 +54,7 @@ cpalmdoc_decompress(PyObject *self, PyObject *args) { // Map chars to bytes for (j = 0; j < input_len; j++) input[j] = (_input[j] < 0) ? _input[j]+256 : _input[j]; - output = (char *)PyMem_Malloc(sizeof(char)*BUFFER); + output = (char *)PyMem_Malloc(sizeof(char)*(MAX(BUFFER, 5*input_len))); if (output == NULL) return PyErr_NoMemory(); while (i < input_len) { diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index 82e9a14d12..1fb1a74679 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -114,8 +114,12 @@ class ISBNMerge(object): return self.results - def merge_metadata_results(self): - ' Merge results with identical title and authors ' + def merge_metadata_results(self, merge_on_identifiers=False): + ''' + Merge results with identical title and authors or an identical + identifier + ''' + # First title/author groups = {} for result in self.results: title = lower(result.title if result.title else '') @@ -135,6 +139,44 @@ class ISBNMerge(object): result = rgroup[0] self.results.append(result) + if merge_on_identifiers: + # Now identifiers + groups, empty = {}, [] + for result in self.results: + key = set() + for typ, val in result.identifiers.iteritems(): + if typ and val: + key.add((typ, val)) + if key: + key = frozenset(key) + match = None + for candidate in list(groups): + if candidate.intersection(key): + # We have at least one identifier in common + match = candidate.union(key) + results = groups.pop(candidate) + results.append(result) + groups[match] = results + break + if match is None: + groups[key] = [result] + else: + empty.append(result) + + if len(groups) != len(self.results): + self.results = [] + for rgroup in groups.itervalues(): + rel = [r.average_source_relevance for r in rgroup] + if len(rgroup) > 1: + result = self.merge(rgroup, None, do_asr=False) + result.average_source_relevance = sum(rel)/len(rel) + elif rgroup: + result = rgroup[0] + self.results.append(result) + + if empty: + self.results.extend(empty) + self.results.sort(key=attrgetter('average_source_relevance')) def merge_isbn_results(self): diff --git a/src/calibre/ebooks/metadata/sources/test.py b/src/calibre/ebooks/metadata/sources/test.py index 284a7ba45e..e280b0c038 100644 --- a/src/calibre/ebooks/metadata/sources/test.py +++ b/src/calibre/ebooks/metadata/sources/test.py @@ -15,14 +15,17 @@ from calibre.customize.ui import metadata_plugins from calibre import prints, sanitize_file_name2 from calibre.ebooks.metadata import check_isbn from calibre.ebooks.metadata.sources.base import (create_log, - get_cached_cover_urls) + get_cached_cover_urls, msprefs) def isbn_test(isbn): isbn_ = check_isbn(isbn) def test(mi): misbn = check_isbn(mi.isbn) - return misbn and misbn == isbn_ + if misbn and misbn == isbn_: + return True + prints('ISBN test failed. Expected: \'%s\' found \'%s\''%(isbn_, misbn)) + return False return test @@ -32,8 +35,11 @@ def title_test(title, exact=False): def test(mi): mt = mi.title.lower() - return (exact and mt == title) or \ - (not exact and title in mt) + if (exact and mt == title) or \ + (not exact and title in mt): + return True + prints('Title test failed. Expected: \'%s\' found \'%s\''%(title, mt)) + return False return test @@ -42,7 +48,22 @@ def authors_test(authors): def test(mi): au = set([x.lower() for x in mi.authors]) - return au == authors + if msprefs['swap_author_names']: + def revert_to_fn_ln(a): + if ',' not in a: + return a + parts = a.split(',', 1) + t = parts[-1] + parts = parts[:-1] + parts.insert(0, t) + return ' '.join(parts) + + au = set([revert_to_fn_ln(x) for x in au]) + + if au == authors: + return True + prints('Author test failed. Expected: \'%s\' found \'%s\''%(authors, au)) + return False return test diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index e5f2cace7f..58083f807f 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -24,7 +24,7 @@ from calibre.translations.dynamic import translate from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.oeb.entitydefs import ENTITYDEFS from calibre.ebooks.conversion.preprocess import CSSPreProcessor -from calibre import isbytestring +from calibre import isbytestring, as_unicode RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True) @@ -643,7 +643,7 @@ class Metadata(object): return unicode(self.value).encode('ascii', 'xmlcharrefreplace') def __unicode__(self): - return unicode(self.value) + return as_unicode(self.value) def to_opf1(self, dcmeta=None, xmeta=None, nsrmap={}): attrib = {} diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index e39427021e..773aea3002 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -648,6 +648,18 @@ def open_url(qurl): if isfrozen and islinux and paths: os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(paths) +def get_current_db(): + ''' + This method will try to return the current database in use by the user as + efficiently as possible, i.e. without constructing duplicate + LibraryDatabase objects. + ''' + from calibre.gui2.ui import get_gui + gui = get_gui() + if gui is not None and gui.current_db is not None: + return gui.current_db + from calibre.library import db + return db() def open_local_file(path): if iswindows: diff --git a/src/calibre/gui2/actions/catalog.py b/src/calibre/gui2/actions/catalog.py index fad6e59294..093985d041 100644 --- a/src/calibre/gui2/actions/catalog.py +++ b/src/calibre/gui2/actions/catalog.py @@ -17,7 +17,7 @@ from calibre.gui2.actions import InterfaceAction class GenerateCatalogAction(InterfaceAction): name = 'Generate Catalog' - action_spec = (_('Create a catalog of the books in your calibre library'), None, None, None) + action_spec = (_('Create a catalog of the books in your calibre library'), 'catalog.png', 'Catalog builder', None) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) def generate_catalog(self): diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 609c2b30f3..6c3dae3c94 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -483,8 +483,15 @@ class BookDetails(QWidget): # {{{ self.book_info.show_data(data) self.cover_view.show_data(data) self._layout.do_layout(self.rect()) - self.setToolTip('

'+_('Double-click to open Book Details window') + - '

' + _('Path') + ': ' + data.get(_('Path'), '')) + try: + sz = self.cover_view.pixmap.size() + except: + sz = QSize(0, 0) + self.setToolTip( + '

'+_('Double-click to open Book Details window') + + '

' + _('Path') + ': ' + data.get(_('Path'), '') + + '

' + _('Cover size: %dx%d')%(sz.width(), sz.height()) + ) def reset_info(self): self.show_data({}) diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index e860579fdf..46d26c2f4a 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -109,6 +109,8 @@ class BookInfo(QDialog, Ui_BookInfo): pixmap = pixmap.scaled(new_width, new_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.cover.set_pixmap(pixmap) + sz = pixmap.size() + self.cover.setToolTip(_('Cover size: %dx%d')%(sz.width(), sz.height())) def refresh(self, row): if isinstance(row, QModelIndex): diff --git a/src/calibre/gui2/dialogs/tweak_epub.py b/src/calibre/gui2/dialogs/tweak_epub.py index db6e93fd7a..a42fb07e40 100755 --- a/src/calibre/gui2/dialogs/tweak_epub.py +++ b/src/calibre/gui2/dialogs/tweak_epub.py @@ -12,6 +12,7 @@ from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED from PyQt4.Qt import QDialog +from calibre.constants import isosx, iswindows from calibre.gui2 import open_local_file from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog from calibre.libunzip import extract as zipextract @@ -42,11 +43,19 @@ class TweakEpub(QDialog, Ui_Dialog): self.move(parent_loc.x(),parent_loc.y()) def cleanup(self): + if isosx: + try: + import appscript + self.finder = appscript.app('Finder') + self.finder.Finder_windows[os.path.basename(self._exploded)].close() + except: + # appscript fails to load on 10.4 + pass + # Delete directory containing exploded ePub if self._exploded is not None: shutil.rmtree(self._exploded, ignore_errors=True) - def display_exploded(self): ''' Generic subprocess launch of native file browser diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 4a09c5b32e..c72b074463 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -317,9 +317,8 @@ class BaseToolBar(QToolBar): # {{{ QToolBar.resizeEvent(self, ev) style = self.get_text_style() self.setToolButtonStyle(style) - if hasattr(self, 'd_widget'): - if hasattr(self.d_widget, 'filler'): - self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly) + if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'): + self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly) def get_text_style(self): style = Qt.ToolButtonTextUnderIcon diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 71b9e38667..9f06d9a6ab 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -73,13 +73,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): choices=sorted(list(choices), key=sort_key)) - self.current_font = None + self.current_font = self.initial_font = None self.change_font_button.clicked.connect(self.change_font) def initialize(self): ConfigWidgetBase.initialize(self) - self.current_font = gprefs['font'] + self.current_font = self.initial_font = gprefs['font'] self.update_font_display() def restore_defaults(self): @@ -119,7 +119,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): def commit(self, *args): rr = ConfigWidgetBase.commit(self, *args) - if self.current_font != gprefs['font']: + if self.current_font != self.initial_font: gprefs['font'] = self.current_font QApplication.setFont(self.font_display.font()) rr = True diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index f234d48739..9a4e0ca70a 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -88,6 +88,11 @@ class SystemTrayIcon(QSystemTrayIcon): # {{{ # }}} +_gui = None + +def get_gui(): + return _gui + class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin, SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin, @@ -97,7 +102,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ def __init__(self, opts, parent=None, gui_debug=None): + global _gui MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True) + _gui = self self.opts = opts self.device_connected = None self.gui_debug = gui_debug