Merge from trunk

This commit is contained in:
Charles Haley 2011-12-01 09:05:29 +01:00
commit fa4e1b808f
33 changed files with 419 additions and 126 deletions

View File

@ -0,0 +1,18 @@
from calibre.web.feeds.news import BasicNewsRecipe
class DailyWritingTips(BasicNewsRecipe):
title = u'Daily Writing Tips'
language = 'en_GB'
__author__ = 'NotTaken'
oldest_article = 7 #days
max_articles_per_feed = 40
use_embedded_content = True
no_stylesheets = True
auto_cleanup = False
encoding = 'utf-8'
feeds = [
('Latest tips',
'http://feeds2.feedburner.com/DailyWritingTips'),
]

43
recipes/gs24_pl.recipe Normal file
View File

@ -0,0 +1,43 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
import re
import string
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1322322819(BasicNewsRecipe):
title = u'GS24.pl (Głos Szczeciński)'
description = u'Internetowy serwis Głosu Szczecińskiego'
__author__ = u'Michał Szkutnik'
__license__ = u'GPL v3'
language = 'pl'
publisher = 'Media Regionalne sp. z o.o.'
category = 'news, szczecin'
oldest_article = 2
max_articles_per_feed = 100
auto_cleanup = True
cover_url = "http://www.gs24.pl/images/top_logo.png"
feeds = [
# (u'Wszystko', u'http://www.gs24.pl/rss.xml'),
(u'Szczecin', u'http://www.gs24.pl/szczecin.xml'),
(u'Stargard', u'http://www.gs24.pl/stargard.xml'),
(u'Świnoujście', u'http://www.gs24.pl/swinoujscie.xml'),
(u'Goleniów', u'http://www.gs24.pl/goleniow.xml'),
(u'Gryfice', u'http://www.gs24.pl/gryfice.xml'),
(u'Kamień Pomorski', u'http://www.gs24.pl/kamienpomorski.xml'),
(u'Police', u'http://www.gs24.pl/police.xml'),
(u'Region', u'http://www.gs24.pl/region.xml'),
(u'Sport', u'http://www.gs24.pl/sport.xml'),
]
def get_article_url(self, article):
s = re.search("""/0L0S(gs24.*)/story01.htm""", article.link)
s = s.group(1)
replacements = { "0B" : ".", "0C" : "/", "0H" : ",", "0I" : "_", "0D" : "?", "0F" : "="}
for (a, b) in replacements.iteritems():
s = string.replace(s, a, b)
s = string.replace(s, "0A", "0")
return "http://"+s
def print_version(self, url):
return url + "&Template=printpicart"

