Merge from trunk

This commit is contained in:
Charles Haley 2012-07-17 08:49:53 +02:00
commit dbaac02e3c
19 changed files with 196 additions and 52 deletions

View File

@ -30,6 +30,7 @@ Environment variables
* ``CALIBRE_OVERRIDE_DATABASE_PATH`` - allows you to specify the full path to metadata.db. Using this variable you can have metadata.db be in a location other than the library folder. Useful if your library folder is on a networked drive that does not support file locking.
* ``CALIBRE_DEVELOP_FROM`` - Used to run from a calibre development environment. See :ref:`develop`.
* ``CALIBRE_OVERRIDE_LANG`` - Used to force the language used by the interface (ISO 639 language code)
* ``CALIBRE_NO_NATIVE_FILEDIALOGS`` - Causes calibre to not use native file dialogs for selecting files/directories.
* ``SYSFS_PATH`` - Use if sysfs is mounted somewhere other than /sys
* ``http_proxy`` - Used on linux to specify an HTTP proxy

View File

@ -12,7 +12,11 @@ class AmericanProspect(BasicNewsRecipe):
no_stylesheets = True
remove_javascript = True
keep_only_tags = [dict(name='div', attrs={'class':'pad_10L10R'})]
remove_tags = [dict(name='form'), dict(name='div', attrs={'class':['bkt_caption','sharebox noprint','badgebox']})]
#keep_only_tags = [dict(name='div', attrs={'class':'pad_10L10R'})]
#remove_tags = [dict(name='form'), dict(name='div', attrs={'class':['bkt_caption','sharebox noprint','badgebox']})]
use_embedded_content = False
no_stylesheets = True
auto_cleanup = True
feeds = [(u'Articles', u'feed://www.prospect.org/articles_rss.jsp')]

View File

