mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
5f9a4c42b9
@ -23,6 +23,9 @@ wWinMain(HINSTANCE Inst, HINSTANCE PrevInst,
|
|||||||
ret = execute_python_entrypoint(BASENAME, MODULE, FUNCTION,
|
ret = execute_python_entrypoint(BASENAME, MODULE, FUNCTION,
|
||||||
stdout_redirect, stderr_redirect);
|
stdout_redirect, stderr_redirect);
|
||||||
|
|
||||||
|
if (stdout != NULL) fclose(stdout);
|
||||||
|
if (stderr != NULL) fclose(stderr);
|
||||||
|
|
||||||
DeleteFile(stdout_redirect);
|
DeleteFile(stdout_redirect);
|
||||||
DeleteFile(stderr_redirect);
|
DeleteFile(stderr_redirect);
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ from Queue import Queue, Empty
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
from urlparse import urlparse
|
||||||
|
|
||||||
from calibre.customize.ui import metadata_plugins, all_metadata_plugins
|
from calibre.customize.ui import metadata_plugins, all_metadata_plugins
|
||||||
from calibre.ebooks.metadata.sources.base import create_log, msprefs
|
from calibre.ebooks.metadata.sources.base import create_log, msprefs
|
||||||
@ -458,6 +459,14 @@ def urls_from_identifiers(identifiers): # {{{
|
|||||||
if oclc:
|
if oclc:
|
||||||
ans.append(('OCLC', 'oclc', oclc,
|
ans.append(('OCLC', 'oclc', oclc,
|
||||||
'http://www.worldcat.org/oclc/'+oclc))
|
'http://www.worldcat.org/oclc/'+oclc))
|
||||||
|
url = identifiers.get('uri', None)
|
||||||
|
if url is None:
|
||||||
|
url = identifiers.get('url', None)
|
||||||
|
if url and url.startswith('http'):
|
||||||
|
url = url[:8].replace('|', ':') + url[8:].replace('|', ',')
|
||||||
|
parts = urlparse(url)
|
||||||
|
name = parts.netloc
|
||||||
|
ans.append((name, 'url', url, url))
|
||||||
return ans
|
return ans
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ __docformat__ = 'restructuredtext en'
|
|||||||
Convert an ODT file into a Open Ebook
|
Convert an ODT file into a Open Ebook
|
||||||
'''
|
'''
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
from odf.odf2xhtml import ODF2XHTML
|
from odf.odf2xhtml import ODF2XHTML
|
||||||
|
|
||||||
from calibre import CurrentDir, walk
|
from calibre import CurrentDir, walk
|
||||||
@ -23,7 +25,51 @@ class Extract(ODF2XHTML):
|
|||||||
with open(name, 'wb') as f:
|
with open(name, 'wb') as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
|
||||||
def __call__(self, stream, odir):
|
def filter_css(self, html, log):
|
||||||
|
root = etree.fromstring(html)
|
||||||
|
style = root.xpath('//*[local-name() = "style" and @type="text/css"]')
|
||||||
|
if style:
|
||||||
|
style = style[0]
|
||||||
|
css = style.text
|
||||||
|
if css:
|
||||||
|
style.text, sel_map = self.do_filter_css(css)
|
||||||
|
for x in root.xpath('//*[@class]'):
|
||||||
|
extra = []
|
||||||
|
orig = x.get('class')
|
||||||
|
for cls in orig.split():
|
||||||
|
extra.extend(sel_map.get(cls, []))
|
||||||
|
if extra:
|
||||||
|
x.set('class', orig + ' ' + ' '.join(extra))
|
||||||
|
html = etree.tostring(root, encoding='utf-8',
|
||||||
|
xml_declaration=True)
|
||||||
|
return html
|
||||||
|
|
||||||
|
def do_filter_css(self, css):
|
||||||
|
from cssutils import parseString
|
||||||
|
from cssutils.css import CSSRule
|
||||||
|
sheet = parseString(css)
|
||||||
|
rules = list(sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE))
|
||||||
|
sel_map = {}
|
||||||
|
count = 0
|
||||||
|
for r in rules:
|
||||||
|
# Check if we have only class selectors for this rule
|
||||||
|
nc = [x for x in r.selectorList if not
|
||||||
|
x.selectorText.startswith('.')]
|
||||||
|
if len(r.selectorList) > 1 and not nc:
|
||||||
|
# Replace all the class selectors with a single class selector
|
||||||
|
# This will be added to the class attribute of all elements
|
||||||
|
# that have one of these selectors.
|
||||||
|
replace_name = 'c_odt%d'%count
|
||||||
|
count += 1
|
||||||
|
for sel in r.selectorList:
|
||||||
|
s = sel.selectorText[1:]
|
||||||
|
if s not in sel_map:
|
||||||
|
sel_map[s] = []
|
||||||
|
sel_map[s].append(replace_name)
|
||||||
|
r.selectorText = '.'+replace_name
|
||||||
|
return sheet.cssText, sel_map
|
||||||
|
|
||||||
|
def __call__(self, stream, odir, log):
|
||||||
from calibre.utils.zipfile import ZipFile
|
from calibre.utils.zipfile import ZipFile
|
||||||
from calibre.ebooks.metadata.meta import get_metadata
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
from calibre.ebooks.metadata.opf2 import OPFCreator
|
from calibre.ebooks.metadata.opf2 import OPFCreator
|
||||||
@ -32,13 +78,17 @@ class Extract(ODF2XHTML):
|
|||||||
if not os.path.exists(odir):
|
if not os.path.exists(odir):
|
||||||
os.makedirs(odir)
|
os.makedirs(odir)
|
||||||
with CurrentDir(odir):
|
with CurrentDir(odir):
|
||||||
print 'Extracting ODT file...'
|
log('Extracting ODT file...')
|
||||||
html = self.odf2xhtml(stream)
|
html = self.odf2xhtml(stream)
|
||||||
# A blanket img specification like this causes problems
|
# A blanket img specification like this causes problems
|
||||||
# with EPUB output as the contaiing element often has
|
# with EPUB output as the containing element often has
|
||||||
# an absolute height and width set that is larger than
|
# an absolute height and width set that is larger than
|
||||||
# the available screen real estate
|
# the available screen real estate
|
||||||
html = html.replace('img { width: 100%; height: 100%; }', '')
|
html = html.replace('img { width: 100%; height: 100%; }', '')
|
||||||
|
try:
|
||||||
|
html = self.filter_css(html, log)
|
||||||
|
except:
|
||||||
|
log.exception('Failed to filter CSS, conversion may be slow')
|
||||||
with open('index.xhtml', 'wb') as f:
|
with open('index.xhtml', 'wb') as f:
|
||||||
f.write(html.encode('utf-8'))
|
f.write(html.encode('utf-8'))
|
||||||
zf = ZipFile(stream, 'r')
|
zf = ZipFile(stream, 'r')
|
||||||
@ -67,7 +117,7 @@ class ODTInput(InputFormatPlugin):
|
|||||||
|
|
||||||
def convert(self, stream, options, file_ext, log,
|
def convert(self, stream, options, file_ext, log,
|
||||||
accelerators):
|
accelerators):
|
||||||
return Extract()(stream, '.')
|
return Extract()(stream, '.', log)
|
||||||
|
|
||||||
def postprocess_book(self, oeb, opts, log):
|
def postprocess_book(self, oeb, opts, log):
|
||||||
# Fix <p><div> constructs as the asinine epubchecker complains
|
# Fix <p><div> constructs as the asinine epubchecker complains
|
||||||
|
@ -15,7 +15,6 @@ import cStringIO
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from calibre.ebooks.metadata import authors_to_string
|
from calibre.ebooks.metadata import authors_to_string
|
||||||
from calibre.utils.filenames import ascii_text
|
|
||||||
from calibre.utils.magick.draw import save_cover_data_to, identify_data
|
from calibre.utils.magick.draw import save_cover_data_to, identify_data
|
||||||
|
|
||||||
TAGS = {
|
TAGS = {
|
||||||
|
@ -246,7 +246,8 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
def delete_requested(self, name, location):
|
def delete_requested(self, name, location):
|
||||||
loc = location.replace('/', os.sep)
|
loc = location.replace('/', os.sep)
|
||||||
if not question_dialog(self.gui, _('Are you sure?'), '<p>'+
|
if not question_dialog(self.gui, _('Are you sure?'), '<p>'+
|
||||||
_('<b style="color: red">All files</b> from <br><br><b>%s</b><br><br> will be '
|
_('<b style="color: red">All files</b> (not just ebooks) '
|
||||||
|
'from <br><br><b>%s</b><br><br> will be '
|
||||||
'<b>permanently deleted</b>. Are you sure?') % loc,
|
'<b>permanently deleted</b>. Are you sure?') % loc,
|
||||||
show_copy_button=False):
|
show_copy_button=False):
|
||||||
return
|
return
|
||||||
|
@ -439,10 +439,16 @@ class BooksView(QTableView): # {{{
|
|||||||
|
|
||||||
if tweaks['sort_columns_at_startup'] is not None:
|
if tweaks['sort_columns_at_startup'] is not None:
|
||||||
sh = []
|
sh = []
|
||||||
for c,d in tweaks['sort_columns_at_startup']:
|
try:
|
||||||
if not isinstance(d, bool):
|
for c,d in tweaks['sort_columns_at_startup']:
|
||||||
d = True if d == 0 else False
|
if not isinstance(d, bool):
|
||||||
sh.append((c, d))
|
d = True if d == 0 else False
|
||||||
|
sh.append((c, d))
|
||||||
|
except:
|
||||||
|
# Ignore invalid tweak values as users seem to often get them
|
||||||
|
# wrong
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
old_state['sort_history'] = sh
|
old_state['sort_history'] = sh
|
||||||
|
|
||||||
self.apply_state(old_state)
|
self.apply_state(old_state)
|
||||||
|
@ -299,13 +299,13 @@ def run_gui(opts, args, actions, listener, app, gui_debug=None):
|
|||||||
if getattr(runner.main, 'debug_on_restart', False):
|
if getattr(runner.main, 'debug_on_restart', False):
|
||||||
run_in_debug_mode()
|
run_in_debug_mode()
|
||||||
else:
|
else:
|
||||||
|
import subprocess
|
||||||
print 'Restarting with:', e, sys.argv
|
print 'Restarting with:', e, sys.argv
|
||||||
if hasattr(sys, 'frameworks_dir'):
|
if hasattr(sys, 'frameworks_dir'):
|
||||||
app = os.path.dirname(os.path.dirname(sys.frameworks_dir))
|
app = os.path.dirname(os.path.dirname(sys.frameworks_dir))
|
||||||
import subprocess
|
|
||||||
subprocess.Popen('sleep 3s; open '+app, shell=True)
|
subprocess.Popen('sleep 3s; open '+app, shell=True)
|
||||||
else:
|
else:
|
||||||
os.execvp(e, sys.argv)
|
subprocess.Popen([e] + sys.argv[1:])
|
||||||
else:
|
else:
|
||||||
if iswindows:
|
if iswindows:
|
||||||
try:
|
try:
|
||||||
|
@ -298,11 +298,12 @@ class AuthorSortEdit(EnLineEdit):
|
|||||||
self.current_val = self.db.author_sort_from_authors(authors)
|
self.current_val = self.db.author_sort_from_authors(authors)
|
||||||
|
|
||||||
def initialize(self, db, id_):
|
def initialize(self, db, id_):
|
||||||
self.current_val = db.author_sort(id_, index_is_id=True)
|
self.current_val = self.original_val = db.author_sort(id_, index_is_id=True)
|
||||||
|
|
||||||
def commit(self, db, id_):
|
def commit(self, db, id_):
|
||||||
aus = self.current_val
|
aus = self.current_val
|
||||||
db.set_author_sort(id_, aus, notify=False, commit=False)
|
if aus != self.original_val:
|
||||||
|
db.set_author_sort(id_, aus, notify=False, commit=False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
@ -33,7 +33,7 @@ from calibre import isbytestring
|
|||||||
from calibre.utils.filenames import ascii_filename
|
from calibre.utils.filenames import ascii_filename
|
||||||
from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp
|
from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp
|
||||||
from calibre.utils.config import prefs, tweaks, from_json, to_json
|
from calibre.utils.config import prefs, tweaks, from_json, to_json
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key, strcmp
|
||||||
from calibre.utils.search_query_parser import saved_searches, set_saved_searches
|
from calibre.utils.search_query_parser import saved_searches, set_saved_searches
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format
|
from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format
|
||||||
from calibre.utils.magick.draw import save_cover_data_to
|
from calibre.utils.magick.draw import save_cover_data_to
|
||||||
@ -1920,6 +1920,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
result.append(r)
|
result.append(r)
|
||||||
return ' & '.join(result).replace('|', ',')
|
return ' & '.join(result).replace('|', ',')
|
||||||
|
|
||||||
|
def _update_author_in_cache(self, id_, ss, final_authors):
|
||||||
|
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (ss, id_))
|
||||||
|
self.data.set(id_, self.FIELD_MAP['authors'],
|
||||||
|
','.join([a.replace(',', '|') for a in final_authors]),
|
||||||
|
row_is_id=True)
|
||||||
|
self.data.set(id_, self.FIELD_MAP['author_sort'], ss, row_is_id=True)
|
||||||
|
|
||||||
|
aum = self.authors_with_sort_strings(id_, index_is_id=True)
|
||||||
|
self.data.set(id_, self.FIELD_MAP['au_map'],
|
||||||
|
':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]),
|
||||||
|
row_is_id=True)
|
||||||
|
|
||||||
def _set_authors(self, id, authors, allow_case_change=False):
|
def _set_authors(self, id, authors, allow_case_change=False):
|
||||||
if not authors:
|
if not authors:
|
||||||
authors = [_('Unknown')]
|
authors = [_('Unknown')]
|
||||||
@ -1933,14 +1945,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
a = a.strip().replace(',', '|')
|
a = a.strip().replace(',', '|')
|
||||||
if not isinstance(a, unicode):
|
if not isinstance(a, unicode):
|
||||||
a = a.decode(preferred_encoding, 'replace')
|
a = a.decode(preferred_encoding, 'replace')
|
||||||
aus = self.conn.get('SELECT id, name FROM authors WHERE name=?', (a,))
|
aus = self.conn.get('SELECT id, name, sort FROM authors WHERE name=?', (a,))
|
||||||
if aus:
|
if aus:
|
||||||
aid, name = aus[0]
|
aid, name, sort = aus[0]
|
||||||
# Handle change of case
|
# Handle change of case
|
||||||
if name != a:
|
if name != a:
|
||||||
if allow_case_change:
|
if allow_case_change:
|
||||||
self.conn.execute('''UPDATE authors
|
ns = author_to_author_sort(a.replace('|', ','))
|
||||||
SET name=? WHERE id=?''', (a, aid))
|
if strcmp(sort, ns) == 0:
|
||||||
|
sort = ns
|
||||||
|
self.conn.execute('''UPDATE authors SET name=?, sort=?
|
||||||
|
WHERE id=?''', (a, sort, aid))
|
||||||
case_change = True
|
case_change = True
|
||||||
else:
|
else:
|
||||||
a = name
|
a = name
|
||||||
@ -1957,17 +1972,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
bks = self.conn.get('''SELECT book FROM books_authors_link
|
bks = self.conn.get('''SELECT book FROM books_authors_link
|
||||||
WHERE author=?''', (aid,))
|
WHERE author=?''', (aid,))
|
||||||
books_to_refresh |= set([bk[0] for bk in bks])
|
books_to_refresh |= set([bk[0] for bk in bks])
|
||||||
|
for bk in books_to_refresh:
|
||||||
|
ss = self.author_sort_from_book(id, index_is_id=True)
|
||||||
|
aus = self.author_sort(bk, index_is_id=True)
|
||||||
|
if strcmp(aus, ss) == 0:
|
||||||
|
self._update_author_in_cache(bk, ss, final_authors)
|
||||||
|
# This can repeat what was done above in rare cases. Let it.
|
||||||
ss = self.author_sort_from_book(id, index_is_id=True)
|
ss = self.author_sort_from_book(id, index_is_id=True)
|
||||||
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
self._update_author_in_cache(id, ss, final_authors)
|
||||||
(ss, id))
|
|
||||||
self.data.set(id, self.FIELD_MAP['authors'],
|
|
||||||
','.join([a.replace(',', '|') for a in final_authors]),
|
|
||||||
row_is_id=True)
|
|
||||||
self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True)
|
|
||||||
aum = self.authors_with_sort_strings(id, index_is_id=True)
|
|
||||||
self.data.set(id, self.FIELD_MAP['au_map'],
|
|
||||||
':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]),
|
|
||||||
row_is_id=True)
|
|
||||||
return books_to_refresh
|
return books_to_refresh
|
||||||
|
|
||||||
def set_authors(self, id, authors, notify=True, commit=True,
|
def set_authors(self, id, authors, notify=True, commit=True,
|
||||||
|
@ -841,11 +841,19 @@ ol, ul { padding-left: 2em; }
|
|||||||
self.styledict[name] = styles
|
self.styledict[name] = styles
|
||||||
# Write the styles to HTML
|
# Write the styles to HTML
|
||||||
self.writeout(self.default_styles)
|
self.writeout(self.default_styles)
|
||||||
|
# Changed by Kovid to not write out endless copies of the same style
|
||||||
|
css_styles = {}
|
||||||
for name in self.stylestack:
|
for name in self.stylestack:
|
||||||
styles = self.styledict.get(name)
|
styles = self.styledict.get(name)
|
||||||
css2 = self.cs.convert_styles(styles)
|
css2 = tuple(self.cs.convert_styles(styles).iteritems())
|
||||||
self.writeout("%s {\n" % name)
|
if css2 in css_styles:
|
||||||
for style, val in css2.items():
|
css_styles[css2].append(name)
|
||||||
|
else:
|
||||||
|
css_styles[css2] = [name]
|
||||||
|
|
||||||
|
for css2, names in css_styles.iteritems():
|
||||||
|
self.writeout("%s {\n" % ', '.join(names))
|
||||||
|
for style, val in css2:
|
||||||
self.writeout("\t%s: %s;\n" % (style, val) )
|
self.writeout("\t%s: %s;\n" % (style, val) )
|
||||||
self.writeout("}\n")
|
self.writeout("}\n")
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user