Sync to trunk.

This commit is contained in:
John Schember 2009-06-25 19:37:22 -04:00
commit e9a27c63e0
15 changed files with 673 additions and 560 deletions

View File

@ -352,6 +352,7 @@ def main():
'BeautifulSoup', 'calibre.ebooks.lrf.fonts.prs500.*', 'BeautifulSoup', 'calibre.ebooks.lrf.fonts.prs500.*',
'dateutil', 'email.iterators', 'dateutil', 'email.iterators',
'email.generator', 'email.generator',
'calibre.ebooks.metadata.amazon',
], ],
'packages' : ['PIL', 'Authorization', 'lxml', 'dns'], 'packages' : ['PIL', 'Authorization', 'lxml', 'dns'],
'excludes' : ['IPython'], 'excludes' : ['IPython'],

View File

@ -70,11 +70,18 @@ def option_recommendation_to_cli_option(add_option, rec):
switches.append('--'+opt.long_switch) switches.append('--'+opt.long_switch)
attrs = dict(dest=opt.name, help=opt.help, attrs = dict(dest=opt.name, help=opt.help,
choices=opt.choices, default=rec.recommended_value) choices=opt.choices, default=rec.recommended_value)
if opt.long_switch == 'verbose':
attrs['action'] = 'count'
if isinstance(rec.recommended_value, type(True)): if isinstance(rec.recommended_value, type(True)):
attrs['action'] = 'store_false' if rec.recommended_value else \ attrs['action'] = 'store_false' if rec.recommended_value else \
'store_true' 'store_true'
else:
if isinstance(rec.recommended_value, int):
attrs['type'] = 'int'
if isinstance(rec.recommended_value, float):
attrs['type'] = 'float'
if opt.long_switch == 'verbose':
attrs['action'] = 'count'
attrs.pop('type', '')
add_option(Option(*switches, **attrs)) add_option(Option(*switches, **attrs))
def add_input_output_options(parser, plumber): def add_input_output_options(parser, plumber):

View File