@ -6,16 +6,15 @@ from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1325006965(BasicNewsRecipe):
title = u'The Sun UK'
description = 'A Recipe for The Sun tabloid UK'
description = 'Articles from The Sun tabloid UK'
__author__ = 'Dave Asbury'
# last updated 29/4/12
# last updated 15/7/12
language = 'en_GB'
oldest_article = 1
max_articles_per_feed = 15
remove_empty_feeds = True
no_stylesheets = True
#auto_cleanup = True
#articles_are_obfuscated = True
masthead_url = 'http://www.thesun.co.uk/sol/img/global/Sun-logo.gif'
encoding = 'UTF-8'
@ -34,7 +33,7 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
keep_only_tags = [
dict(name='h1'),dict(name='h2',attrs={'class' : 'medium centered'}),
dict(name='h1'),dict(name='h2',attrs={'class' : ['large','large centered','medium centered','medium']}),dict(name='h3'),
dict(name='div',attrs={'class' : 'text-center'}),
dict(name='div',attrs={'id' : 'bodyText'})
# dict(name='p')
@ -72,22 +71,18 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
cov2 = str(cov)
cov2=cov2[27:-18]
#cov2 now is pic url, now go back to original function
br = browser()
br.set_handle_redirect(False)
try:
br.open_novisit(cov2)
cover_url = cov2
except:
cover_url = random.choice((
cover_url = random.choice([
'http://img.thesun.co.uk/multimedia/archive/00905/errorpage6_677961a_905507a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage7_677962a_905505a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage5_677960a_905512a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage2_677957a_905502a.jpg'
,'http://img.thesun.co.uk/multimedia/archive/00905/errorpage3_677958a_905503a.jpg'
))
])
return cover_url

View File

@ -98,15 +98,6 @@ authors_split_regex = r'(?i),?\s+(and|with)\s+'
# categories_use_field_for_author_name = 'author_sort'
categories_use_field_for_author_name = 'author'
#: Completion sort order: choose when to change from lexicographic to ASCII-like
# Calibre normally uses locale-dependent lexicographic ordering when showing
# completion values. This means that the sort order is correct for the user's
# language. However, this can be slow. Performance is improved by switching to
# ascii ordering. This tweak controls when that switch happens. Set it to zero
# to always use ascii ordering. Set it to something larger than zero to switch
# to ascii ordering for performance reasons.
completion_change_to_ascii_sorting = 2500
#: Control partitioning of Tag Browser
# When partitioning the tags browser, the format of the subcategory label is
# controlled by a template: categories_collapsed_name_template if sorting by
@ -525,3 +516,11 @@ default_tweak_format = None
# enable_multicharacters_in_tag_browser = False
enable_multicharacters_in_tag_browser = True
#: Do not preselect a completion when editing authors/tags/series/etc.
# This means that you can make changes and press Enter and your changes will
# not be overwritten by a matching completion. However, if you wish to use the
# completions you will now have to press Tab to select one before pressing
# Enter. Which technique you prefer will depend on the state of metadata in
# your library and your personal editing style.
preselect_first_completion = False

View File

@ -23,6 +23,7 @@ MAGICK_PREFIX = '/usr'
binary_includes = [
'/usr/bin/pdftohtml',
'/usr/bin/pdfinfo',
'/usr/lib/libglib-2.0.so.0',
'/usr/bin/pdftoppm',
'/usr/lib/libwmflite-0.2.so.7',
'/usr/lib/liblcms.so.1',

View File

@ -1483,6 +1483,16 @@ class StoreManyBooksStore(StoreBase):
headquarters = 'US'
formats = ['EPUB', 'FB2', 'JAR', 'LIT', 'LRF', 'MOBI', 'PDB', 'PDF', 'RB', 'RTF', 'TCR', 'TXT', 'ZIP']
class StoreMillsBoonUKStore(StoreBase):
name = 'Mills and Boon UK'
author = 'Charles Haley'
description = u'"Bring Romance to Life" "[A] hallmark for romantic fiction, recognised around the world."'
actual_plugin = 'calibre.gui2.store.stores.mills_boon_uk_plugin:MillsBoonUKStore'
headquarters = 'UK'
formats = ['EPUB']
affiliate = True
class StoreMobileReadStore(StoreBase):
name = 'MobileRead'
description = u'Ebooks handcrafted with the utmost care.'
@ -1646,6 +1656,7 @@ plugins += [
StoreLibreDEStore,
StoreLitResStore,
StoreManyBooksStore,
StoreMillsBoonUKStore,
StoreMobileReadStore,
StoreNextoStore,
StoreOpenBooksStore,

View File

@ -194,7 +194,7 @@ class ANDROID(USBMS):
'GENERIC-', 'ZTE', 'MID', 'QUALCOMM', 'PANDIGIT', 'HYSTON',
'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC', 'LENOVO', 'ROCKCHIP',
'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD',
'PMP5097C']
'PMP5097C', 'MASS', 'NOVO7']
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID',
@ -211,7 +211,8 @@ class ANDROID(USBMS):
'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW',
'KTABLET_PC', 'INGENIC', 'GT-I9001_CARD', 'USB_2.0_DRIVER',
'GT-S5830L_CARD', 'UNIVERSE', 'XT875', 'PRO', '.KOBO_VOX',
'THINKPAD_TABLET', 'SGH-T989', 'YP-G70']
'THINKPAD_TABLET', 'SGH-T989', 'YP-G70', 'STORAGE_DEVICE',
'ADVANCED']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',

View File

