Merge from trunk

This commit is contained in:
Charles Haley 2011-04-18 11:07:32 +01:00
commit a13b6065c6
32 changed files with 827 additions and 458 deletions

View File

@ -19,6 +19,45 @@
# new recipes: # new recipes:
# - title: # - title:
- version: 0.7.56
date: 2011-04-17
new features:
- title: "This is primarily a bug fix release that fixes a bug in 0.7.55 that caused calibre to rescan the files on the device every time the device is connected. If you updated to 0.7.55 it is highly recommended you update to 0.7.56"
- title: "Device driver for Coby Kyros"
- title: "Remove the quick access to search options from next to the search bar, as we now have a separate search highlights toggle button"
- title: "MOBI Output: Ensure that MOBI files always have 8KB worth of null bytes at the end of record 0. This appears to be necessary for Amazon to be able to add DRM to calibre generated MOBI files sent to their publishing service."
- title: "Add a tool to inspect MOBI files. To use: calibre-debug -m file.mobi"
bug fixes:
- title: "Fixed regression taht caused calibre to rescan files on the device on every reconnect"
- title: "Fix donate button causing the toolbar to be too large on OS X"
- title: "MOBI Input: Fix detection of Table of Contents for MOBI files that have a page break between the location designated as the Table of Contents and the actual table of contents."
tickets: [763504]
- title: "Comic Input: Fix handling of some CBZ files that have wrongly encoded non ASCII filenames on windows."
tickets: [763280]
- title: "PML Input: Fix multi-line chapter title causing a spurious page break"
tickets: [763238]
- title: "EPUB Input: Speed up processing of files with very large manifest/spines"
- title: "Fix regression that broke cover:False searches in 0.7.55"
improved recipes:
- Suedduetsche Zeitung
- Irish Times
- Big Oven
- NSPM
- version: 0.7.55 - version: 0.7.55
date: 2011-04-15 date: 2011-04-15

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.7.55' __version__ = '0.7.56'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re, importlib import re, importlib

View File