BIN
recipes/icons/skylife.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -29,22 +29,7 @@ class RollingStones(BasicNewsRecipe):
max_articles_per_feed = 25
use_embedded_content = False
no_stylesheets = True
remove_javascript = True
#####################################################################################
# cleanup section #
#####################################################################################
keep_only_tags = [
dict(name='div', attrs={'class':['c65l']}),
dict(name='div', attrs={'id':['col1']}),
]
remove_tags = [
dict(name='div', attrs={'class': ['storyActions upper','storyActions lowerArticleNav']}),
dict(name='div', attrs={'id': ['comments','related']}),
]
auto_cleanup = True
feeds = [
(u'News', u'http://www.rollingstone.com/siteServices/rss/allNews'),
@ -58,25 +43,7 @@ class RollingStones(BasicNewsRecipe):
def get_article_url(self, article):
return article.get('guid', None)
def append_page(self, soup, appendtag, position):
'''
Some are the articles are multipage so the below function
will get the articles that have <next>
'''
pager = soup.find('li',attrs={'class':'next'})
if pager:
nexturl = pager.a['href']
soup2 = self.index_to_soup(nexturl)
texttag = soup2.find('div', attrs={'id':'storyTextContainer'})
for it in texttag.findAll(style=True):
del it['style']
newpos = len(texttag.contents)
self.append_page(soup2,texttag,newpos)
texttag.extract()
appendtag.insert(position,texttag)
def print_version(self, url):
return url +'?print=true'

32
recipes/skylife.recipe Normal file
View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from calibre.web.feeds.news import BasicNewsRecipe
class THY (BasicNewsRecipe):
title = u'Skylife'
__author__ = u'thomass'
description = ' Türk Hava Yollarının yayınladığı aylık kültür dergisi (Fotoğrafları da içermesini isterseniz keep_only_tag''da belirttiğim kodu da ekleyin) '
oldest_article =32
max_articles_per_feed =100
no_stylesheets = True
#delay = 1
#use_embedded_content = False
encoding = 'utf-8'
publisher = 'thomass'
category = 'genel kültür, gezi,Türkçe'
language = 'tr'
publication_type = 'magazine'
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
keep_only_tags = [dict(name='h3', attrs={'id':['hpbaslik']}),dict(name='p', attrs={'id':['pyayin','hspot','picerik']})] #Fotoğrafları da eklemek için: dict(name='div', attrs={'id':['divResimler']})
masthead_url = 'http://www.turkishairlines.com/static/img/skylife/logo.png'
remove_empty_feeds= True
remove_attributes = ['width','height']
feeds = [( u'SKYLIFE', u'http://feed43.com/7783278414103376.xml')]

20
recipes/techdirt.recipe Normal file
View File

@ -0,0 +1,20 @@
from calibre.web.feeds.news import BasicNewsRecipe
class TechDirt(BasicNewsRecipe):
title = u'Tech Dirt'
language = 'en'
__author__ = 'Krittika Goyal'
oldest_article = 7 #days
max_articles_per_feed = 25
use_embedded_content = False
no_stylesheets = True
auto_cleanup = True
encoding = 'latin1'
feeds = [
('News',
'http://feeds.feedburner.com/techdirt/feed'),
]

98
recipes/vanityfair.recipe Normal file
View File

@ -0,0 +1,98 @@
from datetime import date
import re
from calibre.web.feeds.news import BasicNewsRecipe
class VanityFair(BasicNewsRecipe):
title = u"Vanity Fair"
description = 'Vanity Fair Magazine (U.S.)'
language = 'en'
__author__ = 'Barty'
max_articles_per_feed = 100
no_stylesheets = False
auto_cleanup = False
timefmt = ' [%B %Y]'
oldest_article = 365
masthead_url = 'http://www.vanityfair.com/etc/designs/vanityfair/images/shell/print-logo.png'
INDEX = 'http://www.vanityfair.com'
CATEGORIES = [
# comment out categories you don't want
# (user friendly name, url suffix, max number of articles to load)
('Hollywood','hollywood',10),
('Culture','culture',10),
('Business','business',10),
('Politics','politics',10),
('Society','society',10),
('Style','style',10),
('VF Daily','online/daily',10),
("James Wolcott's Blog",'online/wolcott',10),
("The Oscars",'online/oscars',10),
]
# set this to False if you don't want to put the first article
# that appears in each section to a "Featured" section
FEATURED_CAT = True
remove_tags = [
{'name':['nav']},
{'class':re.compile(r'_(header|rubric|share|subnav|leaderboard)|comments-count|ecom_placement')}
]
remove_tags_after = [{'class':'cn_blogpost'},{'id':'wrapper'}]
def parse_index(self):
self.cover_url = 'http://www.vanityfair.com/magazine/toc/contents-%s/_jcr_content/par/cn_contentwell/par-main/cn_pagination_contai/cn_image.size.cover_vanityfair_300.jpg' % (date.today().strftime('%Y%m'))
feeds = []
seen_urls = set([])
features = []
for category in self.CATEGORIES:
(cat_name, tag, max_articles) = category
self.log('Reading category:', cat_name)
articles = []
page = "%s/%s" % (self.INDEX, tag)
soup = self.index_to_soup(page)
headers = soup.findAll(attrs={'class':'headline '})
add_featured = self.FEATURED_CAT
for header in headers:
self.log(self.tag_to_string(header))
atags = header.findAll('a')
# if there's more than one a tag, it's some kind of list, skip
if not atags or len(atags)>1:
continue
atag = atags[0]
url = atag['href']
if url.startswith('/'):
url = self.INDEX + url
if url in seen_urls:
continue
seen_urls.add(url)
title = self.tag_to_string(atag)
self.log('\tFound article:', title)
self.log('\t', url)
par = header.findParent('article') if tag.startswith('online/') else header.findParent('section')
if par is not None:
desc = par.find(attrs={'class':'body '})
desc = self.tag_to_string(desc) if desc else ''
#self.log('\t', desc)
if add_featured:
features.append({'title':title,'url':url,'description':desc})
add_featured = False
else:
articles.append({'title':title,'url':url,'description':desc})
if len(articles) >= max_articles:
break
if articles:
feeds.append((cat_name, articles))
if features:
feeds.insert(0,('Featured', features))
return feeds
def print_version(self, url):
return url.replace('.html', '.print')

View File

@ -545,7 +545,7 @@ from calibre.customize.profiles import input_profiles, output_profiles
from calibre.devices.apple.driver import ITUNES
from calibre.devices.hanlin.driver import HANLINV3, HANLINV5, BOOX, SPECTRA
from calibre.devices.blackberry.driver import BLACKBERRY
from calibre.devices.blackberry.driver import BLACKBERRY, PLAYBOOK
from calibre.devices.cybook.driver import CYBOOK, ORIZON
from calibre.devices.eb600.driver import (EB600, COOL_ER, SHINEBOOK,
POCKETBOOK360, GER2, ITALICA, ECLICTO, DBOOK, INVESBOOK,
@ -646,7 +646,7 @@ plugins += [
plugins += [
HANLINV3,
HANLINV5,
BLACKBERRY,
BLACKBERRY, PLAYBOOK,
CYBOOK,
ORIZON,
ILIAD,
@ -1531,7 +1531,7 @@ class StoreWoblinkStore(StoreBase):
actual_plugin = 'calibre.gui2.store.stores.woblink_plugin:WoblinkStore'
headquarters = 'PL'
formats = ['EPUB']
formats = ['EPUB', 'PDF', 'WOBLINK']
class XinXiiStore(StoreBase):
name = 'XinXii'

View File

@ -39,15 +39,15 @@ def get_connected_device():
if ok:
dev = d
dev.reset(log_packets=False, detected_device=det)
connected_devices.append(dev)
connected_devices.append((det, dev))
if dev is None:
print >>sys.stderr, 'Unable to find a connected ebook reader.'
return
for d in connected_devices:
for det, d in connected_devices:
try:
d.open(None)
d.open(det, None)
except:
continue
else:
@ -121,7 +121,7 @@ def debug(ioreg_to_tmp=False, buf=None):
out('Trying to open', dev.name, '...', end=' ')
try:
dev.reset(detected_device=det)
dev.open(None)
dev.open(det, None)
out('OK')
except:
import traceback

View File

@ -167,12 +167,12 @@ class ANDROID(USBMS):
'MB525', 'ANDROID2.3', 'SGH-I997', 'GT-I5800_CARD', 'MB612',
'GT-S5830_CARD', 'GT-S5570_CARD', 'MB870', 'MID7015A',
'ALPANDIGITAL', 'ANDROID_MID', 'VTAB1008', 'EMX51_BBG_ANDROI',
'UMS', '.K080', 'P990', 'LTE']
'UMS', '.K080', 'P990', 'LTE', 'MB853', 'GT-S5660_CARD']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
'__UMS_COMPOSITE', 'SGH-I997_CARD', 'MB870', 'ALPANDIGITAL',
'ANDROID_MID', 'P990_SD_CARD', '.K080', 'LTE_CARD']
'ANDROID_MID', 'P990_SD_CARD', '.K080', 'LTE_CARD', 'MB853']
OSX_MAIN_MEM = 'Android Device Main Memory'
@ -199,6 +199,18 @@ class ANDROID(USBMS):
dirs = list(map(aldiko_tweak, dirs))
return dirs
def windows_sort_drives(self, drives):
try:
vid, pid, bcd = self.device_being_opened[:3]
except:
vid, pid, bcd = -1, -1, -1
if (vid, pid, bcd) == (0x0e79, 0x1408, 0x0222):
letter_a = drives.get('carda', None)
if letter_a is not None:
drives['carda'] = drives['main']
drives['main'] = letter_a
return drives
class S60(USBMS):
name = 'S60 driver'

