diff --git a/recipes/financial_times.recipe b/recipes/financial_times.recipe
index 0e3c91d3e3..e750b6f113 100644
--- a/recipes/financial_times.recipe
+++ b/recipes/financial_times.recipe
@@ -53,6 +53,7 @@ class FinancialTimes(BasicNewsRecipe):
feeds = [
(u'UK' , u'http://www.ft.com/rss/home/uk' )
,(u'US' , u'http://www.ft.com/rss/home/us' )
+ ,(u'Europe' , u'http://www.ft.com/rss/home/europe' )
,(u'Asia' , u'http://www.ft.com/rss/home/asia' )
,(u'Middle East', u'http://www.ft.com/rss/home/middleeast')
]
diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py
index 091aa9a34d..08017b5c98 100644
--- a/resources/default_tweaks.py
+++ b/resources/default_tweaks.py
@@ -266,26 +266,6 @@ max_content_server_tags_shown=5
content_server_will_display = ['*']
content_server_wont_display = []
-#: Set custom metadata fields that the book details panel will or will not display.
-# book_details_will_display is a list of custom fields to be displayed.
-# book_details_wont_display is a list of custom fields not to be displayed.
-# wont_display has priority over will_display.
-# The special value '*' means all custom fields. The value [] means no entries.
-# Defaults:
-# book_details_will_display = ['*']
-# book_details_wont_display = []
-# Examples:
-# To display only the custom fields #mytags and #genre:
-# book_details_will_display = ['#mytags', '#genre']
-# book_details_wont_display = []
-# To display all fields except #mycomments:
-# book_details_will_display = ['*']
-# book_details_wont_display['#mycomments']
-# As above, this tweak affects only display of custom fields. The standard
-# fields are not affected
-book_details_will_display = ['*']
-book_details_wont_display = []
-
#: Set the maximum number of sort 'levels'
# Set the maximum number of sort 'levels' that calibre will use to resort the
# library after certain operations such as searches or device insertion. Each
diff --git a/resources/quick_start.epub b/resources/quick_start.epub
index 589fd1d0dc..2d590ebef2 100644
Binary files a/resources/quick_start.epub and b/resources/quick_start.epub differ
diff --git a/resources/templates/book_details.css b/resources/templates/book_details.css
new file mode 100644
index 0000000000..5059a8f4c3
--- /dev/null
+++ b/resources/templates/book_details.css
@@ -0,0 +1,41 @@
+a {
+ text-decoration: none;
+ color: blue
+}
+.comments {
+ margin-top: 0;
+ padding-top: 0;
+ text-indent: 0
+}
+
+table.fields {
+ margin-bottom: 0;
+ padding-bottom: 0;
+}
+
+table.fields td {
+ vertical-align: top
+}
+
+table.fields td.title {
+ font-weight: bold
+}
+
+.series_name {
+ font-style: italic
+}
+
+/*
+The HTML that this styleshhet applies to looks like this:
+
+
+ Formats: | EPUB, LIT |
+ Series: | Book II of The Sea Beggars |
+ Tags: | Fantasy, Fiction |
+ Path: | Click to open |
+
+
+
+*/
+
diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py
index 7fe246f450..4a48aef441 100644
--- a/src/calibre/devices/android/driver.py
+++ b/src/calibre/devices/android/driver.py
@@ -26,6 +26,7 @@ class ANDROID(USBMS):
0xc92 : [0x100],
0xc97 : [0x226],
0xc99 : [0x0100],
+ 0xca2 : [0x226],
0xca3 : [0x100],
0xca4 : [0x226],
},
diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py
index faac8e98b1..5f1841d518 100644
--- a/src/calibre/ebooks/metadata/book/base.py
+++ b/src/calibre/ebooks/metadata/book/base.py
@@ -19,6 +19,9 @@ from calibre.utils.date import isoformat, format_date
from calibre.utils.icu import sort_key
from calibre.utils.formatter import TemplateFormatter
+def human_readable(size, precision=2):
+ """ Convert a size in bytes into megabytes """
+ return ('%.'+str(precision)+'f'+ 'MB') % ((size/(1024.*1024.)),)
NULL_VALUES = {
'user_metadata': {},
@@ -551,7 +554,8 @@ class Metadata(object):
def format_field_extended(self, key, series_with_index=True):
from calibre.ebooks.metadata import authors_to_string
'''
- returns the tuple (field_name, formatted_value)
+ returns the tuple (field_name, formatted_value, original_value,
+ field_metadata)
'''
# Handle custom series index
@@ -627,6 +631,8 @@ class Metadata(object):
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
elif datatype == 'rating':
res = res/2.0
+ elif key in ('book_size', 'size'):
+ res = human_readable(res)
return (name, unicode(res), orig_res, fmeta)
return (None, None, None, None)
diff --git a/src/calibre/ebooks/metadata/pdb.py b/src/calibre/ebooks/metadata/pdb.py
index ddf2b0c818..d01bb0ecdb 100644
--- a/src/calibre/ebooks/metadata/pdb.py
+++ b/src/calibre/ebooks/metadata/pdb.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
'''
-Read meta information from eReader pdb files.
+Read meta information from pdb files.
'''
__license__ = 'GPL v3'
@@ -13,10 +13,12 @@ import re
from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.pdb.header import PdbHeaderReader
from calibre.ebooks.metadata.ereader import get_metadata as get_eReader
+from calibre.ebooks.metadata.plucker import get_metadata as get_plucker
MREADER = {
'PNPdPPrs' : get_eReader,
'PNRdPPrs' : get_eReader,
+ 'DataPlkr' : get_plucker,
}
from calibre.ebooks.metadata.ereader import set_metadata as set_eReader
diff --git a/src/calibre/ebooks/metadata/plucker.py b/src/calibre/ebooks/metadata/plucker.py
new file mode 100644
index 0000000000..fabaa080d2
--- /dev/null
+++ b/src/calibre/ebooks/metadata/plucker.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import (unicode_literals, division, absolute_import, print_function)
+
+'''
+Read meta information from Plucker pdb files.
+'''
+
+__license__ = 'GPL v3'
+__copyright__ = '2009, John Schember '
+__docformat__ = 'restructuredtext en'
+
+import struct
+from datetime import datetime
+
+from calibre.ebooks.metadata import MetaInformation
+from calibre.ebooks.pdb.header import PdbHeaderReader
+from calibre.ebooks.pdb.plucker.reader import SectionHeader, DATATYPE_METADATA, \
+ MIBNUM_TO_NAME
+
+def get_metadata(stream, extract_cover=True):
+ '''
+ Return metadata as a L{MetaInfo} object
+ '''
+ mi = MetaInformation(_('Unknown'), [_('Unknown')])
+ stream.seek(0)
+
+ pheader = PdbHeaderReader(stream)
+ section_data = None
+ for i in range(1, pheader.num_sections):
+ raw_data = pheader.section_data(i)
+ section_header = SectionHeader(raw_data)
+ if section_header.type == DATATYPE_METADATA:
+ section_data = raw_data[8:]
+ break
+
+ if not section_data:
+ return mi
+
+ default_encoding = 'latin-1'
+ record_count, = struct.unpack('>H', section_data[0:2])
+ adv = 0
+ title = None
+ author = None
+ pubdate = 0
+ for i in xrange(record_count):
+ type, = struct.unpack('>H', section_data[2+adv:4+adv])
+ length, = struct.unpack('>H', section_data[4+adv:6+adv])
+
+ # CharSet
+ if type == 1:
+ val, = struct.unpack('>H', section_data[6+adv:8+adv])
+ default_encoding = MIBNUM_TO_NAME.get(val, 'latin-1')
+ # Author
+ elif type == 4:
+ author = section_data[6+adv+(2*length)]
+ # Title
+ elif type == 5:
+ title = section_data[6+adv+(2*length)]
+ # Publication Date
+ elif type == 6:
+ pubdate, = struct.unpack('>I', section_data[6+adv:6+adv+4])
+
+ adv += 2*length
+
+ if title:
+ mi.title = title.replace('\0', '').decode(default_encoding, 'replace')
+ if author:
+ author = author.replace('\0', '').decode(default_encoding, 'replace')
+ mi.author = author.split(',')
+ mi.pubdate = datetime.fromtimestamp(pubdate)
+
+ return mi
diff --git a/src/calibre/ebooks/metadata/sources/overdrive.py b/src/calibre/ebooks/metadata/sources/overdrive.py
index e975d41ea6..759da45610 100755
--- a/src/calibre/ebooks/metadata/sources/overdrive.py
+++ b/src/calibre/ebooks/metadata/sources/overdrive.py
@@ -206,6 +206,7 @@ class OverDrive(Source):
xref_q = '+'.join(title_tokens)
#log.error('Initial query is %s'%initial_q)
#log.error('Cross reference query is %s'%xref_q)
+
q_xref = q+'SearchResults.svc/GetResults?iDisplayLength=50&sSearch='+xref_q
query = '{"szKeyword":"'+initial_q+'"}'
@@ -229,34 +230,42 @@ class OverDrive(Source):
if int(m.group('displayrecords')) >= 1:
results = True
elif int(m.group('totalrecords')) >= 1:
+ if int(m.group('totalrecords')) >= 100:
+ if xref_q.find('+') != -1:
+ xref_tokens = xref_q.split('+')
+ xref_q = xref_tokens[0]
+ #log.error('xref_q is '+xref_q)
+ else:
+ xref_q = ''
xref_q = ''
q_xref = q+'SearchResults.svc/GetResults?iDisplayLength=50&sSearch='+xref_q
elif int(m.group('totalrecords')) == 0:
return ''
- return self.sort_ovrdrv_results(raw, title, title_tokens, author, author_tokens)
+ return self.sort_ovrdrv_results(raw, log, title, title_tokens, author, author_tokens)
- def sort_ovrdrv_results(self, raw, title=None, title_tokens=None, author=None, author_tokens=None, ovrdrv_id=None):
+ def sort_ovrdrv_results(self, raw, log, title=None, title_tokens=None, author=None, author_tokens=None, ovrdrv_id=None):
close_matches = []
raw = re.sub('.*?\[\[(?P.*?)\]\].*', '[[\g]]', raw)
results = json.loads(raw)
- #print results
+ #log.error('raw results are:'+str(results))
# The search results are either from a keyword search or a multi-format list from a single ID,
# sort through the results for closest match/format
if results:
for reserveid, od_title, subtitle, edition, series, publisher, format, formatid, creators, \
thumbimage, shortdescription, worldcatlink, excerptlink, creatorfile, sorttitle, \
availabletolibrary, availabletoretailer, relevancyrank, unknown1, unknown2, unknown3 in results:
- #print "this record's title is "+od_title+", subtitle is "+subtitle+", author[s] are "+creators+", series is "+series
+ #log.error("this record's title is "+od_title+", subtitle is "+subtitle+", author[s] are "+creators+", series is "+series)
if ovrdrv_id is not None and int(formatid) in [1, 50, 410, 900]:
- #print "overdrive id is not None, searching based on format type priority"
+ #log.error('overdrive id is not None, searching based on format type priority')
return self.format_results(reserveid, od_title, subtitle, series, publisher,
creators, thumbimage, worldcatlink, formatid)
else:
- creators = creators.split(', ')
+ if creators:
+ creators = creators.split(', ')
# if an exact match in a preferred format occurs
- if (author and creators[0] == author[0]) and od_title == title and int(formatid) in [1, 50, 410, 900] and thumbimage:
+ if ((author and creators[0] == author[0]) or (not author and not creators)) and od_title.lower() == title.lower() and int(formatid) in [1, 50, 410, 900] and thumbimage:
return self.format_results(reserveid, od_title, subtitle, series, publisher,
creators, thumbimage, worldcatlink, formatid)
else:
@@ -282,6 +291,10 @@ class OverDrive(Source):
close_matches.insert(0, self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid))
else:
close_matches.append(self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid))
+
+ elif close_title_match and close_author_match and int(formatid) in [1, 50, 410, 900]:
+ close_matches.append(self.format_results(reserveid, od_title, subtitle, series, publisher, creators, thumbimage, worldcatlink, formatid))
+
if close_matches:
return close_matches[0]
else:
@@ -289,7 +302,7 @@ class OverDrive(Source):
else:
return ''
- def overdrive_get_record(self, br, q, ovrdrv_id):
+ def overdrive_get_record(self, br, log, q, ovrdrv_id):
search_url = q+'SearchResults.aspx?ReserveID={'+ovrdrv_id+'}'
results_url = q+'SearchResults.svc/GetResults?sEcho=1&iColumns=18&sColumns=ReserveID%2CTitle%2CSubtitle%2CEdition%2CSeries%2CPublisher%2CFormat%2CFormatID%2CCreators%2CThumbImage%2CShortDescription%2CWorldCatLink%2CExcerptLink%2CCreatorFile%2CSortTitle%2CAvailableToLibrary%2CAvailableToRetailer%2CRelevancyRank&iDisplayStart=0&iDisplayLength=10&sSearch=&bEscapeRegex=true&iSortingCols=1&iSortCol_0=17&sSortDir_0=asc'
@@ -311,7 +324,7 @@ class OverDrive(Source):
raw = str(list(raw))
clean_cj = mechanize.CookieJar()
br.set_cookiejar(clean_cj)
- return self.sort_ovrdrv_results(raw, None, None, None, ovrdrv_id)
+ return self.sort_ovrdrv_results(raw, log, None, None, None, ovrdrv_id)
def find_ovrdrv_data(self, br, log, title, author, isbn, ovrdrv_id=None):
@@ -319,7 +332,7 @@ class OverDrive(Source):
if ovrdrv_id is None:
return self.overdrive_search(br, log, q, title, author)
else:
- return self.overdrive_get_record(br, q, ovrdrv_id)
+ return self.overdrive_get_record(br, log, q, ovrdrv_id)
diff --git a/src/calibre/ebooks/pdb/__init__.py b/src/calibre/ebooks/pdb/__init__.py
index 092c8a21bd..c8089297db 100644
--- a/src/calibre/ebooks/pdb/__init__.py
+++ b/src/calibre/ebooks/pdb/__init__.py
@@ -12,6 +12,7 @@ from calibre.ebooks.pdb.ereader.reader import Reader as ereader_reader
from calibre.ebooks.pdb.palmdoc.reader import Reader as palmdoc_reader
from calibre.ebooks.pdb.ztxt.reader import Reader as ztxt_reader
from calibre.ebooks.pdb.pdf.reader import Reader as pdf_reader
+from calibre.ebooks.pdb.plucker.reader import Reader as plucker_reader
FORMAT_READERS = {
'PNPdPPrs': ereader_reader,
@@ -19,6 +20,7 @@ FORMAT_READERS = {
'zTXTGPlm': ztxt_reader,
'TEXtREAd': palmdoc_reader,
'.pdfADBE': pdf_reader,
+ 'DataPlkr': plucker_reader,
}
from calibre.ebooks.pdb.palmdoc.writer import Writer as palmdoc_writer
@@ -37,6 +39,7 @@ IDENTITY_TO_NAME = {
'zTXTGPlm': 'zTXT',
'TEXtREAd': 'PalmDOC',
'.pdfADBE': 'Adobe Reader',
+ 'DataPlkr': 'Plucker',
'BVokBDIC': 'BDicty',
'DB99DBOS': 'DB (Database program)',
@@ -50,7 +53,6 @@ IDENTITY_TO_NAME = {
'DATALSdb': 'LIST',
'Mdb1Mdb1': 'MobileDB',
'BOOKMOBI': 'MobiPocket',
- 'DataPlkr': 'Plucker',
'DataSprd': 'QuickSheet',
'SM01SMem': 'SuperMemo',
'TEXtTlDc': 'TealDoc',
diff --git a/src/calibre/ebooks/pdb/ereader/reader132.py b/src/calibre/ebooks/pdb/ereader/reader132.py
index df98ce15b1..09e4b624e5 100644
--- a/src/calibre/ebooks/pdb/ereader/reader132.py
+++ b/src/calibre/ebooks/pdb/ereader/reader132.py
@@ -129,14 +129,22 @@ class Reader132(FormatReader):
footnoteids = re.findall('\w+(?=\x00)', self.section_data(self.header_record.footnote_offset).decode('cp1252' if self.encoding is None else self.encoding))
for fid, i in enumerate(range(self.header_record.footnote_offset + 1, self.header_record.footnote_offset + self.header_record.footnote_count)):
self.log.debug('Extracting footnote page %i' % i)
- html += footnote_to_html(footnoteids[fid], self.decompress_text(i))
+ if fid < len(footnoteids):
+ fid = footnoteids[fid]
+ else:
+ fid = ''
+ html += footnote_to_html(fid, self.decompress_text(i))
if self.header_record.sidebar_count > 0:
html += '
%s
' % _('Sidebar')
sidebarids = re.findall('\w+(?=\x00)', self.section_data(self.header_record.sidebar_offset).decode('cp1252' if self.encoding is None else self.encoding))
for sid, i in enumerate(range(self.header_record.sidebar_offset + 1, self.header_record.sidebar_offset + self.header_record.sidebar_count)):
self.log.debug('Extracting sidebar page %i' % i)
- html += sidebar_to_html(sidebarids[sid], self.decompress_text(i))
+ if sid < len(sidebarids):
+ sid = sidebarids[sid]
+ else:
+ sid = ''
+ html += sidebar_to_html(sid, self.decompress_text(i))
html += '
From Publishers Weekly
At the start of Kearney's rousing sequel to The Mark of Ran (2005), Rol Cortishane, the youthful captain of the privateer Revenant, captures a slaver and frees its chained slaves. Back in the harbor of Ganesh Ka in the land of Umer, Rol encounters an untrustworthy acquaintance he hasn't seen in years, Canker, a former king of thieves, who urges Rol to join in the fight to save Rowen, a darkly beautiful queen, whose throne is at risk in mountainous Bionar. That Rowen is Rol's half-sister for whom he has lusted in the past doesn't make Rol's decision to help an easy one. If as in The Mark of Ran the action is more lively at sea than on land, Kearney's solid storytelling and nautical detail worthy of C.S. Forester or Patrick O'Brian will keep readers turning the pages. (Dec.)
Copyright © Reed Business Information, a division of Reed Elsevier Inc. All rights reserved.
From
The sequel to The Mark of Ran (2005) finds heroic young Rol Cortishane grown to be a much-feared sea captain. Deciding to ignore his mysterious past, he spends his energy on ship and crew. He is still an outlaw, however, and the only port he can call home is Ganesh Ka, the endangered city of exiles. When word comes from Rowan, his half-sister, asking him to fight on her behalf, he must weigh the safety of Ganesh Ka against Rowan's treachery in the past. Finally persuaded to aid Rowan, he learns more of betrayal and his heritage in the ensuing battles than he had wanted to know. Kearney's characters are much better developed here than they were in The Mark of Ran, and since the book tells a single story, the plot is tighter. Moreover, because almost all the action transpires in the here and now, the sequel can be read without reference to the predecessor. Since it ends hanging on a particularly bloody cliff, expect to see more of Kearney's excellent maritime fantasy. Frieda Murray
+Copyright © American Library Association. All rights reserved