@ -201,8 +201,9 @@ class ITUNES(DriverBase):
# 0x1294 iPhone 3GS # 0x1294 iPhone 3GS
# 0x1297 iPhone 4 # 0x1297 iPhone 4
# 0x129a iPad # 0x129a iPad
# 0x12a2 iPad2
VENDOR_ID = [0x05ac] VENDOR_ID = [0x05ac]
PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a] PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a,0x12a2]
BCD = [0x01] BCD = [0x01]
# Plugboard ID # Plugboard ID
@ -421,7 +422,7 @@ class ITUNES(DriverBase):
cached_books[this_book.path] = { cached_books[this_book.path] = {
'title':book.name(), '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, 'lib_book':library_books[this_book.path] if this_book.path in library_books else None,
'dev_book':book, 'dev_book':book,
'uuid': book.composer() 'uuid': book.composer()
@ -459,7 +460,7 @@ class ITUNES(DriverBase):
cached_books[this_book.path] = { cached_books[this_book.path] = {
'title':book.Name, '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, 'lib_book':library_books[this_book.path] if this_book.path in library_books else None,
'uuid': book.Composer, 'uuid': book.Composer,
'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' 'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub'
@ -1021,7 +1022,9 @@ class ITUNES(DriverBase):
if isosx: if isosx:
for (i,file) in enumerate(files): for (i,file) in enumerate(files):
format = file.rpartition('.')[2].lower() 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]) self._remove_existing_copy(path, metadata[i])
fpath = self._get_fpath(file, metadata[i], format, update_md=True) fpath = self._get_fpath(file, metadata[i], format, update_md=True)
db_added, lb_added = self._add_new_copy(fpath, metadata[i]) db_added, lb_added = self._add_new_copy(fpath, metadata[i])
@ -1034,9 +1037,11 @@ class ITUNES(DriverBase):
if DEBUG: if DEBUG:
self.log.info("ITUNES.upload_books()") self.log.info("ITUNES.upload_books()")
self.log.info(" adding '%s' by '%s' uuid:%s to self.cached_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] = { self.cached_books[this_book.path] = {
'author': metadata[i].author, 'author': authors_to_string(metadata[i].authors),
'dev_book': db_added, 'dev_book': db_added,
'format': format, 'format': format,
'lib_book': lb_added, 'lib_book': lb_added,
@ -1055,7 +1060,9 @@ class ITUNES(DriverBase):
for (i,file) in enumerate(files): for (i,file) in enumerate(files):
format = file.rpartition('.')[2].lower() 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]) self._remove_existing_copy(path, metadata[i])
fpath = self._get_fpath(file, metadata[i],format, update_md=True) fpath = self._get_fpath(file, metadata[i],format, update_md=True)
db_added, lb_added = self._add_new_copy(fpath, metadata[i]) db_added, lb_added = self._add_new_copy(fpath, metadata[i])
@ -1075,9 +1082,11 @@ class ITUNES(DriverBase):
if DEBUG: if DEBUG:
self.log.info("ITUNES.upload_books()") self.log.info("ITUNES.upload_books()")
self.log.info(" adding '%s' by '%s' uuid:%s to self.cached_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] = { self.cached_books[this_book.path] = {
'author': metadata[i].author[0], 'author': authors_to_string(metadata[i].authors),
'dev_book': db_added, 'dev_book': db_added,
'format': format, 'format': format,
'lib_book': lb_added, 'lib_book': lb_added,
@ -1190,7 +1199,7 @@ class ITUNES(DriverBase):
base_fn = base_fn.rpartition('.')[0] base_fn = base_fn.rpartition('.')[0]
db_added = self._find_device_book( db_added = self._find_device_book(
{ 'title': base_fn if format == 'pdf' else metadata.title, { 'title': base_fn if format == 'pdf' else metadata.title,
'author': metadata.authors[0], 'author': authors_to_string(metadata.authors),
'uuid': metadata.uuid, 'uuid': metadata.uuid,
'format': format}) 'format': format})
return db_added return db_added
@ -1255,7 +1264,7 @@ class ITUNES(DriverBase):
base_fn = base_fn.rpartition('.')[0] base_fn = base_fn.rpartition('.')[0]
added = self._find_library_book( added = self._find_library_book(
{ 'title': base_fn if format == 'pdf' else metadata.title, { 'title': base_fn if format == 'pdf' else metadata.title,
'author': metadata.author[0], 'author': authors_to_string(metadata.authors),
'uuid': metadata.uuid, 'uuid': metadata.uuid,
'format': format}) 'format': format})
return added return added
@ -1314,7 +1323,7 @@ class ITUNES(DriverBase):
with open(metadata.cover,'r+b') as cd: with open(metadata.cover,'r+b') as cd:
cover_data = cd.read() cover_data = cd.read()
except: 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)) self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title))
import traceback import traceback
@ -1389,7 +1398,7 @@ class ITUNES(DriverBase):
thumb_path = path.rpartition('.')[0] + '.jpg' thumb_path = path.rpartition('.')[0] + '.jpg'
zfw.writestr(thumb_path, thumb) zfw.writestr(thumb_path, thumb)
except: 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)) self.log.error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title))
finally: finally:
try: try:
@ -1407,7 +1416,7 @@ class ITUNES(DriverBase):
if DEBUG: if DEBUG:
self.log.info(" ITUNES._create_new_book()") 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.datetime = time.gmtime()
this_book.db_id = None this_book.db_id = None
this_book.device_collections = [] this_book.device_collections = []
@ -2451,7 +2460,7 @@ class ITUNES(DriverBase):
for book in self.cached_books: for book in self.cached_books:
if self.cached_books[book]['uuid'] == metadata.uuid or \ if self.cached_books[book]['uuid'] == metadata.uuid or \
(self.cached_books[book]['title'] == metadata.title and \ (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.update_list.append(self.cached_books[book])
self._remove_from_device(self.cached_books[book]) self._remove_from_device(self.cached_books[book])
if DEBUG: if DEBUG:
@ -2470,7 +2479,7 @@ class ITUNES(DriverBase):
for book in self.cached_books: for book in self.cached_books:
if self.cached_books[book]['uuid'] == metadata.uuid or \ if self.cached_books[book]['uuid'] == metadata.uuid or \
(self.cached_books[book]['title'] == metadata.title and \ (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.update_list.append(self.cached_books[book])
self._remove_from_iTunes(self.cached_books[book]) self._remove_from_iTunes(self.cached_books[book])
if DEBUG: if DEBUG:
@ -2945,7 +2954,7 @@ class ITUNES(DriverBase):
pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards) pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards)
newmi = book.deepcopy_metadata() newmi = book.deepcopy_metadata()
newmi.template_to_attribute(book, pb) 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(" transforming %s using %s:" % (format, pb))
self.log.info(" title: %s %s" % (book.title, ">>> %s" % self.log.info(" title: %s %s" % (book.title, ">>> %s" %
newmi.title if book.title != newmi.title else '')) newmi.title if book.title != newmi.title else ''))
@ -3062,7 +3071,7 @@ class ITUNES_ASYNC(ITUNES):
cached_books[this_book.path] = { cached_books[this_book.path] = {
'title':library_books[book].name(), 'title':library_books[book].name(),
'author':[library_books[book].artist()], 'author':library_books[book].artist().split(' & '),
'lib_book':library_books[book], 'lib_book':library_books[book],
'dev_book':None, 'dev_book':None,
'uuid': library_books[book].composer(), 'uuid': library_books[book].composer(),
@ -3102,7 +3111,7 @@ class ITUNES_ASYNC(ITUNES):
cached_books[this_book.path] = { cached_books[this_book.path] = {
'title':library_books[book].Name, 'title':library_books[book].Name,
'author':library_books[book].Artist, 'author':library_books[book].Artist.split(' & '),
'lib_book':library_books[book], 'lib_book':library_books[book],
'uuid': library_books[book].Composer, 'uuid': library_books[book].Composer,
'format': format 'format': format
@ -3288,7 +3297,7 @@ class Book(Metadata):
See ebooks.metadata.book.base See ebooks.metadata.book.base
''' '''
def __init__(self,title,author): def __init__(self,title,author):
Metadata.__init__(self, title, authors=[author]) Metadata.__init__(self, title, authors=author.split(' & '))
@property @property
def title_sorter(self): def title_sorter(self):

View File

@ -52,6 +52,9 @@ class CHMInput(InputFormatPlugin):
metadata = get_metadata_from_reader(self._chm_reader) metadata = get_metadata_from_reader(self._chm_reader)
self._chm_reader.CloseCHM() self._chm_reader.CloseCHM()
#print tdir
#from calibre import ipython
#ipython()
odi = options.debug_pipeline odi = options.debug_pipeline
options.debug_pipeline = None options.debug_pipeline = None

View File

@ -147,7 +147,8 @@ class CHMReader(CHMFile):
if self.hhc_path == '.hhc' and self.hhc_path not in files: if self.hhc_path == '.hhc' and self.hhc_path not in files:
from calibre import walk from calibre import walk
for x in walk(output_dir): 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) self.hhc_path = os.path.relpath(x, output_dir)
break break

View File

@ -12,6 +12,7 @@ from Queue import Empty
from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation
from calibre import extract, CurrentDir, prints from calibre import extract, CurrentDir, prints
from calibre.constants import filesystem_encoding
from calibre.ptempfile import PersistentTemporaryDirectory from calibre.ptempfile import PersistentTemporaryDirectory
from calibre.utils.ipc.server import Server from calibre.utils.ipc.server import Server
from calibre.utils.ipc.job import ParallelJob from calibre.utils.ipc.job import ParallelJob
@ -21,6 +22,10 @@ def extract_comic(path_to_comic_file):
Un-archive the comic file. Un-archive the comic file.
''' '''
tdir = PersistentTemporaryDirectory(suffix='_comic_extract') tdir = PersistentTemporaryDirectory(suffix='_comic_extract')
if not isinstance(tdir, unicode):
# Needed in case the zip file has wrongly encoded unicode file/dir
# names
tdir = tdir.decode(filesystem_encoding)
extract(path_to_comic_file, tdir) extract(path_to_comic_file, tdir)
return tdir return tdir

View File

@ -17,6 +17,7 @@
#define BUFFER 6000 #define BUFFER 6000
#define MIN(x, y) ( ((x) < (y)) ? (x) : (y) ) #define MIN(x, y) ( ((x) < (y)) ? (x) : (y) )
#define MAX(x, y) ( ((x) > (y)) ? (x) : (y) )
typedef unsigned short int Byte; typedef unsigned short int Byte;
typedef struct { typedef struct {
@ -53,7 +54,7 @@ cpalmdoc_decompress(PyObject *self, PyObject *args) {
// Map chars to bytes // Map chars to bytes
for (j = 0; j < input_len; j++) for (j = 0; j < input_len; j++)
input[j] = (_input[j] < 0) ? _input[j]+256 : _input[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(); if (output == NULL) return PyErr_NoMemory();
while (i < input_len) { while (i < input_len) {

View File

@ -294,8 +294,24 @@ class Source(Plugin):
Excludes connectives and punctuation. Excludes connectives and punctuation.
''' '''
if title: if title:
pat = re.compile(r'''[-,:;+!@#$%^&*(){}.`~"'\s\[\]/]''') title_patterns = [(re.compile(pat, re.IGNORECASE), repl) for pat, repl in
title = pat.sub(' ', title) [
# Remove things like: (2010) (Omnibus) etc.
(r'(?i)[({\[](\d{4}|omnibus|anthology|hardcover|paperback|mass\s*market|edition|ed\.)[\])}]', ''),
# Remove any strings that contain the substring edition inside
# parentheses
(r'(?i)[({\[].*?(edition|ed.).*?[\]})]', ''),
# Remove commas used a separators in numbers
(r'(\d+),(\d+)', r'\1\2'),
# Remove hyphens only if they have whitespace before them
(r'(\s-)', ' '),
# Remove single quotes
(r"'", ''),
# Replace other special chars with a space
(r'''[:,;+!@#$%^&*(){}.`~"\s\[\]/]''', ' ')
]]
for pat, repl in title_patterns:
title = pat.sub(repl, title)
tokens = title.split() tokens = title.split()
for token in tokens: for token in tokens:
token = token.strip() token = token.strip()

View File

@ -114,8 +114,12 @@ class ISBNMerge(object):
return self.results return self.results
def merge_metadata_results(self): def merge_metadata_results(self, merge_on_identifiers=False):
' Merge results with identical title and authors ' '''
Merge results with identical title and authors or an identical
identifier
'''
# First title/author
groups = {} groups = {}
for result in self.results: for result in self.results:
title = lower(result.title if result.title else '') title = lower(result.title if result.title else '')
@ -135,6 +139,44 @@ class ISBNMerge(object):
result = rgroup[0] result = rgroup[0]
self.results.append(result) 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')) self.results.sort(key=attrgetter('average_source_relevance'))
def merge_isbn_results(self): def merge_isbn_results(self):
@ -408,7 +450,7 @@ if __name__ == '__main__': # tests {{{
{'identifiers':{'isbn': '9780307459671'}, {'identifiers':{'isbn': '9780307459671'},
'title':'Invisible Gorilla', 'authors':['Christopher Chabris']}, 'title':'Invisible Gorilla', 'authors':['Christopher Chabris']},
[title_test('The Invisible Gorilla', [title_test('The Invisible Gorilla',
exact=True), authors_test(['Christopher F. Chabris', 'Daniel Simons'])] exact=True), authors_test(['Christopher Chabris', 'Daniel Simons'])]
), ),

View File

@ -15,14 +15,17 @@ from calibre.customize.ui import metadata_plugins
from calibre import prints, sanitize_file_name2 from calibre import prints, sanitize_file_name2
from calibre.ebooks.metadata import check_isbn from calibre.ebooks.metadata import check_isbn
from calibre.ebooks.metadata.sources.base import (create_log, from calibre.ebooks.metadata.sources.base import (create_log,
get_cached_cover_urls) get_cached_cover_urls, msprefs)
def isbn_test(isbn): def isbn_test(isbn):
isbn_ = check_isbn(isbn) isbn_ = check_isbn(isbn)
def test(mi): def test(mi):
misbn = check_isbn(mi.isbn) 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 return test
@ -32,8 +35,11 @@ def title_test(title, exact=False):
def test(mi): def test(mi):
mt = mi.title.lower() mt = mi.title.lower()
return (exact and mt == title) or \ if (exact and mt == title) or \
(not exact and title in mt) (not exact and title in mt):
return True
prints('Title test failed. Expected: \'%s\' found \'%s\''%(title, mt))
return False
return test return test
@ -42,7 +48,22 @@ def authors_test(authors):
def test(mi): def test(mi):
au = set([x.lower() for x in mi.authors]) 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 return test

View File

@ -716,6 +716,7 @@ class MobiReader(object):
ent_pat = re.compile(r'&(\S+?);') ent_pat = re.compile(r'&(\S+?);')
if elems: if elems:
tocobj = TOC() tocobj = TOC()
found = False
reached = False reached = False
for x in root.iter(): for x in root.iter():
if x == elems[-1]: if x == elems[-1]:
@ -732,7 +733,8 @@ class MobiReader(object):
text = ent_pat.sub(entity_to_unicode, text) text = ent_pat.sub(entity_to_unicode, text)
tocobj.add_item(toc.partition('#')[0], href[1:], tocobj.add_item(toc.partition('#')[0], href[1:],
text) text)
if reached and x.get('class', None) == 'mbp_pagebreak': found = True
if reached and found and x.get('class', None) == 'mbp_pagebreak':
break break
if tocobj is not None: if tocobj is not None:
opf.set_toc(tocobj) opf.set_toc(tocobj)

View File

@ -24,7 +24,7 @@ from calibre.translations.dynamic import translate
from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.chardet import xml_to_unicode
from calibre.ebooks.oeb.entitydefs import ENTITYDEFS from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
from calibre.ebooks.conversion.preprocess import CSSPreProcessor 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) RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True)
@ -643,7 +643,7 @@ class Metadata(object):
return unicode(self.value).encode('ascii', 'xmlcharrefreplace') return unicode(self.value).encode('ascii', 'xmlcharrefreplace')
def __unicode__(self): def __unicode__(self):
return unicode(self.value) return as_unicode(self.value)
def to_opf1(self, dcmeta=None, xmeta=None, nsrmap={}): def to_opf1(self, dcmeta=None, xmeta=None, nsrmap={}):
attrib = {} attrib = {}

View File

@ -648,6 +648,18 @@ def open_url(qurl):
if isfrozen and islinux and paths: if isfrozen and islinux and paths:
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(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): def open_local_file(path):
if iswindows: if iswindows:

View File

@ -17,7 +17,7 @@ from calibre.gui2.actions import InterfaceAction
class GenerateCatalogAction(InterfaceAction): class GenerateCatalogAction(InterfaceAction):
name = 'Generate Catalog' 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']) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
def generate_catalog(self): def generate_catalog(self):

View File

@ -8,14 +8,14 @@ __docformat__ = 'restructuredtext en'
from functools import partial from functools import partial
from PyQt4.Qt import Qt, QMenu, QToolButton, QDialog, QVBoxLayout from PyQt4.Qt import QMenu
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
class StoreAction(InterfaceAction): class StoreAction(InterfaceAction):
name = 'Store' name = 'Store'
action_spec = (_('Store'), 'store.png', None, None) action_spec = (_('Get books'), 'store.png', None, None)
def genesis(self): def genesis(self):
self.qaction.triggered.connect(self.search) self.qaction.triggered.connect(self.search)

View File

@ -483,8 +483,15 @@ class BookDetails(QWidget): # {{{
self.book_info.show_data(data) self.book_info.show_data(data)
self.cover_view.show_data(data) self.cover_view.show_data(data)
self._layout.do_layout(self.rect()) self._layout.do_layout(self.rect())
self.setToolTip('<p>'+_('Double-click to open Book Details window') + try:
'<br><br>' + _('Path') + ': ' + data.get(_('Path'), '')) sz = self.cover_view.pixmap.size()
except:
sz = QSize(0, 0)
self.setToolTip(
'<p>'+_('Double-click to open Book Details window') +
'<br><br>' + _('Path') + ': ' + data.get(_('Path'), '') +
'<br><br>' + _('Cover size: %dx%d')%(sz.width(), sz.height())
)
def reset_info(self): def reset_info(self):
self.show_data({}) self.show_data({})

View File

@ -109,6 +109,8 @@ class BookInfo(QDialog, Ui_BookInfo):
pixmap = pixmap.scaled(new_width, new_height, pixmap = pixmap.scaled(new_width, new_height,
Qt.KeepAspectRatio, Qt.SmoothTransformation) Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.cover.set_pixmap(pixmap) self.cover.set_pixmap(pixmap)
sz = pixmap.size()
self.cover.setToolTip(_('Cover size: %dx%d')%(sz.width(), sz.height()))
def refresh(self, row): def refresh(self, row):
if isinstance(row, QModelIndex): if isinstance(row, QModelIndex):

View File

@ -68,7 +68,7 @@ class DaysOfWeek(Base):
def initialize(self, typ=None, val=None): def initialize(self, typ=None, val=None):
if typ is None: if typ is None:
typ = 'day/time' typ = 'day/time'
val = (-1, 9, 0) val = (-1, 6, 0)
if typ == 'day/time': if typ == 'day/time':
val = convert_day_time_schedule(val) val = convert_day_time_schedule(val)
@ -118,7 +118,7 @@ class DaysOfMonth(Base):
def initialize(self, typ=None, val=None): def initialize(self, typ=None, val=None):
if val is None: if val is None:
val = ((1,), 9, 0) val = ((1,), 6, 0)
days_of_month, hour, minute = val days_of_month, hour, minute = val
self.days.setText(', '.join(map(str, map(int, days_of_month)))) self.days.setText(', '.join(map(str, map(int, days_of_month))))
self.time.setTime(QTime(hour, minute)) self.time.setTime(QTime(hour, minute))
@ -380,7 +380,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
if d < timedelta(days=366): if d < timedelta(days=366):
ld_text = tm ld_text = tm
else: else:
typ, sch = 'day/time', (-1, 9, 0) typ, sch = 'day/time', (-1, 6, 0)
sch_widget = {'day/time': 0, 'days_of_week': 0, 'days_of_month':1, sch_widget = {'day/time': 0, 'days_of_week': 0, 'days_of_month':1,
'interval':2}[typ] 'interval':2}[typ]
rb = getattr(self, list(self.SCHEDULE_TYPES)[sch_widget]) rb = getattr(self, list(self.SCHEDULE_TYPES)[sch_widget])

View File

@ -12,6 +12,7 @@ from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
from PyQt4.Qt import QDialog from PyQt4.Qt import QDialog
from calibre.constants import isosx, iswindows
from calibre.gui2 import open_local_file from calibre.gui2 import open_local_file
from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog
from calibre.libunzip import extract as zipextract from calibre.libunzip import extract as zipextract
@ -42,11 +43,19 @@ class TweakEpub(QDialog, Ui_Dialog):
self.move(parent_loc.x(),parent_loc.y()) self.move(parent_loc.x(),parent_loc.y())
def cleanup(self): 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 # Delete directory containing exploded ePub
if self._exploded is not None: if self._exploded is not None:
shutil.rmtree(self._exploded, ignore_errors=True) shutil.rmtree(self._exploded, ignore_errors=True)
def display_exploded(self): def display_exploded(self):
''' '''
Generic subprocess launch of native file browser Generic subprocess launch of native file browser

View File

@ -317,6 +317,8 @@ class BaseToolBar(QToolBar): # {{{
QToolBar.resizeEvent(self, ev) QToolBar.resizeEvent(self, ev)
style = self.get_text_style() style = self.get_text_style()
self.setToolButtonStyle(style) self.setToolButtonStyle(style)
if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'):
self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly)
def get_text_style(self): def get_text_style(self):
style = Qt.ToolButtonTextUnderIcon style = Qt.ToolButtonTextUnderIcon
@ -399,7 +401,10 @@ class ToolBar(BaseToolBar): # {{{
self.d_widget.layout().addWidget(self.donate_button) self.d_widget.layout().addWidget(self.donate_button)
if isosx: if isosx:
self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }') self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }')
self.d_widget.layout().addWidget(QLabel(u'\u00a0')) self.d_widget.layout().setContentsMargins(0,0,0,0)
self.d_widget.setContentsMargins(0,0,0,0)
self.d_widget.filler = QLabel(u'\u00a0')
self.d_widget.layout().addWidget(self.d_widget.filler)
bar.addWidget(self.d_widget) bar.addWidget(self.d_widget)
self.showing_donate = True self.showing_donate = True
elif what in self.gui.iactions: elif what in self.gui.iactions:

View File

@ -223,7 +223,7 @@ class AuthorSortEdit(EnLineEdit):
LABEL = _('Author s&ort:') LABEL = _('Author s&ort:')
def __init__(self, parent, authors_edit, autogen_button, db, def __init__(self, parent, authors_edit, autogen_button, db,
copy_as_to_a_action): copy_a_to_as_action, copy_as_to_a_action):
EnLineEdit.__init__(self, parent) EnLineEdit.__init__(self, parent)
self.authors_edit = authors_edit self.authors_edit = authors_edit
self.db = db self.db = db
@ -242,6 +242,7 @@ class AuthorSortEdit(EnLineEdit):
self.textChanged.connect(self.update_state) self.textChanged.connect(self.update_state)
autogen_button.clicked.connect(self.auto_generate) autogen_button.clicked.connect(self.auto_generate)
copy_a_to_as_action.triggered.connect(self.auto_generate)
copy_as_to_a_action.triggered.connect(self.copy_to_authors) copy_as_to_a_action.triggered.connect(self.copy_to_authors)
self.update_state() self.update_state()

View File

@ -109,10 +109,12 @@ class MetadataSingleDialogBase(ResizableDialog):
'Using this button to create author sort will change author sort from' 'Using this button to create author sort will change author sort from'
' red to green.')) ' red to green.'))
b.m = m = QMenu() b.m = m = QMenu()
ac = m.addAction(QIcon(I('back.png')), _('Set author from author sort')) ac = m.addAction(QIcon(I('forward.png')), _('Set author sort from author'))
ac2 = m.addAction(QIcon(I('back.png')), _('Set author from author sort'))
b.setMenu(m) b.setMenu(m)
self.authors = AuthorsEdit(self) self.authors = AuthorsEdit(self)
self.author_sort = AuthorSortEdit(self, self.authors, b, self.db, ac) self.author_sort = AuthorSortEdit(self, self.authors, b, self.db, ac,
ac2)
self.basic_metadata_widgets.extend([self.authors, self.author_sort]) self.basic_metadata_widgets.extend([self.authors, self.author_sort])
self.swap_title_author_button = QToolButton(self) self.swap_title_author_button = QToolButton(self)

View File

@ -319,9 +319,12 @@ def show_config_widget(category, name, gui=None, show_restart_msg=False,
:return: True iff a restart is required for the changes made by the user to :return: True iff a restart is required for the changes made by the user to
take effect take effect
''' '''
from calibre.gui2 import gprefs
pl = get_plugin(category, name) pl = get_plugin(category, name)
d = ConfigDialog(parent) d = ConfigDialog(parent)
d.resize(750, 550) d.resize(750, 550)
conf_name = 'config_widget_dialog_geometry_%s_%s'%(category, name)
geom = gprefs.get(conf_name, None)
d.setWindowTitle(_('Configure ') + name) d.setWindowTitle(_('Configure ') + name)
d.setWindowIcon(QIcon(I('config.png'))) d.setWindowIcon(QIcon(I('config.png')))
bb = QDialogButtonBox(d) bb = QDialogButtonBox(d)
@ -345,7 +348,11 @@ def show_config_widget(category, name, gui=None, show_restart_msg=False,
mygui = True mygui = True
w.genesis(gui) w.genesis(gui)
w.initialize() w.initialize()
if geom is not None:
d.restoreGeometry(geom)
d.exec_() d.exec_()
geom = bytearray(d.saveGeometry())
gprefs[conf_name] = geom
rr = getattr(d, 'restart_required', False) rr = getattr(d, 'restart_required', False)
if show_restart_msg and rr: if show_restart_msg and rr:
from calibre.gui2 import warning_dialog from calibre.gui2 import warning_dialog

View File

@ -73,13 +73,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
choices=sorted(list(choices), key=sort_key)) 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) self.change_font_button.clicked.connect(self.change_font)
def initialize(self): def initialize(self):
ConfigWidgetBase.initialize(self) ConfigWidgetBase.initialize(self)
self.current_font = gprefs['font'] self.current_font = self.initial_font = gprefs['font']
self.update_font_display() self.update_font_display()
def restore_defaults(self): def restore_defaults(self):
@ -119,7 +119,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
def commit(self, *args): def commit(self, *args):
rr = ConfigWidgetBase.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 gprefs['font'] = self.current_font
QApplication.setFont(self.font_display.font()) QApplication.setFont(self.font_display.font())
rr = True rr = True

View File

@ -6,7 +6,6 @@ __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>' __copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import re
import urllib2 import urllib2
from contextlib import closing from contextlib import closing

View File

@ -13,9 +13,8 @@ from random import shuffle
from threading import Thread from threading import Thread
from Queue import Queue from Queue import Queue
from PyQt4.Qt import Qt, QAbstractItemModel, QDialog, QTimer, QVariant, \ from PyQt4.Qt import (Qt, QAbstractItemModel, QDialog, QTimer, QVariant,
QModelIndex, QPixmap, QSize, QCheckBox, QVBoxLayout, QHBoxLayout, \ QModelIndex, QPixmap, QSize, QCheckBox, QVBoxLayout)
QPushButton, QString, QByteArray
from calibre import browser from calibre import browser
from calibre.gui2 import NONE from calibre.gui2 import NONE

View File

@ -9,8 +9,8 @@ __docformat__ = 'restructuredtext en'
import os import os
from urlparse import urlparse from urlparse import urlparse
from PyQt4.Qt import QWebView, QWebPage, QNetworkCookieJar, QNetworkRequest, QString, \ from PyQt4.Qt import QNetworkCookieJar, QFileDialog, QNetworkProxy
QFileDialog, QNetworkProxy from PyQt4.QtWebKit import QWebView, QWebPage
from calibre import USER_AGENT, get_proxies, get_download_filename from calibre import USER_AGENT, get_proxies, get_download_filename
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS

View File

@ -88,6 +88,11 @@ class SystemTrayIcon(QSystemTrayIcon): # {{{
# }}} # }}}
_gui = None
def get_gui():
return _gui
class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin, TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin,
SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin, SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin,
@ -97,7 +102,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
def __init__(self, opts, parent=None, gui_debug=None): def __init__(self, opts, parent=None, gui_debug=None):
global _gui
MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True) MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True)
_gui = self
self.opts = opts self.opts = opts
self.device_connected = None self.device_connected = None
self.gui_debug = gui_debug self.gui_debug = gui_debug

View File

@ -426,7 +426,7 @@ def do_show_metadata(db, id, as_opf):
mi = OPFCreator(os.getcwd(), mi) mi = OPFCreator(os.getcwd(), mi)
mi.render(sys.stdout) mi.render(sys.stdout)
else: else:
print unicode(mi).encode(preferred_encoding) prints(unicode(mi))
def show_metadata_option_parser(): def show_metadata_option_parser():
parser = get_parser(_( parser = get_parser(_(

File diff suppressed because it is too large Load Diff