View File

@ -808,7 +808,7 @@ class ITUNES(DriverBase):
self.log.info("ITUNES.get_file(): exporting '%s'" % path)
outfile.write(open(self.cached_books[path]['lib_book'].location().path).read())
def open(self, library_uuid):
def open(self, connected_device, library_uuid):
'''
Perform any device specific initialization. Called after the device is
detected but before any other functions that communicate with the device.
@ -824,7 +824,7 @@ class ITUNES(DriverBase):
'''
if DEBUG:
self.log.info("ITUNES.open()")
self.log.info("ITUNES.open(connected_device: %s)" % repr(connected_device))
# Display a dialog recommending using 'Connect to iTunes' if user hasn't
# previously disabled the dialog
@ -1306,7 +1306,8 @@ class ITUNES(DriverBase):
if DEBUG:
self.log.info(" ITUNES._add_new_copy()")
self._update_epub_metadata(fpath, metadata)
if fpath.rpartition('.')[2].lower() == 'epub':
self._update_epub_metadata(fpath, metadata)
db_added = None
lb_added = None
@ -3223,7 +3224,7 @@ class ITUNES_ASYNC(ITUNES):
only_presence=False):
return self.connected, self
def open(self, library_uuid):
def open(self, connected_device, library_uuid):
'''
Perform any device specific initialization. Called after the device is
detected but before any other functions that communicate with the device.
@ -3238,7 +3239,7 @@ class ITUNES_ASYNC(ITUNES):
we need to talk to iTunes to discover if there's a connected iPod
'''
if DEBUG:
self.log.info("ITUNES_ASYNC.open()")
self.log.info("ITUNES_ASYNC.open(connected_device: %s)" % repr(connected_device))
# Confirm/create thumbs archive
if not os.path.exists(self.cache_dir):