@ -163,6 +163,7 @@ class EXTHRecord(object):
501 : 'cdetype', # 4 chars (PDOC or EBOK)
502 : 'lastupdatetime',
503 : 'updatedtitle',
524 : 'language',
}.get(self.type, repr(self.type))
if (self.name in {'coveroffset', 'thumboffset', 'hasfakecover',

View File

@ -13,6 +13,7 @@ from calibre.utils.date import parse_date
from calibre.ebooks.mobi import MobiError
from calibre.ebooks.metadata import MetaInformation, check_isbn
from calibre.ebooks.mobi.langcodes import main_language, sub_language, mobi2iana
from calibre.utils.localization import canonicalize_lang
NULL_INDEX = 0xffffffff
@ -68,6 +69,14 @@ class EXTHHeader(object): # {{{
title = content.decode(codec)
except:
pass
elif idx == 524: # Lang code
try:
lang = content.decode(codec)
lang = canonicalize_lang(lang)
if lang:
self.mi.language = lang
except:
pass
#else:
# print 'unknown record', idx, repr(content)
if title:
@ -201,10 +210,11 @@ class BookHeader(object):
self.exth = EXTHHeader(raw[16 + self.length:], self.codec,
self.title)
self.exth.mi.uid = self.unique_id
try:
self.exth.mi.language = mobi2iana(langid, sublangid)
except:
self.log.exception('Unknown language code')
if self.exth.mi.is_null('language'):
try:
self.exth.mi.language = mobi2iana(langid, sublangid)
except:
self.log.exception('Unknown language code')
except:
self.log.exception('Invalid EXTH header')
self.exth_flag = 0

View File

@ -297,10 +297,13 @@ class MobiWriter(object):
# 0x70 - 0x73 : EXTH flags
# Bit 6 (0b1000000) being set indicates the presence of an EXTH header
# Bit 12 being set indicates the presence of embedded fonts
# The purpose of the other bits is unknown
exth_flags = 0b1010000
if self.is_periodical:
exth_flags |= 0b1000
if self.resources.has_fonts:
exth_flags |= 0b1000000000000
record0.write(pack(b'>I', exth_flags))
# 0x74 - 0x93 : Unknown
@ -406,7 +409,10 @@ class MobiWriter(object):
# Now change the header fields that need to be different in the MOBI 6
# header
header_fields['first_resource_record'] = first_image_record
header_fields['exth_flags'] = 0b100001010000 # Kinglegen uses this
ef = 0b100001010000 # Kinglegen uses this
if self.resources.has_fonts:
ef |= 0b1000000000000
header_fields['exth_flags'] = ef
header_fields['fdst_record'] = pack(b'>HH', 1, last_content_record)
header_fields['fdst_count'] = 1 # Why not 0? Kindlegen uses 1
header_fields['flis_record'] = flis_number

View File

@ -32,6 +32,7 @@ class Resources(object):
self.used_image_indices = set()
self.image_indices = set()
self.cover_offset = self.thumbnail_offset = None
self.has_fonts = False
self.add_resources(add_fonts)
@ -109,6 +110,7 @@ class Resources(object):
'ttf', 'otf'} and isinstance(item.data, bytes):
self.records.append(write_font_record(item.data))
self.item_map[item.href] = len(self.records)
self.has_fonts = True
def add_extra_images(self):
'''

View File

@ -12,6 +12,7 @@ from struct import pack
from io import BytesIO
from calibre.ebooks.mobi.utils import utf8_text
from calibre.utils.localization import lang_as_iso639_1
EXTH_CODES = {
'creator': 100,
@ -35,6 +36,7 @@ EXTH_CODES = {
'hasfakecover': 203,
'lastupdatetime': 502,
'title': 503,
'language': 524,
}
COLLAPSE_RE = re.compile(r'[ \t\r\n\v]+')
@ -57,6 +59,16 @@ def build_exth(metadata, prefer_author_sort=False, is_periodical=False,
else:
creators = [unicode(c) for c in items]
items = creators
elif term == 'rights':
try:
rights = utf8_text(unicode(metadata.rights[0]))
except:
rights = b'Unknown'
exth.write(pack(b'>II', EXTH_CODES['rights'], len(rights) + 8))
exth.write(rights)
nrecs += 1
continue
for item in items:
data = unicode(item)
if term != 'description':
@ -68,18 +80,14 @@ def build_exth(metadata, prefer_author_sort=False, is_periodical=False,
pass
else:
continue
if term == 'language':
d2 = lang_as_iso639_1(data)
if d2:
data = d2
data = utf8_text(data)
exth.write(pack(b'>II', code, len(data) + 8))
exth.write(data)
nrecs += 1
if term == 'rights' :
try:
rights = utf8_text(unicode(metadata.rights[0]))
except:
rights = b'Unknown'
exth.write(pack(b'>II', EXTH_CODES['rights'], len(rights) + 8))
exth.write(rights)
nrecs += 1
# Write UUID as ASIN
uuid = None
@ -132,7 +140,7 @@ def build_exth(metadata, prefer_author_sort=False, is_periodical=False,
nrecs += 1
if be_kindlegen2:
vals = {204:201, 205:2, 206:2, 207:35621}
vals = {204:201, 205:2, 206:5, 207:0}
elif is_periodical:
# Pretend to be amazon's super secret periodical generator
vals = {204:201, 205:2, 206:0, 207:101}

View File

@ -277,6 +277,8 @@ class KF8Book(object):
self.exth_flags = 0b1010000
if writer.opts.mobi_periodical:
self.exth_flags |= 0b1000
if resources.has_fonts:
self.exth_flags |= 0b1000000000000
self.opts = writer.opts
self.start_offset = writer.start_offset

View File

@ -223,7 +223,8 @@ class PDFWriter(QObject): # {{{
if self.cover_data is None:
return
item_path = os.path.join(self.tmp_path, 'cover.pdf')
printer = get_pdf_printer(self.opts, output_file_name=item_path)
printer = get_pdf_printer(self.opts, output_file_name=item_path,
for_comic=True)
self.combine_queue.insert(0, item_path)
p = QPixmap()
p.loadFromData(self.cover_data)

View File

@ -573,17 +573,24 @@ class FileDialog(QObject):
if not isinstance(initial_dir, basestring):
initial_dir = os.path.expanduser(default_dir)
self.selected_files = []
use_native_dialog = not os.environ.has_key('CALIBRE_NO_NATIVE_FILEDIALOGS')
with SanitizeLibraryPath():
opts = QFileDialog.Option()
if not use_native_dialog:
opts |= QFileDialog.DontUseNativeDialog
if mode == QFileDialog.AnyFile:
f = unicode(QFileDialog.getSaveFileName(parent, title, initial_dir, ftext, ""))
f = unicode(QFileDialog.getSaveFileName(parent, title,
initial_dir, ftext, "", opts))
if f:
self.selected_files.append(f)
elif mode == QFileDialog.ExistingFile:
f = unicode(QFileDialog.getOpenFileName(parent, title, initial_dir, ftext, ""))
f = unicode(QFileDialog.getOpenFileName(parent, title,
initial_dir, ftext, "", opts))
if f and os.path.exists(f):
self.selected_files.append(f)
elif mode == QFileDialog.ExistingFiles:
fs = QFileDialog.getOpenFileNames(parent, title, initial_dir, ftext, "")
fs = QFileDialog.getOpenFileNames(parent, title, initial_dir,
ftext, "", opts)
for f in fs:
f = unicode(f)
if not f: continue
@ -594,7 +601,8 @@ class FileDialog(QObject):
if f and os.path.exists(f):
self.selected_files.append(f)
else:
opts = QFileDialog.ShowDirsOnly if mode == QFileDialog.Directory else QFileDialog.Option()
if mode == QFileDialog.Directory:
opts |= QFileDialog.ShowDirsOnly
f = unicode(QFileDialog.getExistingDirectory(parent, title, initial_dir, opts))
if os.path.exists(f):
self.selected_files.append(f)

View File

@ -16,7 +16,6 @@ from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt,
from calibre.utils.icu import sort_key
from calibre.gui2 import NONE
from calibre.gui2.widgets import EnComboBox, LineEditECM
from calibre.utils.config_base import tweaks
class CompleteModel(QAbstractListModel):
@ -27,7 +26,7 @@ class CompleteModel(QAbstractListModel):
def set_items(self, items):
items = [unicode(x.strip()) for x in items]
if len(items) < tweaks['completion_change_to_ascii_sorting']:
if len(items) < 2500:
self.items = sorted(items, key=sort_key)
self.sorting = QCompleter.UnsortedModel
else:

View File

@ -16,6 +16,7 @@ from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, pyqtSignal, QObject,
from calibre.utils.icu import sort_key, primary_startswith
from calibre.gui2 import NONE
from calibre.gui2.widgets import EnComboBox, LineEditECM
from calibre.utils.config import tweaks
class CompleteModel(QAbstractListModel): # {{{
@ -157,8 +158,8 @@ class Completer(QListView): # {{{
p.setGeometry(pos.x(), pos.y(), w, h)
if (select_first and not self.currentIndex().isValid() and
self.model().rowCount() > 0):
if (tweaks['preselect_first_completion'] and select_first and not
self.currentIndex().isValid() and self.model().rowCount() > 0):
self.setCurrentIndex(self.model().index(0))
if not p.isVisible():
@ -189,12 +190,11 @@ class Completer(QListView): # {{{
e.accept()
return True
return False
if key in (Qt.Key_End, Qt.Key_Home, Qt.Key_Up, Qt.Key_Down,
Qt.Key_PageUp, Qt.Key_PageDown):
if key in (Qt.Key_PageUp, Qt.Key_PageDown):
# Let the list view handle these keys
return False
if key in (Qt.Key_Tab, Qt.Key_Backtab):
self.next_match(previous=key == Qt.Key_Backtab)
if key in (Qt.Key_Tab, Qt.Key_Backtab, Qt.Key_Up, Qt.Key_Down):
self.next_match(previous=key in (Qt.Key_Backtab, Qt.Key_Up))
e.accept()
return True
# Send to widget
@ -284,6 +284,8 @@ class LineEdit(QLineEdit, LineEditECM):
if self.no_popup: return
self.update_completions()
select_first = len(self.mcompleter.model().current_prefix) > 0
if not select_first:
self.mcompleter.setCurrentIndex(QModelIndex())
self.complete(select_first=select_first)
def update_completions(self):

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import urllib2
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class MillsBoonUKStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://www.awin1.com/awclick.php?mid=1150&id=120917'
detail_url = 'http://www.awin1.com/cread.php?awinmid=1150&awinaffid=120917&clickref=&p='
if external or self.config.get('open_external', False):
if detail_item:
url = detail_url + detail_item
open_url(QUrl(url_slash_cleaner(url)))
else:
detail_url = None
if detail_item:
detail_url = url + detail_item
d = WebStoreDialog(self.gui, url, parent, detail_url)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
base_url = 'http://millsandboon.co.uk'
url = base_url + '/pages/searchres.htm?search=true&booktypesearch=ebook&first=yes&inputsearch=' + urllib2.quote(query)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//div[@class="catProdDiv"]'):
if counter <= 0:
break
id_ = ''.join(data.xpath('.//div[@class="catProdImage"]/div/a/@href')).strip()
id_ = base_url + id_[2:]
if not id_:
continue
cover_url = ''.join(data.xpath('.//div[@class="catProdImage"]/div/a/img/@src'))
cover_url = base_url + cover_url[2:]
title = ''.join(data.xpath('.//div[@class="catProdImage"]/div/a/img/@alt')).strip()
title = title[23:]
author = ''.join(data.xpath('.//div[@class="catProdDetails"]/div[@class="catProdDetails-top"]/p[1]/a/text()'))
price = ''.join(data.xpath('.//span[@class="priceBold"]/text()'))
format_ = ''.join(data.xpath('.//p[@class="doc-meta-format"]/span[last()]/text()'))
drm = SearchResult.DRM_LOCKED
counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.title = title.strip()
s.author = author.strip()
s.price = price
s.detail_item = id_
s.drm = drm
s.formats = format_
yield s

View File

@ -1096,8 +1096,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
identical_book_ids = set([])
if mi.authors:
try:
quathors = mi.authors[:10] # Too many authors causes parsing of
# the search expression to fail
query = u' and '.join([u'author:"=%s"'%(a.replace('"', '')) for a in
mi.authors])
quathors])
qauthors = mi.authors[10:]
except ValueError:
return identical_book_ids
try:
@ -1105,6 +1108,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
except:
traceback.print_exc()
return identical_book_ids
if qauthors and book_ids:
matches = set()
qauthors = {lower(x) for x in qauthors}
for book_id in book_ids:
aut = self.authors(book_id, index_is_id=True)
if aut:
aut = {lower(x.replace('|', ',')) for x in
aut.split(',')}
if aut.issuperset(qauthors):
matches.add(book_id)
book_ids = matches
for book_id in book_ids:
fbook_title = self.title(book_id, index_is_id=True)
fbook_title = fuzzy_title(fbook_title)