so we can format with CSS
- divTag = soup.find('div',attrs={'id':'authorId'})
- if divTag and divTag.contents[0]:
- tag = Tag(soup, "p")
- tag['class'] = "authorId"
- tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0],
- use_alt=False)))
- divTag.replaceWith(tag)
-
- return soup
-
- def populate_article_metadata(self,article,soup,first):
- '''
- Extract author and description from article, add to article metadata
- '''
- def extract_author(soup):
- byline = soup.find('meta',attrs={'name':['byl','CLMST']})
- if byline :
- author = byline['content']
- else :
- # Try for
- byline = soup.find('div', attrs={'class':'byline'})
- if byline:
- author = byline.renderContents()
- else:
- print soup.prettify()
- return None
- return author
-
- def extract_description(soup):
- description = soup.find('meta',attrs={'name':['description','description ']})
- if description :
- return self.massageNCXText(description['content'])
- else:
- # Take first paragraph of article
- articlebody = soup.find('div',attrs={'id':'articlebody'})
- if not articlebody:
- # Try again with class instead of id
- articlebody = soup.find('div',attrs={'class':'articlebody'})
- if not articlebody:
- print 'postprocess_book.extract_description(): Did not find
:'
- print soup.prettify()
- return None
- paras = articlebody.findAll('p')
- for p in paras:
- if p.renderContents() > '' :
- return self.massageNCXText(self.tag_to_string(p,use_alt=False))
- return None
-
- if not article.author:
- article.author = extract_author(soup)
- if not article.summary:
- article.summary = article.text_summary = extract_description(soup)
-
- def strip_anchors(self,soup):
- paras = soup.findAll(True)
- for para in paras:
- aTags = para.findAll('a')
- for a in aTags:
- if a.img is None:
- a.replaceWith(a.renderContents().decode('utf-8','replace'))
- #a.replaceWith(a.renderContents().decode('cp1252','replace'))
- return soup
diff --git a/setup/extensions.py b/setup/extensions.py
index df6f0ffbcd..d520cbf622 100644
--- a/setup/extensions.py
+++ b/setup/extensions.py
@@ -54,7 +54,7 @@ reflow_error = poppler_error if poppler_error else magick_error
pdfreflow_libs = []
if iswindows:
- pdfreflow_libs = ['advapi32', 'User32', 'Gdi32']
+ pdfreflow_libs = ['advapi32', 'User32', 'Gdi32', 'zlib']
extensions = [
diff --git a/setup/installer/windows/notes.rst b/setup/installer/windows/notes.rst
index f41b7215b5..a8ba41e8ff 100644
--- a/setup/installer/windows/notes.rst
+++ b/setup/installer/windows/notes.rst
@@ -213,7 +213,7 @@ It contains correct fonts.conf etc.
poppler
-------------
-In Cmake: disable GTK, Qt, OPenjpeg, zlib, lcms, gtk_tests, qt_tests. Enable qt4, jpeg, png and zlib
+In Cmake: disable GTK, Qt, OPenjpeg, cpp, lcms, gtk_tests, qt_tests. Enable qt4, jpeg, png and zlib
NOTE: poppler must be built as a static library, unless you build the qt4 bindings
diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py
index 27f0805f86..1df2e65f1e 100644
--- a/src/calibre/customize/profiles.py
+++ b/src/calibre/customize/profiles.py
@@ -4,6 +4,7 @@ __license__ = 'GPL 3'
__copyright__ = '2009, Kovid Goyal '
__docformat__ = 'restructuredtext en'
+import sys
from itertools import izip
from xml.sax.saxutils import escape
@@ -417,6 +418,13 @@ class iPadOutput(OutputProfile):
'''
# }}}
+class TabletOutput(iPadOutput):
+ name = 'Tablet'
+ short_name = 'tablet'
+ description = _('Intended for generic tablet devices, does no resizing of images')
+
+ screen_size = (sys.maxint, sys.maxint)
+ comic_screen_size = (sys.maxint, sys.maxint)
class SonyReaderOutput(OutputProfile):
@@ -664,7 +672,7 @@ class BambookOutput(OutputProfile):
output_profiles = [OutputProfile, SonyReaderOutput, SonyReader300Output,
SonyReader900Output, MSReaderOutput, MobipocketOutput, HanlinV3Output,
HanlinV5Output, CybookG3Output, CybookOpusOutput, KindleOutput,
- iPadOutput, KoboReaderOutput,
+ iPadOutput, KoboReaderOutput, TabletOutput,
SonyReaderLandscapeOutput, KindleDXOutput, IlliadOutput,
IRexDR1000Output, IRexDR800Output, JetBook5Output, NookOutput,
BambookOutput, ]
diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py
index dd08c745b1..918de28e67 100644
--- a/src/calibre/devices/android/driver.py
+++ b/src/calibre/devices/android/driver.py
@@ -20,7 +20,8 @@ class ANDROID(USBMS):
VENDOR_ID = {
# HTC
0x0bb4 : { 0x0c02 : [0x100, 0x0227], 0x0c01 : [0x100, 0x0227], 0x0ff9
- : [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226]},
+ : [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226],
+ 0xc92 : [0x100]},
# Motorola
0x22b8 : { 0x41d9 : [0x216], 0x2d67 : [0x100], 0x41db : [0x216],
diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py
index 418bfe5e0d..3562da55d2 100644
--- a/src/calibre/devices/kobo/driver.py
+++ b/src/calibre/devices/kobo/driver.py
@@ -22,7 +22,9 @@ class KOBO(USBMS):
gui_name = 'Kobo Reader'
description = _('Communicate with the Kobo Reader')
author = 'Timothy Legge and Kovid Goyal'
- version = (1, 0, 6)
+ version = (1, 0, 7)
+
+ dbversion = 0
supported_platforms = ['windows', 'osx', 'linux']
@@ -92,7 +94,7 @@ class KOBO(USBMS):
if lpath.startswith(os.sep):
lpath = lpath[len(os.sep):]
lpath = lpath.replace('\\', '/')
-# print "LPATH: " + lpath
+ # debug_print("LPATH: ", lpath, " - Title: " , title)
playlist_map = {}
@@ -112,7 +114,7 @@ class KOBO(USBMS):
#print "Image name Normalized: " + imagename
if imagename is not None:
bl[idx].thumbnail = ImageWrapper(imagename)
- if ContentType != '6':
+ if (ContentType != '6'and self.dbversion < 8) or (self.dbversion >= 8):
if self.update_metadata_item(bl[idx]):
# print 'update_metadata_item returned true'
changed = True
@@ -120,10 +122,16 @@ class KOBO(USBMS):
playlist_map[lpath] not in bl[idx].device_collections:
bl[idx].device_collections.append(playlist_map[lpath])
else:
- if ContentType == '6':
+ if ContentType == '6' and self.dbversion < 8:
book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID, size=1048576)
else:
- book = self.book_from_path(prefix, lpath, title, authors, mime, date, ContentType, ImageID)
+ try:
+ book = self.book_from_path(prefix, lpath, title, authors, mime, date, ContentType, ImageID)
+ except:
+ debug_print("prefix: ", prefix, "lpath: ", lpath, "title: ", title, "authors: ", authors, \
+ "mime: ", mime, "date: ", date, "ContentType: ", ContentType, "ImageID: ", ImageID)
+ raise
+
# print 'Update booklist'
book.device_collections = [playlist_map[lpath]] if lpath in playlist_map else []
@@ -143,6 +151,13 @@ class KOBO(USBMS):
# numrows = row[0]
#cursor.close()
+ # Determine the database version
+ # 4 - Bluetooth Kobo Rev 2 (1.4)
+ # 8 - WIFI KOBO Rev 1
+ cursor.execute('select version from dbversion')
+ result = cursor.fetchone()
+ self.dbversion = result[0]
+
query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
'ImageID, ReadStatus from content where BookID is Null'
@@ -153,7 +168,8 @@ class KOBO(USBMS):
# self.report_progress((i+1) / float(numrows), _('Getting list of books on device...'))
path = self.path_from_contentid(row[3], row[5], oncard)
- mime = mime_type_ext(path_to_ext(row[3]))
+ mime = mime_type_ext(path_to_ext(path)) if path.find('kepub') == -1 else 'application/epub+zip'
+ # debug_print("mime:", mime)
if oncard != 'carda' and oncard != 'cardb' and not row[3].startswith("file:///mnt/sd/"):
changed = update_booklist(self._main_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7])
@@ -206,7 +222,7 @@ class KOBO(USBMS):
cursor.close()
cursor = connection.cursor()
- if ContentType == 6:
+ if ContentType == 6 and self.dbversion < 8:
# Delete the shortcover_pages first
cursor.execute('delete from shortcover_page where shortcoverid in (select ContentID from content where BookID = ?)', t)
@@ -249,7 +265,7 @@ class KOBO(USBMS):
path = self.normalize_path(path)
# print "Delete file normalized path: " + path
extension = os.path.splitext(path)[1]
- ContentType = self.get_content_type_from_extension(extension)
+ ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(path)
ContentID = self.contentid_from_path(path, ContentType)
@@ -332,9 +348,14 @@ class KOBO(USBMS):
def contentid_from_path(self, path, ContentType):
if ContentType == 6:
- ContentID = os.path.splitext(path)[0]
- # Remove the prefix on the file. it could be either
- ContentID = ContentID.replace(self._main_prefix, '')
+ if self.dbversion < 8:
+ ContentID = os.path.splitext(path)[0]
+ # Remove the prefix on the file. it could be either
+ ContentID = ContentID.replace(self._main_prefix, '')
+ else:
+ ContentID = path
+ ContentID = ContentID.replace(self._main_prefix + '.kobo/kepub/', '')
+
if self._card_a_prefix is not None:
ContentID = ContentID.replace(self._card_a_prefix, '')
elif ContentType == 999: # HTML Files
@@ -350,6 +371,13 @@ class KOBO(USBMS):
ContentID = ContentID.replace("\\", '/')
return ContentID
+ def get_content_type_from_path(self, path):
+ # Strictly speaking the ContentType could be 6 or 10
+ # however newspapers have the same storage format
+ if path.find('kepub') >= 0:
+ ContentType = 6
+ return ContentType
+
def get_content_type_from_extension(self, extension):
if extension == '.kobo':
# Kobo books do not have book files. They do have some images though
@@ -369,19 +397,22 @@ class KOBO(USBMS):
print 'path from_contentid cardb'
elif oncard == 'carda':
path = path.replace("file:///mnt/sd/", self._card_a_prefix)
- # print "SD Card: " + filename
+ # print "SD Card: " + path
else:
- if ContentType == "6":
+ if ContentType == "6" and self.dbversion < 8:
# This is a hack as the kobo files do not exist
# but the path is required to make a unique id
# for calibre's reference
path = self._main_prefix + path + '.kobo'
# print "Path: " + path
+ elif (ContentType == "6" or ContentType == "10") and self.dbversion >= 8:
+ path = self._main_prefix + '.kobo/kepub/' + path
+ # print "Internal: " + path
else:
# if path.startswith("file:///mnt/onboard/"):
path = path.replace("file:///mnt/onboard/", self._main_prefix)
path = path.replace("/mnt/onboard/", self._main_prefix)
- # print "Internal: " + filename
+ # print "Internal: " + path
return path
@@ -469,7 +500,7 @@ class KOBO(USBMS):
book.device_collections = ['Im_Reading']
extension = os.path.splitext(book.path)[1]
- ContentType = self.get_content_type_from_extension(extension)
+ ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path)
ContentID = self.contentid_from_path(book.path, ContentType)
datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
@@ -505,7 +536,7 @@ class KOBO(USBMS):
book.device_collections = ['Read']
extension = os.path.splitext(book.path)[1]
- ContentType = self.get_content_type_from_extension(extension)
+ ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path)
ContentID = self.contentid_from_path(book.path, ContentType)
# datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
diff --git a/src/calibre/devices/nokia/driver.py b/src/calibre/devices/nokia/driver.py
index f378a656fb..ed10f849d0 100644
--- a/src/calibre/devices/nokia/driver.py
+++ b/src/calibre/devices/nokia/driver.py
@@ -36,15 +36,15 @@ class N770(USBMS):
class N810(N770):
name = 'Nokia 810 Device Interface'
- gui_name = 'Nokia 810'
- description = _('Communicate with the Nokia 810 internet tablet.')
+ gui_name = 'Nokia 810/900'
+ description = _('Communicate with the Nokia 810/900 internet tablet.')
- PRODUCT_ID = [0x96]
+ PRODUCT_ID = [0x96, 0x1c7]
BCD = [0x316]
- WINDOWS_MAIN_MEM = 'N810'
+ WINDOWS_MAIN_MEM = ['N810', 'N900']
- MAIN_MEMORY_VOLUME_LABEL = 'N810 Main Memory'
+ MAIN_MEMORY_VOLUME_LABEL = 'Nokia Tablet Main Memory'
class E71X(USBMS):
diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py
index 15245d3cd5..17eea3a27c 100644
--- a/src/calibre/devices/prs505/sony_cache.py
+++ b/src/calibre/devices/prs505/sony_cache.py
@@ -573,7 +573,10 @@ class XMLCache(object):
ans = root.makeelement('{%s}text'%namespace, attrib=attrib,
nsmap=root.nsmap)
ans.tail = '\n'
- root[-1].tail = '\n' + '\t'
+ if len(root) > 0:
+ root[-1].tail = '\n\t'
+ else:
+ root.text = '\n\t'
root.append(ans)
if thumbnail and thumbnail[-1]:
ans.text = '\n' + '\t\t'
diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py
index f520365c1c..9a863d7e66 100644
--- a/src/calibre/ebooks/conversion/plumber.py
+++ b/src/calibre/ebooks/conversion/plumber.py
@@ -14,7 +14,7 @@ from calibre.ebooks.conversion.preprocess import HTMLPreProcessor
from calibre.ptempfile import PersistentTemporaryDirectory
from calibre.utils.date import parse_date
from calibre.utils.zipfile import ZipFile
-from calibre import extract, walk
+from calibre import extract, walk, isbytestring, filesystem_encoding
from calibre.constants import __version__
DEBUG_README=u'''
@@ -77,6 +77,10 @@ class Plumber(object):
:param input: Path to input file.
:param output: Path to output file/directory
'''
+ if isbytestring(input):
+ input = input.decode(filesystem_encoding)
+ if isbytestring(output):
+ output = output.decode(filesystem_encoding)
self.original_input_arg = input
self.input = os.path.abspath(input)
self.output = os.path.abspath(output)
diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py
index 9105890d44..cd6674c2e2 100644
--- a/src/calibre/ebooks/mobi/writer.py
+++ b/src/calibre/ebooks/mobi/writer.py
@@ -2043,12 +2043,16 @@ class MobiWriter(object):
else :
self._oeb.logger.info("chapterCount: %d" % self._chapterCount)
- if True:
- rec_count = len(self._ctoc_records)
- self._oeb.logger.info(" CNCX utilization: %d %s %.0f%% full" % \
- (rec_count + 1, 'records, last record' if rec_count else 'record,', len(self._ctoc.getvalue())/655) )
+ # Apparently the CTOC must end with a null byte
+ self._ctoc.write('\0')
- return align_block(self._ctoc.getvalue())
+ ctoc = self._ctoc.getvalue()
+ rec_count = len(self._ctoc_records)
+ self._oeb.logger.info(" CNCX utilization: %d %s %.0f%% full" % \
+ (rec_count + 1, 'records, last record' if rec_count else 'record,',
+ len(ctoc)/655) )
+
+ return align_block(ctoc)
def _write_periodical_node(self, indxt, indices, index, offset, length, count, firstSection, lastSection) :
pos = 0xc0 + indxt.tell()
diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py
index cf80e4abe2..632696b4ae 100644
--- a/src/calibre/ebooks/oeb/base.py
+++ b/src/calibre/ebooks/oeb/base.py
@@ -25,6 +25,7 @@ from calibre.translations.dynamic import translate
from calibre.ebooks.chardet import xml_to_unicode
from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
from calibre.ebooks.conversion.preprocess import CSSPreProcessor
+from calibre import isbytestring
RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True)
@@ -404,7 +405,8 @@ class DirContainer(object):
def __init__(self, path, log):
self.log = log
- path = unicode(path)
+ if isbytestring(path):
+ path = path.decode(filesystem_encoding)
ext = os.path.splitext(path)[1].lower()
if ext == '.opf':
self.opfname = os.path.basename(path)
diff --git a/src/calibre/ebooks/oeb/transforms/rescale.py b/src/calibre/ebooks/oeb/transforms/rescale.py
index 79d4c76487..d73205709b 100644
--- a/src/calibre/ebooks/oeb/transforms/rescale.py
+++ b/src/calibre/ebooks/oeb/transforms/rescale.py
@@ -6,8 +6,6 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import cStringIO
-
from calibre import fit_image
class RescaleImages(object):
@@ -19,13 +17,7 @@ class RescaleImages(object):
self.rescale(qt=is_ok_to_use_qt())
def rescale(self, qt=True):
- from PyQt4.Qt import QImage, Qt
- from calibre.gui2 import pixmap_to_data
- try:
- from PIL import Image as PILImage
- PILImage
- except ImportError:
- import Image as PILImage
+ from calibre.utils.magick.draw import Image
is_image_collection = getattr(self.opts, 'is_image_collection', False)
@@ -35,6 +27,7 @@ class RescaleImages(object):
page_width, page_height = self.opts.dest.width, self.opts.dest.height
page_width -= (self.opts.margin_left + self.opts.margin_right) * self.opts.dest.dpi/72.
page_height -= (self.opts.margin_top + self.opts.margin_bottom) * self.opts.dest.dpi/72.
+
for item in self.oeb.manifest:
if item.media_type.startswith('image'):
ext = item.media_type.split('/')[-1].upper()
@@ -44,42 +37,25 @@ class RescaleImages(object):
raw = item.data
if not raw: continue
- if qt:
- img = QImage(10, 10, QImage.Format_ARGB32_Premultiplied)
- try:
- if not img.loadFromData(raw): continue
- except:
- continue
- width, height = img.width(), img.height()
- else:
- f = cStringIO.StringIO(raw)
- try:
- im = PILImage.open(f)
- except IOError:
- continue
- width, height = im.size
-
+ try:
+ img = Image()
+ img.load(raw)
+ except:
+ continue
+ width, height = img.size
scaled, new_width, new_height = fit_image(width, height,
page_width, page_height)
if scaled:
- data = None
self.log('Rescaling image from %dx%d to %dx%d'%(
width, height, new_width, new_height), item.href)
- if qt:
- img = img.scaled(new_width, new_height,
- Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
- data = pixmap_to_data(img, format=ext)
+ try:
+ img.size = (new_width, new_height)
+ data = img.export(ext.lower())
+ except:
+ self.log.exception('Failed to rescale image')
else:
- try:
- im = im.resize((int(new_width), int(new_height)), PILImage.ANTIALIAS)
- of = cStringIO.StringIO()
- im.convert('RGB').save(of, ext)
- data = of.getvalue()
- except:
- self.log.exception('Failed to rescale image')
- if data is not None:
item.data = data
item.unload_data_from_memory()
diff --git a/src/calibre/gui2/actions/fetch_news.py b/src/calibre/gui2/actions/fetch_news.py
index 98be7cdcb2..5c2a5e9663 100644
--- a/src/calibre/gui2/actions/fetch_news.py
+++ b/src/calibre/gui2/actions/fetch_news.py
@@ -9,7 +9,6 @@ from PyQt4.Qt import Qt
from calibre.gui2 import Dispatcher
from calibre.gui2.tools import fetch_scheduled_recipe
-from calibre.utils.config import dynamic
from calibre.gui2.actions import InterfaceAction
class FetchNewsAction(InterfaceAction):
@@ -60,9 +59,9 @@ class FetchNewsAction(InterfaceAction):
return self.gui.job_exception(job)
id = self.gui.library_view.model().add_news(pt.name, arg)
self.gui.library_view.model().reset()
- sync = dynamic.get('news_to_be_synced', set([]))
+ sync = self.gui.news_to_be_synced
sync.add(id)
- dynamic.set('news_to_be_synced', sync)
+ self.gui.news_to_be_synced = sync
self.scheduler.recipe_downloaded(arg)
self.gui.status_bar.show_message(arg['title'] + _(' fetched.'), 3000)
self.gui.email_news(id)
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index e662c6a5cc..a1f0288050 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -1102,12 +1102,35 @@ class DeviceMixin(object): # {{{
self.status_bar.show_message(_('Sending catalogs to device.'), 5000)
+ @dynamic_property
+ def news_to_be_synced(self):
+ doc = 'Set of ids to be sent to device'
+ def fget(self):
+ ans = []
+ try:
+ ans = self.library_view.model().db.prefs.get('news_to_be_synced',
+ [])
+ except:
+ import traceback
+ traceback.print_exc()
+ return set(ans)
+
+ def fset(self, ids):
+ try:
+ self.library_view.model().db.prefs.set('news_to_be_synced',
+ list(ids))
+ except:
+ import traceback
+ traceback.print_exc()
+
+ return property(fget=fget, fset=fset, doc=doc)
+
def sync_news(self, send_ids=None, do_auto_convert=True):
if self.device_connected:
del_on_upload = config['delete_news_from_library_on_upload']
settings = self.device_manager.device.settings()
- ids = list(dynamic.get('news_to_be_synced', set([]))) if send_ids is None else send_ids
+ ids = list(self.news_to_be_synced) if send_ids is None else send_ids
ids = [id for id in ids if self.library_view.model().db.has_id(id)]
files, _auto_ids = self.library_view.model().get_preferred_formats_from_ids(
ids, settings.format_map,
@@ -1139,7 +1162,7 @@ class DeviceMixin(object): # {{{
for f in files:
f.deleted_after_upload = del_on_upload
if not files:
- dynamic.set('news_to_be_synced', set([]))
+ self.news_to_be_synced = set([])
return
metadata = self.library_view.model().metadata_for(ids)
names = []
@@ -1153,7 +1176,7 @@ class DeviceMixin(object): # {{{
if mi.cover and os.access(mi.cover, os.R_OK):
mi.thumbnail = self.cover_to_thumbnail(open(mi.cover,
'rb').read())
- dynamic.set('news_to_be_synced', set([]))
+ self.news_to_be_synced = set([])
if config['upload_news_to_device'] and files:
remove = ids if del_on_upload else []
space = { self.location_manager.free[0] : None,
@@ -1347,8 +1370,9 @@ class DeviceMixin(object): # {{{
# If it does not, then do it here.
if not self.set_books_in_library(self.booklists(), reset=True):
self.upload_booklists()
- self.book_on_device(None, reset=True)
- self.refresh_ondevice()
+ with self.library_view.preserve_selected_books:
+ self.book_on_device(None, reset=True)
+ self.refresh_ondevice()
view = self.card_a_view if on_card == 'carda' else \
self.card_b_view if on_card == 'cardb' else self.memory_view
diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py
index e085dcc3e2..cca2abb6e0 100644
--- a/src/calibre/gui2/dialogs/book_info.py
+++ b/src/calibre/gui2/dialogs/book_info.py
@@ -90,10 +90,15 @@ class BookInfo(QDialog, Ui_BookInfo):
row = row.row()
if row == self.current_row:
return
+ info = self.view.model().get_book_info(row)
+ if info is None:
+ # Indicates books was deleted from library, or row numbers have
+ # changed
+ return
+
self.previous_button.setEnabled(False if row == 0 else True)
self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True)
self.current_row = row
- info = self.view.model().get_book_info(row)
self.setWindowTitle(info[_('Title')])
self.title.setText(''+info.pop(_('Title')))
comments = info.pop(_('Comments'), '')
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 0286acc782..112df3023c 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -374,6 +374,8 @@ class BooksModel(QAbstractTableModel): # {{{
if isinstance(index, int):
index = self.index(index, 0)
data = self.current_changed(index, None, False)
+ if data is None:
+ return data
row = index.row()
data[_('Title')] = self.db.title(row)
au = self.db.authors(row)
diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index 8f86bf43b8..1c2a541116 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -22,6 +22,26 @@ from calibre.gui2.library import DEFAULT_SORT
from calibre.constants import filesystem_encoding
from calibre import force_unicode
+class PreserveSelection(object): # {{{
+
+ '''
+ Save the set of selected books at enter time. If at exit time there are no
+ selected books, restore the previous selection.
+ '''
+
+ def __init__(self, view):
+ self.view = view
+ self.selected_ids = []
+
+ def __enter__(self):
+ self.selected_ids = self.view.get_selected_ids()
+
+ def __exit__(self, *args):
+ current = self.view.get_selected_ids()
+ if not current:
+ self.view.select_rows(self.selected_ids, using_ids=True)
+# }}}
+
class BooksView(QTableView): # {{{
files_dropped = pyqtSignal(object)
@@ -58,6 +78,7 @@ class BooksView(QTableView): # {{{
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSortingEnabled(True)
self.selectionModel().currentRowChanged.connect(self._model.current_changed)
+ self.preserve_selected_books = PreserveSelection(self)
# {{{ Column Header setup
self.can_add_columns = True
@@ -613,6 +634,16 @@ class BooksView(QTableView): # {{{
sel.select(m.index(row, 0), m.index(row, max_col))
sm.select(sel, sm.ClearAndSelect)
+ def get_selected_ids(self):
+ ans = []
+ m = self.model()
+ for idx in self.selectedIndexes():
+ r = idx.row()
+ i = m.id(r)
+ if i not in ans:
+ ans.append(i)
+ return ans
+
def close(self):
self._model.close()
diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py
index e113ef0611..09019af18b 100644
--- a/src/calibre/gui2/viewer/main.py
+++ b/src/calibre/gui2/viewer/main.py
@@ -716,6 +716,9 @@ View an ebook.
def main(args=sys.argv):
+ # Ensure viewer can continue to function if GUI is closed
+ os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None)
+
parser = option_parser()
opts, args = parser.parse_args(args)
pid = os.fork() if False and (islinux or isfreebsd) else -1
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py
index 60224aefc7..12d64bbbcd 100644
--- a/src/calibre/gui2/widgets.py
+++ b/src/calibre/gui2/widgets.py
@@ -16,7 +16,7 @@ from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, QAction, \
QTimer, QRect
from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs
-
+from calibre.constants import isosx
from calibre.gui2.filename_pattern_ui import Ui_Form
from calibre import fit_image
from calibre.utils.fonts import fontconfig
@@ -303,7 +303,8 @@ class FontFamilyModel(QAbstractListModel):
return NONE
if role == Qt.DisplayRole:
return QVariant(family)
- if role == Qt.FontRole:
+ if not isosx and role == Qt.FontRole:
+ # Causes a Qt crash with some fonts on OS X
return QVariant(QFont(family))
return NONE
diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py
index 300ddbac0b..03383ee7dd 100644
--- a/src/calibre/library/caches.py
+++ b/src/calibre/library/caches.py
@@ -380,7 +380,7 @@ class ResultCache(SearchQueryParser): # {{{
field_count = 3
else:
try:
- qd = parse_date(query)
+ qd = parse_date(query, as_utc=False)
except:
raise ParseException(query, len(query), 'Date conversion error', self)
if '-' in query:
diff --git a/src/calibre/library/server/browse.py b/src/calibre/library/server/browse.py
index 463fcd6fde..142f40efab 100644
--- a/src/calibre/library/server/browse.py
+++ b/src/calibre/library/server/browse.py
@@ -509,7 +509,7 @@ class BrowseServer(object):
hide_sort = 'true' if dt == 'series' else 'false'
if category == 'search':
- which = unhexlify(cid)
+ which = unhexlify(cid).decode('utf-8')
try:
ids = self.search_cache('search:"%s"'%which)
except:
diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py
index d95cd1818c..52a08e6175 100644
--- a/src/calibre/library/server/content.py
+++ b/src/calibre/library/server/content.py
@@ -124,7 +124,7 @@ class ContentServer(object):
if want_mobile:
return self.mobile()
- return self.static('index.html')
+ return self.browse_toplevel()
def old(self, **kwargs):
return self.static('index.html')
diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst
index 220e7ff9e4..687c8480be 100644
--- a/src/calibre/manual/faq.rst
+++ b/src/calibre/manual/faq.rst
@@ -435,3 +435,35 @@ And since I'm sure someone will ask: The reason adding/saving books are in separ
Finally, the reason calibre keep workers alive and idle instead of launching on demand is to workaround the slow startup time of python processes.
+How do I run parts of |app| like news download and the content server on my own linux server?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First, you must install |app| onto your linux server. If your server is using a modern linux distro, you should have no problems installing |app| onto it.
+
+.. note::
+ If you bought into the notion that a real server must run a decade old version of Debian, then you will have to jump through a few hoops. First, compile a newer version of glibc (>= 2.10) on your server from source. Then get the |app| linux binary tarball from the |app| google code page for your server architecture. Extract it into :file:`/opt/calibre`. Put your previously compiled glibc into :file:`/opt/calibre` as :file:`libc.so.6`. You can now run the calibre binaries from :file:`/opt/calibre`.
+
+You can run the |app| server via the command::
+
+ /opt/calibre/calibre-server --with-library /path/to/the/library/you/want/to/share
+
+You can download news and convert it into an ebook with the command::
+
+ /opt/calibre/ebook-convert "Title of news source.recipe" outputfile.epub
+
+If you want to generate MOBI, use outputfile.mobi instead.
+
+You can email downloaded news with the command::
+
+ /opt/calibre/calibre-smtp
+
+I leave figuring out the exact command line as an exercise for the reader.
+
+Finally, you can add downloaded news to the |app| library with::
+
+ /opt/calibre/calibredb add --with-library /path/to/library outfile.epub
+
+Remember to read the command line documentation section of the |app| User Manual to learn more about these, and other commands.
+
+.. note:: Some parts of calibre require a X server. If you're lucky, nothing you do will fall into this category, if not, you will have to look into using xvfb.
+
diff --git a/src/calibre/ptempfile.py b/src/calibre/ptempfile.py
index 16a1ef4ce4..71ae9b0789 100644
--- a/src/calibre/ptempfile.py
+++ b/src/calibre/ptempfile.py
@@ -7,7 +7,7 @@ being closed.
"""
import tempfile, os, atexit, binascii, cPickle
-from calibre import __version__, __appname__
+from calibre.constants import __version__, __appname__
def cleanup(path):
try:
diff --git a/src/calibre/utils/ipc/worker.py b/src/calibre/utils/ipc/worker.py
index e3584380a1..d8ffad7c53 100644
--- a/src/calibre/utils/ipc/worker.py
+++ b/src/calibre/utils/ipc/worker.py
@@ -105,7 +105,7 @@ def main():
notifier.start()
result = func(*args, **kwargs)
- if result is not None:
+ if result is not None and os.path.exists(os.path.dirname(resultf)):
cPickle.dump(result, open(resultf, 'wb'), -1)
notifier.queue.put(None)
diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py
index 5c978a27e0..d3cbd58c7d 100644
--- a/src/calibre/utils/magick/draw.py
+++ b/src/calibre/utils/magick/draw.py
@@ -9,6 +9,7 @@ import os
from calibre.utils.magick import Image, DrawingWand, create_canvas
from calibre.constants import __appname__, __version__
+from calibre.utils.config import tweaks
from calibre import fit_image
def normalize_format_name(fmt):
@@ -113,7 +114,9 @@ def add_borders_to_image(img_data, left=0, top=0, right=0, bottom=0,
def create_text_wand(font_size, font_path=None):
if font_path is None:
- font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
+ font_path = tweaks['generate_cover_title_font']
+ if font_path is None:
+ font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
ans = DrawingWand()
ans.font = font_path
ans.font_size = font_size
@@ -203,8 +206,11 @@ def create_cover_page(top_lines, logo_path, width=590, height=750,
bottom += line.bottom_margin
bottom -= top_lines[-1].bottom_margin
+ foot_font = tweaks['generate_cover_foot_font']
+ if not foot_font:
+ foot_font = P('fonts/liberation/LiberationMono-Regular.ttf')
vanity = create_text_arc(__appname__ + ' ' + __version__, 24,
- font=P('fonts/liberation/LiberationMono-Regular.ttf'))
+ font=foot_font)
lwidth, lheight = vanity.size
left = int(max(0, (width - lwidth)/2.))
top = height - lheight - 10
diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py
index cb6bf30bcf..869799f6bb 100644
--- a/src/calibre/web/feeds/news.py
+++ b/src/calibre/web/feeds/news.py
@@ -842,6 +842,9 @@ class BasicNewsRecipe(Recipe):
except NotImplementedError:
feeds = self.parse_feeds()
+ if not feeds:
+ raise ValueError('No articles found, aborting')
+
#feeds = FeedCollection(feeds)
self.report_progress(0, _('Trying to download cover...'))