View File

@ -59,9 +59,9 @@ class BAMBOOK(DeviceConfig, DevicePlugin):
def reset(self, key='-1', log_packets=False, report_progress=None,
detected_device=None) :
self.open(None)
self.open(None, None)
def open(self, library_uuid):
def open(self, connected_device, library_uuid):
# Make sure the Bambook library is ready
if not is_bambook_lib_ready():
raise OpenFeedback(_("Unable to connect to Bambook, you need to install Bambook library first."))
@ -309,8 +309,8 @@ class BAMBOOK(DeviceConfig, DevicePlugin):
with TemporaryFile('.snb') as snbfile:
if self.bambook.PackageSNB(snbfile, tdir) and self.bambook.VerifySNB(snbfile):
guid = self.bambook.SendFile(snbfile, self.get_guid(metadata[i].uuid))
elif f[-3:].upper() == 'SNB':
elif f[-3:].upper() == 'SNB':
if self.bambook.VerifySNB(f):
guid = self.bambook.SendFile(f, self.get_guid(metadata[i].uuid))
else:

View File

@ -28,3 +28,26 @@ class BLACKBERRY(USBMS):
EBOOK_DIR_MAIN = 'eBooks'
SUPPORTS_SUB_DIRS = True
class PLAYBOOK(USBMS):
name = 'Blackberry Playbook Interface'
gui_name = 'Playbook'
description = _('Communicate with the Blackberry playbook.')
author = _('Kovid Goyal')
supported_platforms = ['windows', 'linux', 'osx']
# Ordered list of supported formats
FORMATS = ['epub']
VENDOR_ID = [0x0fca]
PRODUCT_ID = [0x8010]
BCD = [0x1]
VENDOR_NAME = 'GENERIC-'
WINDOWS_MAIN_MEM = 'MULTI-CARD'
MAIN_MEMORY_VOLUME_LABEL = 'Blackberry'
EBOOK_DIR_MAIN = 'media/books'
SUPPORTS_SUB_DIRS = True

View File

@ -79,7 +79,7 @@ class FOLDER_DEVICE(USBMS):
only_presence=False):
return self.is_connected, self
def open(self, library_uuid):
def open(self, connected_device, library_uuid):
self.current_library_uuid = library_uuid
if not self._main_prefix:
return False

View File