@ -616,8 +616,7 @@ OptionRecommendation(name='list_recipes',
self.opts.dest = self.opts.output_profile self.opts.dest = self.opts.output_profile
from calibre.ebooks.oeb.transforms.metadata import MergeMetadata from calibre.ebooks.oeb.transforms.metadata import MergeMetadata
MergeMetadata()(self.oeb, self.user_metadata, MergeMetadata()(self.oeb, self.user_metadata, self.opts)
self.opts.prefer_metadata_cover)
pr(0.2) pr(0.2)
self.flush() self.flush()

View File

@ -58,8 +58,8 @@ class LRFOptions(object):
for x in ('top', 'bottom', 'left', 'right'): for x in ('top', 'bottom', 'left', 'right'):
setattr(self, x+'_margin', (self.profile.dpi/72.) * getattr(opts, setattr(self, x+'_margin',
'margin_'+x)) (self.profile.dpi/72.) * float(getattr(opts, 'margin_'+x)))
for x in ('wordspace', 'header', 'header_format', for x in ('wordspace', 'header', 'header_format',
'minimum_indent', 'serif_family', 'minimum_indent', 'serif_family',

View File

@ -15,8 +15,8 @@ from calibre import relpath
_author_pat = re.compile(',?\s+and\s+', re.IGNORECASE) _author_pat = re.compile(',?\s+and\s+', re.IGNORECASE)
def string_to_authors(raw): def string_to_authors(raw):
raw = _author_pat.sub('&', raw)
raw = raw.replace('&&', u'\uffff') raw = raw.replace('&&', u'\uffff')
raw = _author_pat.sub('&', raw)
authors = [a.strip().replace(u'\uffff', '&') for a in raw.split('&')] authors = [a.strip().replace(u'\uffff', '&') for a in raw.split('&')]
return authors return authors

View File

@ -6,7 +6,7 @@ from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>' __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
import sys, os, uuid, copy, re import sys, os, uuid, copy, re, cStringIO
from itertools import izip from itertools import izip
from urlparse import urldefrag, urlparse from urlparse import urldefrag, urlparse
from urllib import unquote as urlunquote from urllib import unquote as urlunquote
@ -22,7 +22,7 @@ from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, OEB_IMAGES, \
PAGE_MAP_MIME, JPEG_MIME, NCX_MIME, SVG_MIME PAGE_MAP_MIME, JPEG_MIME, NCX_MIME, SVG_MIME
from calibre.ebooks.oeb.base import XMLDECL_RE, COLLAPSE_RE, \ from calibre.ebooks.oeb.base import XMLDECL_RE, COLLAPSE_RE, \
ENTITY_RE, MS_COVER_TYPE, iterlinks ENTITY_RE, MS_COVER_TYPE, iterlinks
from calibre.ebooks.oeb.base import namespace, barename, qname, XPath, xpath, \ from calibre.ebooks.oeb.base import namespace, barename, XPath, xpath, \
urlnormalize, BINARY_MIME, \ urlnormalize, BINARY_MIME, \
OEBError, OEBBook, DirContainer OEBError, OEBBook, DirContainer
from calibre.ebooks.oeb.writer import OEBWriter from calibre.ebooks.oeb.writer import OEBWriter
@ -30,6 +30,7 @@ from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
from calibre.ebooks.metadata.epub import CoverRenderer from calibre.ebooks.metadata.epub import CoverRenderer
from calibre.startup import get_lang from calibre.startup import get_lang
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
from calibre.constants import __appname__, __version__
__all__ = ['OEBReader'] __all__ = ['OEBReader']
@ -123,53 +124,25 @@ class OEBReader(object):
return opf return opf
def _metadata_from_opf(self, opf): def _metadata_from_opf(self, opf):
uid = opf.get('unique-identifier', None) from calibre.ebooks.metadata.opf2 import OPF
self.oeb.uid = None from calibre.ebooks.metadata import MetaInformation
metadata = self.oeb.metadata from calibre.ebooks.oeb.transforms.metadata import meta_info_to_oeb_metadata
for elem in xpath(opf, '/o2:package/o2:metadata//*'): stream = cStringIO.StringIO(etree.tostring(opf))
term = elem.tag mi = MetaInformation(OPF(stream))
value = elem.text if not mi.title:
attrib = dict(elem.attrib) mi.title = self.oeb.translate(__('Unknown'))
nsmap = elem.nsmap if not mi.authors:
if term == OPF('meta'): mi.authors = [self.oeb.translate(__('Unknown'))]
term = qname(attrib.pop('name', None), nsmap) if not mi.book_producer:
value = attrib.pop('content', None) mi.book_producer = '%(a)s (%(v)s) [http://%(a)s.kovidgoyal.net]'%\
if value: dict(a=__appname__, v=__version__)
value = COLLAPSE_RE.sub(' ', value.strip()) if not mi.language:
if term and (value or attrib): mi.language = get_lang()
metadata.add(term, value, attrib, nsmap=nsmap) meta_info_to_oeb_metadata(mi, self.oeb.metadata, self.logger)
haveuuid = haveid = False bookid = "urn:uuid:%s" % str(uuid.uuid4()) if mi.application_id is None \
for ident in metadata.identifier: else mi.application_id
if unicode(ident).startswith('urn:uuid:'): self.oeb.metadata.add('identifier', bookid, id='calibre-uuid')
haveuuid = True self.oeb.uid = self.oeb.metadata.identifier[0]
if 'id' in ident.attrib:
haveid = True
if not (haveuuid and haveid):
bookid = "urn:uuid:%s" % str(uuid.uuid4())
metadata.add('identifier', bookid, id='calibre-uuid')
if uid is None:
self.logger.warn(u'Unique-identifier not specified')
for item in metadata.identifier:
if not item.id:
continue
if uid is None or item.id == uid:
self.oeb.uid = item
break
else:
self.logger.warn(u'Unique-identifier %r not found' % uid)
for ident in metadata.identifier:
if 'id' in ident.attrib:
self.oeb.uid = metadata.identifier[0]
break
if not metadata.language:
self.logger.warn(u'Language not specified')
metadata.add('language', get_lang())
if not metadata.creator:
self.logger.warn('Creator not specified')
metadata.add('creator', self.oeb.translate(__('Unknown')))
if not metadata.title:
self.logger.warn('Title not specified')
metadata.add('title', self.oeb.translate(__('Unknown')))
def _manifest_prune_invalid(self): def _manifest_prune_invalid(self):
''' '''

View File

@ -169,11 +169,11 @@ class Stylizer(object):
if not matches and class_sel_pat.match(text): if not matches and class_sel_pat.match(text):
found = False found = False
for x in tree.xpath('//*[@class]'): for x in tree.xpath('//*[@class]'):
if x.get('class').lower() == text[1:].lower(): if text.lower().endswith('.'+x.get('class').lower()):
matches.append(x) matches.append(x)
found = True found = True
if found: if found:
self.logger.warn('Ignoring case mismatch for CSS selector: %s in %s' self.logger.warn('Ignoring case mismatches for CSS selector: %s in %s'
%(text, item.href)) %(text, item.href))
for elem in matches: for elem in matches:
self.style(elem)._update_cssdict(cssdict) self.style(elem)._update_cssdict(cssdict)

View File

@ -8,66 +8,65 @@ __docformat__ = 'restructuredtext en'
import os import os
def meta_info_to_oeb_metadata(mi, m, log):
if mi.title:
m.clear('title')
m.add('title', mi.title)
if mi.title_sort:
if not m.title:
m.add('title', mi.title_sort)
m.title[0].file_as = mi.title_sort
if mi.authors:
m.filter('creator', lambda x : x.role.lower() == 'aut')
for a in mi.authors:
attrib = {'role':'aut'}
if mi.author_sort:
attrib['file_as'] = mi.author_sort
m.add('creator', a, attrib=attrib)
if mi.book_producer:
m.filter('contributor', lambda x : x.role.lower() == 'bkp')
m.add('contributor', mi.book_producer, role='bkp')
if mi.comments:
m.clear('description')
m.add('description', mi.comments)
if mi.publisher:
m.clear('publisher')
m.add('publisher', mi.publisher)
if mi.series:
m.clear('series')
m.add('series', mi.series)
if mi.isbn:
has = False
for x in m.identifier:
if x.scheme.lower() == 'isbn':
x.content = mi.isbn
has = True
if not has:
m.add('identifier', mi.isbn, scheme='ISBN')
if mi.language:
m.clear('language')
m.add('language', mi.language)
if mi.series_index is not None:
m.clear('series_index')
m.add('series_index', mi.format_series_index())
if mi.rating is not None:
m.clear('rating')
m.add('rating', '%.2f'%mi.rating)
if mi.tags:
m.clear('subject')
for t in mi.tags:
m.add('subject', t)
class MergeMetadata(object): class MergeMetadata(object):
'Merge in user metadata, including cover' 'Merge in user metadata, including cover'
def __call__(self, oeb, mi, prefer_metadata_cover=False, def __call__(self, oeb, mi, opts):
prefer_author_sort=False):
from calibre.ebooks.oeb.base import DC
self.oeb, self.log = oeb, oeb.log self.oeb, self.log = oeb, oeb.log
m = self.oeb.metadata m = self.oeb.metadata
meta_info_to_oeb_metadata(mi, m, oeb.log)
self.log('Merging user specified metadata...') self.log('Merging user specified metadata...')
if mi.title: cover_id = self.set_cover(mi, opts.prefer_metadata_cover)
m.clear('title')
m.add('title', mi.title)
if mi.title_sort:
if not m.title:
m.add(DC('title'), mi.title_sort)
m.title[0].file_as = mi.title_sort
if prefer_author_sort and mi.author_sort:
mi.authors = [mi.author_sort]
if mi.authors:
m.filter('creator', lambda x : x.role.lower() == 'aut')
for a in mi.authors:
attrib = {'role':'aut'}
if mi.author_sort:
attrib['file_as'] = mi.author_sort
m.add('creator', a, attrib=attrib)
if mi.comments:
m.clear('description')
m.add('description', mi.comments)
if mi.publisher:
m.clear('publisher')
m.add('publisher', mi.publisher)
if mi.series:
m.clear('series')
m.add('series', mi.series)
if mi.isbn:
has = False
for x in m.identifier:
if x.scheme.lower() == 'isbn':
x.content = mi.isbn
has = True
if not has:
m.add('identifier', mi.isbn, scheme='ISBN')
if mi.language:
m.clear('language')
m.add('language', mi.language)
if mi.book_producer:
m.filter('creator', lambda x : x.role.lower() == 'bkp')
m.add('creator', mi.book_producer, role='bkp')
if mi.series_index is not None:
m.clear('series_index')
m.add('series_index', mi.format_series_index())
if mi.rating is not None:
m.clear('rating')
m.add('rating', '%.2f'%mi.rating)
if mi.tags:
m.clear('subject')
for t in mi.tags:
m.add('subject', t)
cover_id = self.set_cover(mi, prefer_metadata_cover)
m.clear('cover') m.clear('cover')
if cover_id is not None: if cover_id is not None:
m.add('cover', cover_id) m.add('cover', cover_id)

View File

@ -69,7 +69,7 @@ def pdftohtml(output_dir, pdf_path, no_images):
if not os.path.exists(index) or os.stat(index).st_size < 100: if not os.path.exists(index) or os.stat(index).st_size < 100:
raise DRMError() raise DRMError()
with open(index, 'rb+wb') as i: with open(index, 'r+b') as i:
raw = i.read() raw = i.read()
raw = '<!-- created by calibre\'s pdftohtml -->\n' + raw raw = '<!-- created by calibre\'s pdftohtml -->\n' + raw
i.seek(0) i.seek(0)

View File

@ -318,8 +318,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
pm = QMenu() pm = QMenu()
ap = self.action_preferences ap = self.action_preferences
pm.addAction(ap.icon(), ap.text()) pm.addAction(ap.icon(), ap.text())
pm.addAction(self.preferences_action)
pm.addAction(_('Run welcome wizard')) pm.addAction(_('Run welcome wizard'))
self.connect(pm.actions()[0], SIGNAL('triggered(bool)'),
self.do_config)
self.connect(pm.actions()[1], SIGNAL('triggered(bool)'), self.connect(pm.actions()[1], SIGNAL('triggered(bool)'),
self.run_wizard) self.run_wizard)
self.action_preferences.setMenu(pm) self.action_preferences.setMenu(pm)
@ -933,16 +934,14 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
details = ['%s: %s'%(title, reason) for title, details = ['%s: %s'%(title, reason) for title,
reason in x.failures.values()] reason in x.failures.values()]
details = '%s\n'%('\n'.join(details)) details = '%s\n'%('\n'.join(details))
warning_dialog(_('Failed to download some metadata'), warning_dialog(self, _('Failed to download some metadata'),
_('Failed to download metadata for the following:'), _('Failed to download metadata for the following:'),
details, self).exec_() det_msg=details).exec_()
else: else:
err = _('Failed to download metadata:') err = _('Failed to download metadata:')
error_dialog(self, _('Error'), err, det_msg=x.tb).exec_() error_dialog(self, _('Error'), err, det_msg=x.tb).exec_()
def edit_metadata(self, checked, bulk=None): def edit_metadata(self, checked, bulk=None):
''' '''
Edit metadata of selected books in library. Edit metadata of selected books in library.

View File

@ -482,6 +482,9 @@ class Wizard(QWizard):
self.device_page = DevicePage() self.device_page = DevicePage()
self.library_page = LibraryPage() self.library_page = LibraryPage()
self.finish_page = FinishPage() self.finish_page = FinishPage()
bt = unicode(self.buttonText(self.FinishButton))
t = unicode(self.finish_page.finish_text.text())
self.finish_page.finish_text.setText(t%bt)
self.kindle_page = KindlePage() self.kindle_page = KindlePage()
self.stanza_page = StanzaPage() self.stanza_page = StanzaPage()
self.setPage(self.library_page.ID, self.library_page) self.setPage(self.library_page.ID, self.library_page)

View File

@ -21,9 +21,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="finish_text">
<property name="text"> <property name="text">
<string>&lt;h2&gt;Congratulations!&lt;/h2&gt; You have succesfully setup calibre. Press the Finish button to apply your settings.</string> <string>&lt;h2&gt;Congratulations!&lt;/h2&gt; You have successfully setup calibre. Press the %s button to apply your settings.</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>

View File

@ -12,7 +12,7 @@ from ctypes import Structure as _Structure, c_char_p, c_uint, c_void_p, POINTER,
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from StringIO import StringIO from StringIO import StringIO
from calibre import iswindows, load_library, CurrentDir from calibre import iswindows, load_library, CurrentDir, prints
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
_librar_name = 'libunrar' _librar_name = 'libunrar'
@ -173,7 +173,7 @@ def extract(path, dir):
try: try:
if open_archive_data.OpenResult != 0: if open_archive_data.OpenResult != 0:
raise UnRARException(_interpret_open_error(open_archive_data.OpenResult, path)) raise UnRARException(_interpret_open_error(open_archive_data.OpenResult, path))
print 'Archive:', path prints('Archive:', path)
#print get_archive_info(open_archive_data.Flags) #print get_archive_info(open_archive_data.Flags)
header_data = RARHeaderDataEx(CmtBuf=None) header_data = RARHeaderDataEx(CmtBuf=None)
#_libunrar.RARSetCallback(arc_data, callback_func, mode) #_libunrar.RARSetCallback(arc_data, callback_func, mode)

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,96 @@ import time, atexit, os
class LockError(Exception): class LockError(Exception):
pass pass
class WindowsExclFile(object):
def __init__(self, path, timeout=20):
self.name = path
import win32file as w
import pywintypes
while timeout > 0:
timeout -= 1
try:
self._handle = w.CreateFile(path,
w.GENERIC_READ|w.GENERIC_WRITE, # Open for reading and writing
0, # Open exclusive
None, # No security attributes
w.OPEN_ALWAYS, # If file does not exist, create it
w.FILE_ATTRIBUTE_NORMAL, #Normal attributes
None, #No template file
)
break
except pywintypes.error, err:
if getattr(err, 'args', [-1])[0] in (0x20, 0x21):
time.sleep(1)
continue
else:
raise
def seek(self, amt, frm=0):
import win32file as w
if frm not in (0, 1, 2):
raise ValueError('Invalid from for seek: %s'%frm)
frm = {0:w.FILE_BEGIN, 1: w.FILE_CURRENT, 2:w.FILE_END}[frm]
if frm is w.FILE_END:
amt = 0 - amt
w.SetFilePointer(self._handle, amt, frm)
def tell(self):
import win32file as w
return w.SetFilePointer(self._handle, 0, w.FILE_CURRENT)
def flush(self):
import win32file as w
w.FlushFileBuffers(self._handle)
def close(self):
if self._handle is not None:
import win32file as w
self.flush()
w.CloseHandle(self._handle)
self._handle = None
def read(self, bytes=-1):
import win32file as w
sz = w.GetFileSize(self._handle)
max = sz - self.tell()
if bytes < 0: bytes = max
bytes = min(max, bytes)
if bytes < 1:
return ''
hr, ans = w.ReadFile(self._handle, bytes, None)
if hr != 0:
raise IOError('Error reading file: %s'%hr)
return ans
def readlines(self, sizehint=-1):
return self.read().splitlines()
def write(self, bytes):
if isinstance(bytes, unicode):
bytes = bytes.encode('utf-8')
import win32file as w
w.WriteFile(self._handle, bytes, None)
def truncate(self, size=None):
import win32file as w
pos = self.tell()
if size is None:
size = pos
t = min(size, pos)
self.seek(t)
w.SetEndOfFile(self._handle)
self.seek(pos)
def isatty(self):
return False
@property
def closed(self):
return self._handle is None
class ExclusiveFile(object): class ExclusiveFile(object):
def __init__(self, path, timeout=15): def __init__(self, path, timeout=15):
@ -20,17 +110,10 @@ class ExclusiveFile(object):
self.timeout = timeout self.timeout = timeout
def __enter__(self): def __enter__(self):
self.file = open(self.path, 'a+b') self.file = WindowsExclFile(self.path, self.timeout) if iswindows else open(self.path, 'a+b')
self.file.seek(0) self.file.seek(0)
timeout = self.timeout timeout = self.timeout
if iswindows: if not iswindows:
name = ('Local\\'+(__appname__+self.file.name).replace('\\', '_'))[:201]
while self.timeout < 0 or timeout >= 0:
self.mutex = win32event.CreateMutex(None, False, name)
if win32api.GetLastError() != winerror.ERROR_ALREADY_EXISTS: break
time.sleep(1)
timeout -= 1
else:
while self.timeout < 0 or timeout >= 0: while self.timeout < 0 or timeout >= 0:
try: try:
fcntl.lockf(self.file.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB) fcntl.lockf(self.file.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
@ -38,14 +121,12 @@ class ExclusiveFile(object):
except IOError: except IOError:
time.sleep(1) time.sleep(1)
timeout -= 1 timeout -= 1
if timeout < 0 and self.timeout >= 0: if timeout < 0 and self.timeout >= 0:
self.file.close() self.file.close()
raise LockError raise LockError('Failed to lock')
return self.file return self.file
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
if iswindows:
win32api.CloseHandle(self.mutex)
self.file.close() self.file.close()
def _clean_lock_file(file): def _clean_lock_file(file):