@ -175,7 +175,7 @@ class ODYSSEY(N516):
FORMATS = ['epub', 'fb2', 'html', 'pdf', 'txt']
EBOOK_DIR_MAIN = 'calibre'
EBOOK_DIR_MAIN = 'Digital Editions'
def get_main_ebook_dir(self, for_upload=False):
if for_upload:

View File

@ -133,8 +133,14 @@ class DevicePlugin(Plugin):
if debug:
self.print_usb_device_info(device_id)
if only_presence or self.can_handle_windows(device_id, debug=debug):
return True
return False
try:
bcd = int(device_id.rpartition(
'rev_')[-1].replace(':', 'a'), 16)
except:
bcd = None
return True, (vendor_id, product_id, bcd, None,
None, None)
return False, None
def test_bcd(self, bcdDevice, bcd):
if bcd is None or len(bcd) == 0:
@ -154,7 +160,7 @@ class DevicePlugin(Plugin):
'''
if iswindows:
return self.is_usb_connected_windows(devices_on_system,
debug=debug, only_presence=only_presence), None
debug=debug, only_presence=only_presence)
vendors_on_system = set([x[0] for x in devices_on_system])
vendors = self.VENDOR_ID if hasattr(self.VENDOR_ID, '__len__') else [self.VENDOR_ID]
@ -224,7 +230,7 @@ class DevicePlugin(Plugin):
return True
def open(self, library_uuid):
def open(self, connected_device, library_uuid):
'''
Perform any device specific initialization. Called after the device is
detected but before any other functions that communicate with the device.
@ -238,6 +244,16 @@ class DevicePlugin(Plugin):
This method can raise an OpenFeedback exception to display a message to
the user.
:param connected_device: The device that we are trying to open. It is
a tuple of (vendor id, product id, bcd, manufacturer name, product
name, device serial number). However, some device have no serial number
and on windows only the first three fields are present, the rest are
None.
:param library_uuid: The UUID of the current calibre library. Can be
None if there is no library (for example when used from the command
line.
'''
raise NotImplementedError()

View File

@ -205,15 +205,15 @@ def main():
if ok:
dev = d
dev.reset(log_packets=options.log_packets, detected_device=det)
connected_devices.append(dev)
connected_devices.append((det, dev))
if dev is None:
print >>sys.stderr, 'Unable to find a connected ebook reader.'
return 1
for d in connected_devices:
for det, d in connected_devices:
try:
d.open(None)
d.open(det, None)
except:
continue
else:

View File

@ -240,7 +240,7 @@ class PRS500(DeviceConfig, DevicePlugin):
def set_progress_reporter(self, report_progress):
self.report_progress = report_progress
def open(self, library_uuid) :
def open(self, connected_device, library_uuid) :
"""
Claim an interface on the device for communication.
Requires write privileges to the device file.

View File

@ -847,38 +847,42 @@ class Device(DeviceConfig, DevicePlugin):
self._card_b_prefix = None
# ------------------------------------------------------
def open(self, library_uuid):
def open(self, connected_device, library_uuid):
time.sleep(5)
self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
if islinux:
try:
self.open_linux()
except DeviceError:
time.sleep(7)
self.open_linux()
if isfreebsd:
self._main_dev = self._card_a_dev = self._card_b_dev = None
try:
self.open_freebsd()
except DeviceError:
subprocess.Popen(["camcontrol", "rescan", "all"])
time.sleep(2)
self.open_freebsd()
if iswindows:
try:
self.open_windows()
except DeviceError:
time.sleep(7)
self.open_windows()
if isosx:
try:
self.open_osx()
except DeviceError:
time.sleep(7)
self.open_osx()
self.device_being_opened = connected_device
try:
if islinux:
try:
self.open_linux()
except DeviceError:
time.sleep(7)
self.open_linux()
if isfreebsd:
self._main_dev = self._card_a_dev = self._card_b_dev = None
try:
self.open_freebsd()
except DeviceError:
subprocess.Popen(["camcontrol", "rescan", "all"])
time.sleep(2)
self.open_freebsd()
if iswindows:
try:
self.open_windows()
except DeviceError:
time.sleep(7)
self.open_windows()
if isosx:
try:
self.open_osx()
except DeviceError:
time.sleep(7)
self.open_osx()
self.current_library_uuid = library_uuid
self.post_open_callback()
self.current_library_uuid = library_uuid
self.post_open_callback()
finally:
self.device_being_opened = None
def post_open_callback(self):
pass

View File

@ -132,9 +132,11 @@ class EPUBOutput(OutputFormatPlugin):
def upshift_markup(self): # {{{
'Upgrade markup to comply with XHTML 1.1 where possible'
from calibre.ebooks.oeb.base import XPath
from calibre.ebooks.oeb.base import XPath, XML
for x in self.oeb.spine:
root = x.data
if (not root.get(XML('lang'))) and (root.get('lang')):
root.set(XML('lang'), root.get('lang'))
body = XPath('//h:body')(root)
if body:
body = body[0]

View File

@ -741,6 +741,14 @@ if __name__ == '__main__': # tests {{{
isbn_test, title_test, authors_test)
com_tests = [ # {{{
( # # in title
{'title':'Expert C# 2008 Business Objects',
'authors':['Lhotka']},
[title_test('Expert C# 2008 Business Objects', exact=True),
authors_test(['Rockford Lhotka'])
]
),
( # Description has links
{'identifiers':{'isbn': '9780671578275'}},
[title_test('A Civil Campaign: A Comedy of Biology and Manners',

View File

@ -121,7 +121,18 @@ def cap_author_token(token):
# Normalize tokens of the form J.K. to J. K.
parts = token.split('.')
return '. '.join(map(capitalize, parts)).strip()
scots_name = None
for x in ('mc', 'mac'):
if (token.lower().startswith(x) and len(token) > len(x) and
(
token[len(x)] == upper(token[len(x)]) or
lt == token
)):
scots_name = len(x)
break
ans = capitalize(token)
if scots_name is not None:
ans = ans[:scots_name] + upper(ans[scots_name]) + ans[scots_name+1:]
for x in ('-', "'"):
idx = ans.find(x)
if idx > -1 and len(ans) > idx+2:
@ -333,7 +344,7 @@ class Source(Plugin):
# Remove single quotes not followed by 's'
(r"'(?!s)", ''),
# Replace other special chars with a space
(r'''[:,;+!@#$%^&*(){}.`~"\s\[\]/]''', ' ')
(r'''[:,;+!@$%^&*(){}.`~"\s\[\]/]''', ' '),
]]
for pat, repl in title_patterns:

View File

@ -539,7 +539,8 @@ class Style(object):
'Return value in pts'
if base is None:
base = self.width
font = font or self.fontSize
if not font and font != 0:
font = self.fontSize
return unit_convert(value, base, font, self._profile.dpi)
def pt_to_px(self, value):

View File

@ -283,7 +283,10 @@ class CSSFlattener(object):
psize = fsize
elif 'font-size' in cssdict or tag == 'body':
fsize = self.fmap[font_size]
cssdict['font-size'] = "%0.5fem" % (fsize / psize)
try:
cssdict['font-size'] = "%0.5fem" % (fsize / psize)
except ZeroDivisionError:
cssdict['font-size'] = '%.1fpt'%fsize
psize = fsize
try:

View File

@ -162,7 +162,7 @@ class DeviceManager(Thread): # {{{
try:
dev.reset(detected_device=detected_device,
report_progress=self.report_progress)
dev.open(self.current_library_uuid)
dev.open(detected_device, self.current_library_uuid)
except OpenFeedback as e:
if dev not in self.ejected_devices:
self.open_feedback_msg(dev.get_gui_name(), e)

View File

@ -164,7 +164,14 @@ class MyBlockingBusy(QDialog): # {{{
self.db.set_title(id, titlecase(title), notify=False)
if do_title_sort:
title = self.db.title(id, index_is_id=True)
self.db.set_title_sort(id, title_sort(title), notify=False)
if languages:
lang = languages[0]
else:
lang = self.db.languages(id, index_is_id=True)
if lang:
lang = lang.partition(',')[0]
self.db.set_title_sort(id, title_sort(title, lang=lang),
notify=False)
if au:
self.db.set_authors(id, string_to_authors(au), notify=False)
if cover_action == 'remove':
@ -736,6 +743,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
else:
flags = re.I
flags |= re.UNICODE
try:
if self.search_mode.currentIndex() == 0:
self.s_r_obj = re.compile(re.escape(unicode(self.search_for.text())), flags)

View File

@ -21,13 +21,14 @@ from calibre.gui2.store.stores.mobileread.cache_update_thread import CacheUpdate
from calibre.gui2.store.stores.mobileread.store_dialog import MobileReadStoreDialog
class MobileReadStore(BasicStoreConfig, StorePlugin):
def genesis(self):
def __init__(self, *args, **kwargs):
StorePlugin.__init__(self, *args, **kwargs)
self.lock = Lock()
def open(self, parent=None, detail_item=None, external=False):
url = 'http://www.mobileread.com/'
if external or self.config.get('open_external', False):
open_url(QUrl(detail_item if detail_item else url))
else:
@ -44,7 +45,7 @@ class MobileReadStore(BasicStoreConfig, StorePlugin):
def search(self, query, max_results=10, timeout=60):
books = self.get_book_list()
if not books:
return
@ -56,24 +57,25 @@ class MobileReadStore(BasicStoreConfig, StorePlugin):
book.drm = SearchResult.DRM_UNLOCKED
yield book
def update_cache(self, parent=None, timeout=10, force=False, suppress_progress=False):
def update_cache(self, parent=None, timeout=10, force=False,
suppress_progress=False):
if self.lock.acquire(False):
try:
update_thread = CacheUpdateThread(self.config, self.seralize_books, timeout)
if not suppress_progress:
progress = CacheProgressDialog(parent)
progress.set_message(_('Updating MobileRead book cache...'))
update_thread.total_changed.connect(progress.set_total)
update_thread.update_progress.connect(progress.set_progress)
update_thread.update_details.connect(progress.set_details)
progress.rejected.connect(update_thread.abort)
progress.open()
update_thread.start()
while update_thread.is_alive() and not progress.canceled:
QCoreApplication.processEvents()
if progress.isVisible():
progress.accept()
return not progress.canceled
@ -84,7 +86,7 @@ class MobileReadStore(BasicStoreConfig, StorePlugin):
def get_book_list(self):
return self.deseralize_books(self.config.get('book_list', []))
def seralize_books(self, books):
sbooks = []
for b in books:
@ -95,7 +97,7 @@ class MobileReadStore(BasicStoreConfig, StorePlugin):
data['formats'] = b.formats
sbooks.append(data)
return sbooks
def deseralize_books(self, sbooks):
books = []
for s in sbooks:

View File

@ -61,6 +61,12 @@ class WoblinkStore(BasicStoreConfig, StorePlugin):
price = ''.join(data.xpath('.//div[@class="prices"]/p[1]/span/text()'))
price = re.sub('PLN', '', price)
price = re.sub('\.', ',', price)
formats = ', '.join(data.xpath('.//p[3]/img/@src'))
formats = formats[8:-4].upper()
if formats == 'EPUB':
formats = 'WOBLINK'
if 'E Ink' in data.xpath('.//div[@class="prices"]/img/@title'):
formats += ', EPUB'
counter -= 1
@ -71,6 +77,6 @@ class WoblinkStore(BasicStoreConfig, StorePlugin):
s.price = price
s.detail_item = id.strip()
s.drm = SearchResult.DRM_LOCKED
s.formats = 'EPUB'
s.formats = formats
yield s

View File

@ -195,7 +195,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
for ac in self.iactions.values():
ac.do_genesis()
self.donate_action = QAction(QIcon(I('donate.png')), _('&Donate to support calibre'), self)
self.donate_action = QAction(QIcon(I('donate.png')),
_('&Donate to support calibre'), self)
for st in self.istores.values():
st.do_genesis()
MainWindowMixin.__init__(self, db)

View File

@ -195,14 +195,25 @@ class ContentServer(object):
return data
def get_format(self, id, format):
format = format.upper()
fm = self.db.format_metadata(id, format, allow_cache=False)
if not fm:
raise cherrypy.HTTPError(404, 'book: %d does not have format: %s'%(id, format))
mi = newmi = self.db.get_metadata(id, index_is_id=True)
cherrypy.response.headers['Last-Modified'] = \
self.last_modified(max(fm['mtime'], mi.last_modified))
fmt = self.db.format(id, format, index_is_id=True, as_file=True,
mode='rb')
if fmt is None:
raise cherrypy.HTTPError(404, 'book: %d does not have format: %s'%(id, format))
mi = newmi = self.db.get_metadata(id, index_is_id=True)
mt = guess_type('dummy.'+format.lower())[0]
if mt is None:
mt = 'application/octet-stream'
cherrypy.response.headers['Content-Type'] = mt
if format == 'EPUB':
# Get the original metadata
@ -221,19 +232,19 @@ class ContentServer(object):
set_metadata(fmt, newmi, format.lower())
fmt.seek(0)
mt = guess_type('dummy.'+format.lower())[0]
if mt is None:
mt = 'application/octet-stream'
au = authors_to_string(mi.authors if mi.authors else [_('Unknown')])
title = mi.title if mi.title else _('Unknown')
fmt.seek(0, 2)
cherrypy.response.headers['Content-Length'] = fmt.tell()
fmt.seek(0)
au = authors_to_string(newmi.authors if newmi.authors else
[_('Unknown')])
title = newmi.title if newmi.title else _('Unknown')
fname = u'%s - %s_%s.%s'%(title[:30], au[:30], id, format.lower())
fname = ascii_filename(fname).replace('"', '_')
cherrypy.response.headers['Content-Type'] = mt
cherrypy.response.headers['Content-Disposition'] = \
b'attachment; filename="%s"'%fname
cherrypy.response.body = fmt
cherrypy.response.timeout = 3600
cherrypy.response.headers['Last-Modified'] = \
self.last_modified(self.db.format_last_modified(id, format))
return fmt
# }}}

View File

@ -265,7 +265,7 @@ How do I use |app| with my Android phone/tablet?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two ways that you can connect your Android device to calibre. Using a USB cable-- or wirelessly, over the air.
The USB cable method only works if your Android device can act as a USB disk, which some Android tablets cannot.
**The USB cable method only works if your Android device can act as a USB disk, that means in windows it must have a drive letter, like K:**.
Using a USB cable
^^^^^^^^^^^^^^^^^^^^
@ -374,6 +374,8 @@ any |app| developers will ever feel motivated enough to support it. There is how
that allows you to create collections on your Kindle from the |app| metadata. It is available
`from here <http://www.mobileread.com/forums/showthread.php?t=118635>`_.
.. note:: Amazon have removed the ability to manipulate collections completely in their newer models, like the Kindle Touch and Kindle Fire, making even the above plugin useless. If you really want the ability to manage collections on your Kindle via a USB connection, we encourage you to complain to Amazon about it, or get a reader where this is supported, like the SONY Readers.
Library Management
------------------

View File

@ -1398,6 +1398,8 @@ class BasicNewsRecipe(Recipe):
oldest_article=self.oldest_article,
max_articles_per_feed=self.max_articles_per_feed,
get_article_url=self.get_article_url))
if (self.delay > 0):
time.sleep(self.delay)
except Exception as err:
feed = Feed()
msg = 'Failed feed: %s'%(title if title else url)

View File

@ -13,7 +13,7 @@ from datetime import timedelta
from lxml import etree
from lxml.builder import ElementMaker
from calibre import browser
from calibre import browser, force_unicode
from calibre.utils.date import parse_date, now as nowf, utcnow, tzlocal, \
isoformat, fromordinal
@ -66,8 +66,9 @@ def serialize_collection(mapping_of_recipe_classes):
x.title.decode('ascii')
'''
for urn in sorted(mapping_of_recipe_classes.keys(),
key=lambda key: getattr(mapping_of_recipe_classes[key], 'title',
'zzz')):
key=lambda key: force_unicode(
getattr(mapping_of_recipe_classes[key], 'title', 'zzz'),
'utf-8')):
recipe = serialize_recipe(urn, mapping_of_recipe_classes[urn])
collection.append(recipe)
collection.set('count', str(len(collection)))