This commit is contained in:
GRiker 2012-06-15 04:15:02 -06:00
commit f7b93c1554
130 changed files with 42010 additions and 39095 deletions

View File

@ -20,6 +20,57 @@
# - title: # - title:
- version: 0.8.56
date: 2012-06-15
new features:
- title: "Make the new calibre style default on Windows and OS X."
type: major
description: "This change gives a more 'modern' feel to the calibre user interface with focus highlighting, gradients, rounded corners, etc. In case you prefer the old look, you can restore under Preferences->Look & Feel->User interface style"
- title: "Get Books: Add the new SONY Reader store"
- title: "Read metadata from .docx (Microsoft Word) files"
- title: "Allow customizing the behavior of the searching for similar books by right clicking the book. You can now tell calibre to search different columns than the traditional author/series/publisher/tags/etc. in Preferences->Searching"
- title: "Add option to restore alternating row colors to the Tag Browser under Preferences->Look & Feel->Tag Browser"
- title: "Update to Qt 4.8.2 on windows compiled with link time code generation for a small performance boost"
bug fixes:
- title: "Get Books: Update plugins to handle website changes at ebooks.com, project gutenberg, and virtualo"
- title: "AZW3 Output: Fix TOC at start option not working"
- title: "AZW3 Output: Close self closing script/style/title/head tags explicitly as they cause problems in webkit based renderers like the Kindle Fire and calibre's viewers."
- title: "Fix the current_library_name() template function not updating after a library switch"
- title: "AZW3 Output: Handle the case of a link pointing to the last line of text in the document."
tickets: [1011330]
- title: "Fix regression in 0.8.55 that broke highlighting of items matching a search in the Tag Browser"
tickets: [1011030]
- title: "News download: Handle query only relative URLs"
improved recipes:
- Christian Science Monitor
- Neue Zurcher Zeitung
- Birmignham Post
- Metro UK
- New Musical Express
- The Independent
- The Daily Mirror
- Vreme
- Smithsonian Magazine
new recipes:
- title: NZZ Webpaper
author: Bernd Leinfelder
- version: 0.8.55 - version: 0.8.55
date: 2012-06-08 date: 2012-06-08

View File

@ -172,7 +172,7 @@ You can see the ``prefs`` object being used in main.py:
:pyobject: DemoDialog.config :pyobject: DemoDialog.config
The different types of plugins The plugin API
-------------------------------- --------------------------------
As you may have noticed above, a plugin in |app| is a class. There are different classes for the different types of plugins in |app|. As you may have noticed above, a plugin in |app| is a class. There are different classes for the different types of plugins in |app|.

View File

@ -5,11 +5,11 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
timefmt = '' timefmt = ''
__author__ = 'Dave Asbury' __author__ = 'Dave Asbury'
cover_url = 'http://1.bp.blogspot.com/_GwWyq5eGw9M/S9BHPHxW55I/AAAAAAAAB6Q/iGCWl0egGzg/s320/Birmingham+post+Lite+front.JPG' cover_url = 'http://1.bp.blogspot.com/_GwWyq5eGw9M/S9BHPHxW55I/AAAAAAAAB6Q/iGCWl0egGzg/s320/Birmingham+post+Lite+front.JPG'
oldest_article = 1 oldest_article = 2
max_articles_per_feed = 20 max_articles_per_feed = 12
remove_empty_feeds = True remove_empty_feeds = True
remove_javascript = True remove_javascript = True
auto_cleanup = True #auto_cleanup = True
language = 'en_GB' language = 'en_GB'
@ -17,9 +17,12 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
keep_only_tags = [ keep_only_tags = [
#dict(name='h1',attrs={'id' : 'article-headline'}), dict(name='h1',attrs={'id' : 'article-headline'}),
#dict(attrs={'class':['article-meta-author','article-meta-date','article main','art-o art-align-center otm-1 ']}), dict(attrs={'class':['article-meta-author','article-meta-date','article main','art-o art-align-center otm-1 ']}),
#dict(name='p') dict(name='div',attrs={'class' : 'article-image full'}),
dict(attrs={'clas' : 'art-o art-align-center otm-1 '}),
dict(name='div',attrs={'class' : 'article main'}),
#dict(name='p')
#dict(attrs={'id' : 'three-col'}) #dict(attrs={'id' : 'three-col'})
] ]
remove_tags = [ remove_tags = [
@ -28,7 +31,7 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
] ]
feeds = [ feeds = [
#(u'News',u'http://www.birminghampost.net/news/rss.xml'), #(u'News',u'http://www.birminghampost.net/news/rss.xml'),
(u'Local News', u'http://www.birminghampost.net/news/west-midlands-news/rss.xml'), (u'West Mids. News', u'http://www.birminghampost.net/news/west-midlands-news/rss.xml'),
(u'UK News', u'http://www.birminghampost.net/news/uk-news/rss.xml'), (u'UK News', u'http://www.birminghampost.net/news/uk-news/rss.xml'),
(u'Sports',u'http://www.birminghampost.net/midlands-birmingham-sport/rss.xml'), (u'Sports',u'http://www.birminghampost.net/midlands-birmingham-sport/rss.xml'),
(u'Bloggs & Comments',u'http://www.birminghampost.net/comment/rss.xml') (u'Bloggs & Comments',u'http://www.birminghampost.net/comment/rss.xml')

View File

@ -4,6 +4,7 @@ __copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
www.csmonitor.com www.csmonitor.com
''' '''
import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class CSMonitor(BasicNewsRecipe): class CSMonitor(BasicNewsRecipe):
@ -40,13 +41,15 @@ class CSMonitor(BasicNewsRecipe):
remove_tags = [ remove_tags = [
dict(name=['meta','link','iframe','object','embed']) dict(name=['meta','link','iframe','object','embed'])
,dict(attrs={'class':['podStoryRel','bottom-rel','hide']}) ,dict(attrs={'class':re.compile('(^|| )podStoryRel($|| )', re.DOTALL)})
,dict(attrs={'class':['bottom-rel','hide']})
,dict(attrs={'id':['pgallerycarousel_enlarge','pgallerycarousel_related']}) ,dict(attrs={'id':['pgallerycarousel_enlarge','pgallerycarousel_related']})
] ]
keep_only_tags = [ keep_only_tags = [
dict(name='h1', attrs={'class':'head'}) dict(name='h1', attrs={'class':'head'})
,dict(name='h2', attrs={'class':'subhead'}) ,dict(name='h2', attrs={'class':'subhead'})
,dict(attrs={'class':['sByline','podStoryGal','ui-body-header','sBody']}) ,dict(attrs={'class':['sByline','thePhoto','ui-body-header']})
,dict(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
] ]
remove_attributes=['xmlns:fb'] remove_attributes=['xmlns:fb']
@ -74,10 +77,10 @@ class CSMonitor(BasicNewsRecipe):
if nexttag: if nexttag:
nurl = 'http://www.csmonitor.com' + nexttag['href'] nurl = 'http://www.csmonitor.com' + nexttag['href']
soup2 = self.index_to_soup(nurl) soup2 = self.index_to_soup(nurl)
texttag = soup2.find(attrs={'class':'sBody'}) texttag = soup2.find(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
if texttag: if texttag:
appendtag = soup.find(attrs={'class':'sBody'}) appendtag = soup.find(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
for citem in texttag.findAll(attrs={'class':['podStoryRel','bottom-rel','hide']}): for citem in texttag.findAll(attrs={'class':[re.compile('(^|| )podStoryRel($|| )', re.DOTALL),'bottom-rel','hide']}):
citem.extract() citem.extract()
self.append_page(soup2) self.append_page(soup2)
texttag.extract() texttag.extract()

View File

@ -7,7 +7,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
description = 'News as provided by The Daily Mirror -UK' description = 'News as provided by The Daily Mirror -UK'
__author__ = 'Dave Asbury' __author__ = 'Dave Asbury'
# last updated 28/4/12 # last updated 8/6/12
language = 'en_GB' language = 'en_GB'
#cover_url = 'http://yookeo.com/screens/m/i/mirror.co.uk.jpg' #cover_url = 'http://yookeo.com/screens/m/i/mirror.co.uk.jpg'
@ -28,7 +28,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
dict(name='div',attrs={'class' : 'lead-text'}), dict(name='div',attrs={'class' : 'lead-text'}),
dict(name='div',attrs={'class' : 'styleGroup clearfix'}), dict(name='div',attrs={'class' : 'styleGroup clearfix'}),
dict(name='div',attrs={'class' : 'widget relatedContents pictures widget-editable viziwyg-section-245 inpage-widget-158123'}), dict(name='div',attrs={'class' : 'widget relatedContents pictures widget-editable viziwyg-section-245 inpage-widget-158123'}),
dict(name='figure',attrs={'class' : 'clearfix'}), # dict(name='figure',attrs={'class' : 'clearfix'}),
dict(name='div',attrs={'class' :'body '}), dict(name='div',attrs={'class' :'body '}),
#dict(attrs={'class' : ['article-attr','byline append-1','published']}), #dict(attrs={'class' : ['article-attr','byline append-1','published']}),
@ -37,6 +37,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
remove_tags = [ remove_tags = [
dict(attrs={'class' : ['article sa-teaser type-opinion','image-gallery','gallery-caption']}),
dict(attrs={'class' : 'comment'}), dict(attrs={'class' : 'comment'}),
dict(name='title'), dict(name='title'),
dict(name='ul',attrs={'class' : 'clearfix breadcrumbs '}), dict(name='ul',attrs={'class' : 'clearfix breadcrumbs '}),
@ -89,6 +90,3 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
#cover_url = cov2 #cover_url = cov2
#cover_url = 'http://www.thesun.co.uk/img/global/new-masthead-logo.png' #cover_url = 'http://www.thesun.co.uk/img/global/new-masthead-logo.png'
return cover_url return cover_url

View File

@ -15,6 +15,10 @@ class TheIndependentNew(BasicNewsRecipe):
#Flag to enable/disable image fetching (not business) #Flag to enable/disable image fetching (not business)
_FETCH_IMAGES = True _FETCH_IMAGES = True
#Set max gallery images here (respects _FETCH_IMAGES)
# -1 for infinite
_MAX_GALLERY_IMAGES = -1
#used for converting rating to stars #used for converting rating to stars
_STAR_URL = 'http://www.independent.co.uk/skins/ind/images/rating_star.png' _STAR_URL = 'http://www.independent.co.uk/skins/ind/images/rating_star.png'
@ -41,6 +45,7 @@ class TheIndependentNew(BasicNewsRecipe):
dict(attrs={'id' : ['RelatedArtTag','renderBiography']}), dict(attrs={'id' : ['RelatedArtTag','renderBiography']}),
dict(attrs={'class' : ['autoplay','openBiogPopup']}), dict(attrs={'class' : ['autoplay','openBiogPopup']}),
dict(name='img',attrs={'alt' : ['Get Adobe Flash player']}), dict(name='img',attrs={'alt' : ['Get Adobe Flash player']}),
dict(name='img',attrs={'alt' : ['view gallery']}),
dict(attrs={'style' : re.compile('.*')}), dict(attrs={'style' : re.compile('.*')}),
] ]
@ -119,15 +124,15 @@ class TheIndependentNew(BasicNewsRecipe):
if len(para.contents) and isinstance(para.contents[0],NavigableString) \ if len(para.contents) and isinstance(para.contents[0],NavigableString) \
and para.contents[0] == 'ADVERTORIAL FEATURE': and para.contents[0] == 'ADVERTORIAL FEATURE':
return None return None
# remove Suggested Topics # remove Suggested Topics
items_to_extract = [] items_to_extract = []
for item in soup.findAll('div',attrs={'class' : re.compile('.*RelatedArtTag.*')}): for item in soup.findAll('div',attrs={'class' : re.compile('.*RelatedArtTag.*')}):
items_to_extract.append(item) items_to_extract.append(item)
for item in items_to_extract: for item in items_to_extract:
item.extract() item.extract()
items_to_extract = [] items_to_extract = []
slideshow_elements = [] slideshow_elements = []
@ -171,25 +176,43 @@ class TheIndependentNew(BasicNewsRecipe):
for item in element.findAll('a',attrs={'href' : re.compile('.*')}): for item in element.findAll('a',attrs={'href' : re.compile('.*')}):
if item.img is not None: if item.img is not None:
#use full size image #use full size image
images = []
img = item.findNext('img') img = item.findNext('img')
img['src'] = item['href'] if not '?action=gallery' in item['href']:
img['src'] = item['href']
#insert caption if available
if img.get('title') and (len(img['title']) > 1):
tag = Tag(soup,'h3') tag = Tag(soup,'h3')
text = NavigableString(img['title']) text = ''
try:
text = img['data-title']
except:
pass
if img.get('title') and (len(img['title']) > 1):
text = NavigableString(img['title'])
tag.insert(0,text) tag.insert(0,text)
images.append((img, tag))
#picture before text else:
gallery_images, remove_link = self._get_gallery_images(item['href'])
images = images + gallery_images
if remove_link:
gal_link = soup.find('a',attrs={'id' : 'view-gallery'})
if gal_link:
gal_link.extract()
img.extract() img.extract()
item.insert(0,img) for (img, title) in images:
item.insert(1,tag) #insert caption if available
if title:
#picture before text
img.extract()
item.insert(0,img)
item.insert(1,title)
# remove link # remove link
item.name = "div" item.name = "div"
item["class"]='image' item["class"]='image'
del item["href"] del item["href"]
#remove empty subtitles #remove empty subtitles
@ -317,13 +340,51 @@ class TheIndependentNew(BasicNewsRecipe):
for item in items_to_extract: for item in items_to_extract:
item.extract() item.extract()
# nickredding's fix for non-justified text # nickredding's fix for non-justified text
for ptag in soup.findAll('p',attrs={'align':'left'}): for ptag in soup.findAll('p',attrs={'align':'left'}):
del(ptag['align']) del(ptag['align'])
return soup return soup
def _get_gallery_images(self,url):
gallery_soup = self.index_to_soup(url)
images = []
remove_link = True
total = 1
try:
counter = gallery_soup.find('div',attrs={'id' : ['counter']})
total = counter.contents[0].split('/')
total = int(total[1].rstrip())
except:
total = 1
if self._MAX_GALLERY_IMAGES >= 0 and total > self._MAX_GALLERY_IMAGES:
total = self._MAX_GALLERY_IMAGES
remove_link = False
for i in range(1, total +1):
image, title = self._get_image_from_gallery(gallery_soup)
if image:
images.append((image,title))
next = url + '&ino=' + str(i + 1)
gallery_soup = self.index_to_soup(next)
images.reverse()
return images, remove_link
def _get_image_from_gallery(self,soup):
try:
container = soup.find('div',attrs={'id' : ['main-image']})
image = container.find('img')
if image:
title = soup.find('div',attrs={'id' : ['image-title']})
return image, title
except:
print 'error fetching gallery image'
return None
def _recurisvely_linearise_tag_tree( def _recurisvely_linearise_tag_tree(
self, self,
item, item,

View File

@ -4,6 +4,7 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
description = 'News as provide by The Metro -UK' description = 'News as provide by The Metro -UK'
#timefmt = '' #timefmt = ''
__author__ = 'Dave Asbury' __author__ = 'Dave Asbury'
#last update 9/6/12
cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/276636_117118184990145_2132092232_n.jpg' cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/276636_117118184990145_2132092232_n.jpg'
#no_stylesheets = True #no_stylesheets = True
oldest_article = 1 oldest_article = 1
@ -11,7 +12,7 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
remove_empty_feeds = True remove_empty_feeds = True
remove_javascript = True remove_javascript = True
auto_cleanup = True auto_cleanup = True
encoding = 'UTF-8'
language = 'en_GB' language = 'en_GB'
masthead_url = 'http://e-edition.metro.co.uk/images/metro_logo.gif' masthead_url = 'http://e-edition.metro.co.uk/images/metro_logo.gif'

View File

@ -1,23 +1,47 @@
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from calibre import browser
class AdvancedUserRecipe1306061239(BasicNewsRecipe): class AdvancedUserRecipe1306061239(BasicNewsRecipe):
title = u'New Musical Express Magazine' title = u'New Musical Express Magazine'
__author__ = "scissors" description = 'Author D.Asbury. UK Rock & Pop Mag. '
language = 'en' __author__ = 'Dave Asbury'
# last updated 9/6/12
remove_empty_feeds = True remove_empty_feeds = True
remove_javascript = True remove_javascript = True
no_stylesheets = True no_stylesheets = True
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 20
cover_url = 'http://tawanda3000.files.wordpress.com/2011/02/nme-logo.jpg' #auto_cleanup = True
language = 'en_GB'
def get_cover_url(self):
soup = self.index_to_soup('http://www.magazinesdirect.com/categories/mens/tv-and-music/')
cov = soup.find(attrs={'title' : 'NME magazine subscriptions'})
cov2 = 'http://www.magazinesdirect.com'+cov['src']
print '***cov = ',cov2,' ***'
cover_url = str(cov2)
# print '**** Cov url =*', cover_url,'***'
#print '**** Cov url =*','http://www.magazinesdirect.com/article_images/articledir_3138/1569221/1_largelisting.jpg','***'
br = browser()
br.set_handle_redirect(False)
try:
br.open_novisit(cov2)
cover_url = str(cov2)
except:
cover_url = 'http://tawanda3000.files.wordpress.com/2011/02/nme-logo.jpg'
return cover_url
masthead_url = 'http://tawanda3000.files.wordpress.com/2011/02/nme-logo.jpg'
remove_tags = [ remove_tags = [
dict( attrs={'class':'clear_icons'}), dict( attrs={'class':'clear_icons'}),
dict( attrs={'class':'share_links'}), dict( attrs={'class':'share_links'}),
dict( attrs={'id':'right_panel'}), dict( attrs={'id':'right_panel'}),
dict( attrs={'class':'today box'}) dict( attrs={'class':'today box'}),
]
]
keep_only_tags = [ keep_only_tags = [
@ -28,7 +52,9 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
dict(attrs={'class' : 'bPosts'}), dict(attrs={'class' : 'bPosts'}),
dict(attrs={'class' : 'text'}), dict(attrs={'class' : 'text'}),
dict(attrs={'id' : 'article_gallery'}), dict(attrs={'id' : 'article_gallery'}),
#dict(attrs={'class' : 'image'}),
dict(attrs={'class' : 'article_text'}) dict(attrs={'class' : 'article_text'})
] ]
@ -36,7 +62,8 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
feeds = [ feeds = [
(u'NME News', u'http://feeds2.feedburner.com/nmecom/rss/newsxml'), (u'NME News', u'http://feeds2.feedburner.com/nmecom/rss/newsxml'),
(u'Reviews', u'http://feeds2.feedburner.com/nme/SdML'), #(u'Reviews', u'http://feeds2.feedburner.com/nme/SdML'),
(u'Blogs', u'http://www.nme.com/blog/index.php?blog=140&tempskin=_rss2'), (u'Reviews',u'http://feed43.com/4138608576351646.xml'),
(u'Bloggs',u'http://feed43.com/3326754333186048.xml'),
] ]

View File

@ -1,6 +1,6 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>, 2012 Bernd Leinfelder <skoll1975@gmail.com>'
''' '''
www.nzz.ch www.nzz.ch
@ -10,7 +10,7 @@ from calibre.web.feeds.recipes import BasicNewsRecipe
class Nzz(BasicNewsRecipe): class Nzz(BasicNewsRecipe):
title = 'NZZ Online' title = 'NZZ Online'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic, Bernd Leinfelder'
description = 'Laufend aktualisierte Nachrichten, Analysen und Hintergruende zu Politik, Wirtschaft, Kultur und Sport' description = 'Laufend aktualisierte Nachrichten, Analysen und Hintergruende zu Politik, Wirtschaft, Kultur und Sport'
publisher = 'NZZ AG' publisher = 'NZZ AG'
category = 'news, politics, nachrichten, Switzerland' category = 'news, politics, nachrichten, Switzerland'
@ -41,31 +41,31 @@ class Nzz(BasicNewsRecipe):
,'publisher' : publisher ,'publisher' : publisher
} }
keep_only_tags = [dict(name='div', attrs={'class':'zone'})]
remove_tags_before = dict(name='p', attrs={'class':'dachzeile'})
remove_tags_after=dict(name='p', attrs={'class':'fussnote'})
remove_attributes=['width','height','lang'] remove_attributes=['width','height','lang']
remove_tags_before = dict(id='main')
remove_tags_after = dict(id='articleBodyText')
remove_tags = [ remove_tags = [
dict(name=['object','link','base','meta','iframe']) dict(name=['object','link','base','meta','iframe'])
,dict(attrs={'id':'content_rectangle_1'}) ,dict(id='social-media-floater')
,dict(attrs={'class':['weiterfuehrendeLinks','fussnote','video']}) ,dict(name='div',attrs={'class':['box']})
] ]
feeds = [ feeds = [
(u'International' , u'http://www.nzz.ch/nachrichten/international?rss=true') (u'International' , u'http://www.nzz.ch/aktuell/international.rss')
,(u'Schweiz' , u'http://www.nzz.ch/nachrichten/schweiz?rss=true') ,(u'Schweiz' , u'http://www.nzz.ch/aktuell/schweiz.rss')
,(u'Wirtschaft' , u'http://www.nzz.ch/nachrichten/wirtschaft/aktuell?rss=true') ,(u'Wirtschaft' , u'http://www.nzz.ch/aktuell/wirtschaft/uebersicht.rss')
,(u'Finanzmaerkte' , u'http://www.nzz.ch/finanzen/nachrichten?rss=true') ,(u'Finanzmaerkte' , u'http://www.nzz.ch/finanzen/uebersicht/finanznachrichten.rss')
,(u'Zuerich' , u'http://www.nzz.ch/nachrichten/zuerich?rss=true') ,(u'Zuerich' , u'http://www.nzz.ch/aktuell/zuerich/uebersicht.rss')
,(u'Sport' , u'http://www.nzz.ch/nachrichten/sport?rss=true') ,(u'Sport' , u'http://www.nzz.ch/aktuell/sport/uebersicht.rss')
,(u'Panorama' , u'http://www.nzz.ch/nachrichten/panorama?rss=true') ,(u'Panorama' , u'http://www.nzz.ch/aktuell/panorama.rss')
,(u'Kultur' , u'http://www.nzz.ch/nachrichten/kultur/aktuell?rss=true') ,(u'Kultur' , u'http://www.nzz.ch/aktuell/feuilleton/uebersicht.rss')
,(u'Wissenschaft' , u'http://www.nzz.ch/nachrichten/wissenschaft?rss=true') ,(u'Wissenschaft' , u'http://www.nzz.ch/wissen/uebersicht.rss')
,(u'Medien' , u'http://www.nzz.ch/nachrichten/medien?rss=true') ,(u'Reisen' , u'http://www.nzz.ch/lebensart/reisen-freizeit.rss')
,(u'Reisen' , u'http://www.nzz.ch/magazin/reisen?rss=true') ,(u'Auto Mobil' , u'http://www.nzz.ch/lebensart/auto-mobil.rss')
,(u'Digital' , u'http://www.nzz.ch/lebensart/digital.rss')
,(u'Stil' , u'http://www.nzz.ch/lebensart/stil.rss')
,(u'Wein-Keller' , u'http://www.nzz.ch/lebensart/wein-keller.rss')
] ]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return self.adeify_images(soup)

View File

@ -0,0 +1,90 @@
from calibre import strftime
__license__ = 'GPL v3'
__copyright__ = '2012, Bernd Leinfelder <skoll1975 at gmail.com> '
'''
webpaper.nzz.ch
'''
from calibre.web.feeds.recipes import BasicNewsRecipe
class Nzz(BasicNewsRecipe):
title = 'NZZ Webpaper'
__author__ = 'Bernd Leinfelder'
description = 'Neue Zuercher Zeitung Webpaper - Erfordert NZZ Digital Abonnement'
timefmt = ' [%a, %d %b, %Y]'
publisher = 'NZZ AG'
needs_subscription = True
category = 'news, politics, nachrichten, Switzerland'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
encoding = 'utf-8'
use_embedded_content = False
language = 'de'
extra_css = 'h1 {font: sans-serif large;}\n.byline {font:monospace;}'
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
}
remove_tags = [dict(name='footer')]
remove_tags_before = dict(name='article')
remove_tags_after= dict(name='footer')
def parse_index(self):
baseref = 'https://webpaper.nzz.ch'
soup = self.index_to_soup(baseref)
articles = {}
key = None
ans = []
issuelist = soup.find(id="issueSelectorList")
feeds = issuelist.findAll("a")
for f in feeds:
section = f.string
sectionref = baseref + f['href']
# print "section is "+section +" and ref is "+sectionref
ans.append(section)
articlesoup = self.index_to_soup(sectionref)
articlesoup = articlesoup.findAll('article','article')
for a in articlesoup:
artlink = a.find('a')
arthref = baseref + artlink['href']
arthead = a.find('h2')
artcaption = arthead.string
pubdate = strftime('%a, %d %b')
if not artcaption is None:
# print " found article named "+artcaption+" at "+arthref
if not articles.has_key(section):
articles[section] = []
articles[section].append(
dict(title=artcaption, url=arthref, date=pubdate, description='', content=''))
ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
return ans
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open('https://webpaper.nzz.ch/login')
br.select_form(nr=0)
br['_username'] = self.username
br['_password'] = self.password
br.submit()
return br

View File

@ -1,26 +1,42 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulSoup
class SmithsonianMagazine(BasicNewsRecipe): class SmithsonianMagazine(BasicNewsRecipe):
title = u'Smithsonian Magazine' title = u'Smithsonian Magazine'
language = 'en' language = 'en'
__author__ = 'Krittika Goyal' __author__ = 'Krittika Goyal and TerminalVeracity'
oldest_article = 31#days oldest_article = 31#days
max_articles_per_feed = 50 max_articles_per_feed = 50
use_embedded_content = False use_embedded_content = False
#encoding = 'latin1'
recursions = 1 recursions = 1
cover_url = 'http://sphotos.xx.fbcdn.net/hphotos-snc7/431147_10150602715983253_764313347_n.jpg'
match_regexps = ['&page=[2-9]$'] match_regexps = ['&page=[2-9]$']
preprocess_regexps = [
(re.compile(r'for more of Smithsonian\'s coverage on history, science and nature.', re.DOTALL), lambda m: '')
]
extra_css = """
h1{font-size: large; margin: .2em 0}
h2{font-size: medium; margin: .2em 0}
h3{font-size: medium; margin: .2em 0}
#byLine{margin: .2em 0}
.articleImageCaptionwide{font-style: italic}
.wp-caption-text{font-style: italic}
img{display: block}
"""
remove_stylesheets = True remove_stylesheets = True
#remove_tags_before = dict(name='h1', attrs={'class':'heading'}) remove_tags_after = dict(name='div', attrs={'class':['post','articlePaginationWrapper']})
remove_tags_after = dict(name='p', attrs={'id':'articlePaginationWrapper'})
remove_tags = [ remove_tags = [
dict(name='iframe'), dict(name='iframe'),
dict(name='div', attrs={'class':'article_sidebar_border'}), dict(name='div', attrs={'class':['article_sidebar_border','viewMorePhotos','addtoany_share_save_container','meta','social','OUTBRAIN','related-articles-inpage']}),
dict(name='div', attrs={'id':['article_sidebar_border', 'most-popular_large', 'most-popular-body_large']}), dict(name='div', attrs={'id':['article_sidebar_border', 'most-popular_large', 'most-popular-body_large','comment_section','article-related']}),
##dict(name='ul', attrs={'class':'article-tools'}),
dict(name='ul', attrs={'class':'cat-breadcrumb col three last'}), dict(name='ul', attrs={'class':'cat-breadcrumb col three last'}),
dict(name='h4', attrs={'id':'related-topics'}),
dict(name='table'),
dict(name='a', attrs={'href':['/subArticleBottomWeb','/subArticleTopWeb','/subArticleTopMag','/subArticleBottomMag']}),
dict(name='a', attrs={'name':'comments_shaded'}),
] ]
@ -39,15 +55,7 @@ class SmithsonianMagazine(BasicNewsRecipe):
def preprocess_html(self, soup): def preprocess_html(self, soup):
story = soup.find(name='div', attrs={'id':'article-body'}) story = soup.find(name='div', attrs={'id':'article-body'})
##td = heading.findParent(name='td')
##td.extract()
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>') soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
body = soup.find(name='body') body = soup.find(name='body')
body.insert(0, story) body.insert(0, story)
return soup return soup
#def postprocess_html(self, soup, first):
#for p in soup.findAll(id='articlePaginationWrapper'): p.extract()
#if not first:
#for div in soup.findAll(id='article-head'): div.extract()
#return soup

View File

@ -1,5 +1,5 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2008-2012, Darko Miletic <darko.miletic at gmail.com>'
''' '''
vreme.com vreme.com
''' '''
@ -24,7 +24,17 @@ class Vreme(BasicNewsRecipe):
language = 'sr' language = 'sr'
publication_type = 'magazine' publication_type = 'magazine'
masthead_url = 'http://www.vreme.com/g/vreme-logo.gif' masthead_url = 'http://www.vreme.com/g/vreme-logo.gif'
extra_css = ' @font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif} @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} .heading1{font-family: sans1, sans-serif; font-size: x-large; font-weight: bold} .heading2{font-family: sans1, sans-serif; font-size: large; font-weight: bold} .toc-heading{font-family: sans1, sans-serif; font-size: small} .column-heading2{font-family: sans1, sans-serif; font-size: large} .column-heading1{font-family: sans1, sans-serif; font-size: x-large} .column-normal{font-family: sans1, sans-serif; font-size: medium} .large{font-family: sans1, sans-serif; font-size: large} ' extra_css = """
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
body{font-family: serif1, serif}
.article_description{font-family: serif1, serif}
.heading1{font-family: sans1, sans-serif; font-size: x-large; font-weight: bold} .heading2{font-family: sans1, sans-serif; font-size: large; font-weight: bold} .toc-heading{font-family: sans1, sans-serif; font-size: small}
.column-heading2{font-family: sans1, sans-serif; font-size: large}
.column-heading1{font-family: sans1, sans-serif; font-size: x-large}
.column-normal{font-family: sans1, sans-serif; font-size: medium}
.large{font-family: sans1, sans-serif; font-size: large}
"""
conversion_options = { conversion_options = {
'comment' : description 'comment' : description
@ -58,9 +68,12 @@ class Vreme(BasicNewsRecipe):
for item in soup.findAll(['h3','h4']): for item in soup.findAll(['h3','h4']):
description = u'' description = u''
title_prefix = u'' title_prefix = u''
feed_link = item.find('a') feed_link = item.find('a', href=True)
if feed_link and feed_link.has_key('href') and feed_link['href'].startswith('/cms/view.php'): if feed_link and (feed_link['href'].startswith('cms/view.php') or feed_link['href'].startswith('/cms/view.php')):
url = self.INDEX + feed_link['href'] if feed_link['href'].startswith('cms/view.php'):
url = self.INDEX + '/' + feed_link['href']
else:
url = self.INDEX + feed_link['href']
title = title_prefix + self.tag_to_string(feed_link) title = title_prefix + self.tag_to_string(feed_link)
date = strftime(self.timefmt) date = strftime(self.timefmt)
articles.append({ articles.append({

View File

@ -442,12 +442,6 @@ metadata_edit_custom_column_order = []
# calibre. # calibre.
public_smtp_relay_delay = 301 public_smtp_relay_delay = 301
#: Remove the bright yellow lines at the edges of the book list
# Control whether the bright yellow lines at the edges of book list are drawn
# when a section of the user interface is hidden. Changes will take effect
# after a restart of calibre.
draw_hidden_section_indicators = True
#: The maximum width and height for covers saved in the calibre library #: The maximum width and height for covers saved in the calibre library
# All covers in the calibre library will be resized, preserving aspect ratio, # All covers in the calibre library will be resized, preserving aspect ratio,
# to fit within this size. This is to prevent slowdowns caused by extremely # to fit within this size. This is to prevent slowdowns caused by extremely

View File

@ -15,7 +15,7 @@ function show_reference_panel(ref) {
panel = $("#calibre_reference_panel"); panel = $("#calibre_reference_panel");
} }
$("> p", panel).text(ref); $("> p", panel).text(ref);
panel.css({top:(window.pageYOffset+20)+"px"}); panel.css({top:(window.pageYOffset+20)+"px", left:(window.pageXOffset+20)+"px"});
panel.fadeIn(500); panel.fadeIn(500);
} }

View File

@ -482,6 +482,10 @@ class Py2App(object):
shutil.rmtree(tdir) shutil.rmtree(tdir)
shutil.rmtree(os.path.join(self.site_packages, 'calibre', 'plugins')) shutil.rmtree(os.path.join(self.site_packages, 'calibre', 'plugins'))
self.remove_bytecode(join(self.resources_dir, 'Python', 'site-packages')) self.remove_bytecode(join(self.resources_dir, 'Python', 'site-packages'))
# Create dummy IPython README_STARTUP
with open(join(self.site_packages,
'IPython/config/profile/README_STARTUP'), 'wb') as f:
f.write('\n')
@flush @flush
def add_modules_from_dir(self, src): def add_modules_from_dir(self, src):
@ -551,6 +555,15 @@ class Py2App(object):
if dest2.endswith('.so'): if dest2.endswith('.so'):
self.fix_dependencies_in_lib(dest2) self.fix_dependencies_in_lib(dest2)
self.remove_bytecode(join(self.resources_dir, 'Python', 'lib')) self.remove_bytecode(join(self.resources_dir, 'Python', 'lib'))
confdir = join(self.resources_dir, 'Python',
'lib/python%s/config'%self.version_info)
os.makedirs(confdir)
shutil.copy2(join(src, 'config/Makefile'), confdir)
incdir = join(self.resources_dir, 'Python',
'include/python'+self.version_info)
os.makedirs(incdir)
shutil.copy2(join(src.replace('/lib/', '/include/'), 'pyconfig.h'),
incdir)
@flush @flush
def remove_bytecode(self, dest): def remove_bytecode(self, dest):

View File

@ -14,7 +14,7 @@ from setup.build_environment import msvc, MT, RC
from setup.installer.windows.wix import WixMixIn from setup.installer.windows.wix import WixMixIn
OPENSSL_DIR = r'Q:\openssl' OPENSSL_DIR = r'Q:\openssl'
QT_DIR = 'Q:\\Qt\\4.8.1' QT_DIR = 'Q:\\Qt\\4.8.2'
QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns'] QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns']
QTCURVE = r'C:\plugins\styles' QTCURVE = r'C:\plugins\styles'
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll' LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'

View File

@ -97,7 +97,7 @@ Now, run configure and make::
-no-plugin-manifests is needed so that loading the plugins does not fail looking for the CRT assembly -no-plugin-manifests is needed so that loading the plugins does not fail looking for the CRT assembly
configure -opensource -release -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake configure -opensource -release -ltcg -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake
Add the path to the bin folder inside the Qt dir to your system PATH. Add the path to the bin folder inside the Qt dir to your system PATH.

View File

@ -18,14 +18,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-" "Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n" "devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n" "POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2012-05-29 09:12+0000\n" "PO-Revision-Date: 2012-06-10 11:16+0000\n"
"Last-Translator: Moritz Höwer <moritzhoewermail@gmx.de>\n" "Last-Translator: SimonFS <simonschuette@arcor.de>\n"
"Language-Team: German <debian-l10n-german@lists.debian.org>\n" "Language-Team: German <debian-l10n-german@lists.debian.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-05-30 05:20+0000\n" "X-Launchpad-Export-Date: 2012-06-11 04:46+0000\n"
"X-Generator: Launchpad (build 15316)\n" "X-Generator: Launchpad (build 15376)\n"
"Language: de\n" "Language: de\n"
#. name for aaa #. name for aaa
@ -139,7 +139,7 @@ msgstr ""
#. name for abe #. name for abe
msgid "Abnaki; Western" msgid "Abnaki; Western"
msgstr "" msgstr "Abnaki; Westlich"
#. name for abf #. name for abf
msgid "Abai Sungai" msgid "Abai Sungai"

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = u'calibre' __appname__ = u'calibre'
numeric_version = (0, 8, 55) numeric_version = (0, 8, 56)
__version__ = u'.'.join(map(unicode, numeric_version)) __version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>" __author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -1177,6 +1177,16 @@ class StoreAmazonKindleStore(StoreBase):
formats = ['KINDLE'] formats = ['KINDLE']
affiliate = True affiliate = True
class StoreSonyStore(StoreBase):
name = 'SONY Reader Store'
description = u'SONY Reader books.'
author = 'Kovid Goyal'
actual_plugin = 'calibre.gui2.store.stores.sony_plugin:SonyStore'
headquarters = 'US'
formats = ['SONY']
affiliate = False
class StoreAmazonDEKindleStore(StoreBase): class StoreAmazonDEKindleStore(StoreBase):
name = 'Amazon DE Kindle' name = 'Amazon DE Kindle'
author = 'Charles Haley' author = 'Charles Haley'
@ -1623,7 +1633,7 @@ plugins += [
StoreAmazonITKindleStore, StoreAmazonITKindleStore,
StoreAmazonUKKindleStore, StoreAmazonUKKindleStore,
StoreBaenWebScriptionStore, StoreBaenWebScriptionStore,
StoreBNStore, StoreBNStore, StoreSonyStore,
StoreBeamEBooksDEStore, StoreBeamEBooksDEStore,
StoreBeWriteStore, StoreBeWriteStore,
StoreBiblioStore, StoreBiblioStore,

View File

@ -72,6 +72,7 @@ class ANDROID(USBMS):
# Sony Ericsson # Sony Ericsson
0xfce : { 0xfce : {
0xd12e : [0x0100], 0xd12e : [0x0100],
0xe156 : [0x226],
0xe15d : [0x226], 0xe15d : [0x226],
0xe14f : [0x0226], 0xe14f : [0x0226],
0x614f : [0x0226, 0x100], 0x614f : [0x0226, 0x100],

View File

@ -53,6 +53,7 @@ class KF8Writer(object):
self.log('\tGenerating KF8 markup...') self.log('\tGenerating KF8 markup...')
self.dup_data() self.dup_data()
self.cleanup_markup()
self.replace_resource_links() self.replace_resource_links()
self.extract_css_into_flows() self.extract_css_into_flows()
self.extract_svg_into_flows() self.extract_svg_into_flows()
@ -89,6 +90,15 @@ class KF8Writer(object):
def data(self, item): def data(self, item):
return self._data_cache.get(item.href, item.data) return self._data_cache.get(item.href, item.data)
def cleanup_markup(self):
for item in self.oeb.spine:
root = self.data(item)
# Remove empty script tags as they are pointless
for tag in XPath('//h:script')(root):
if not tag.text and not tag.get('src', False):
tag.getparent().remove(tag)
def replace_resource_links(self): def replace_resource_links(self):
''' Replace links to resources (raster images/fonts) with pointers to ''' Replace links to resources (raster images/fonts) with pointers to
the MOBI record containing the resource. The pointers are of the form: the MOBI record containing the resource. The pointers are of the form:

View File

@ -33,7 +33,8 @@ aid_able_tags = {'a', 'abbr', 'address', 'article', 'aside', 'audio', 'b',
'video'} 'video'}
_self_closing_pat = re.compile(bytes( _self_closing_pat = re.compile(bytes(
r'<(?P<tag>%s)(?=[\s/])(?P<arg>[^>]*)/>'%('|'.join(aid_able_tags))), r'<(?P<tag>%s)(?=[\s/])(?P<arg>[^>]*)/>'%('|'.join(aid_able_tags|{'script',
'style', 'title', 'head'}))),
re.IGNORECASE) re.IGNORECASE)
def close_self_closing_tags(raw): def close_self_closing_tags(raw):
@ -118,6 +119,7 @@ class Skeleton(object):
def render(self, root): def render(self, root):
raw = tostring(root, xml_declaration=True) raw = tostring(root, xml_declaration=True)
raw = raw.replace(b'<html', bytes('<html xmlns="%s"'%XHTML_NS), 1) raw = raw.replace(b'<html', bytes('<html xmlns="%s"'%XHTML_NS), 1)
raw = close_self_closing_tags(raw)
return raw return raw
def calculate_metrics(self, root): def calculate_metrics(self, root):
@ -372,6 +374,11 @@ class Chunker(object):
# the chunk immediately after # the chunk immediately after
pos_fid = (chunk.sequence_number, 0, offset) pos_fid = (chunk.sequence_number, 0, offset)
break break
if chunk is self.chunk_table[-1]:
# This can happen for aids very close to the end of the the
# end of the text (https://bugs.launchpad.net/bugs/1011330)
pos_fid = (chunk.sequence_number, offset-chunk.insert_pos,
offset)
if pos_fid is None: if pos_fid is None:
raise ValueError('Could not find chunk for aid: %r'% raise ValueError('Could not find chunk for aid: %r'%
match.group(1)) match.group(1))

View File

@ -73,7 +73,7 @@ class TOCAdder(object):
id, href = oeb.manifest.generate('contents', 'contents.xhtml') id, href = oeb.manifest.generate('contents', 'contents.xhtml')
item = self.generated_item = oeb.manifest.add(id, href, XHTML_MIME, item = self.generated_item = oeb.manifest.add(id, href, XHTML_MIME,
data=root) data=root)
if opts.mobi_toc_at_start == 'end': if self.at_start:
oeb.spine.insert(0, item, linear=True) oeb.spine.insert(0, item, linear=True)
else: else:
oeb.spine.add(item, linear=False) oeb.spine.add(item, linear=False)

View File

@ -106,7 +106,8 @@ gprefs.defaults['auto_add_path'] = None
gprefs.defaults['auto_add_check_for_duplicates'] = False gprefs.defaults['auto_add_check_for_duplicates'] = False
gprefs.defaults['blocked_auto_formats'] = [] gprefs.defaults['blocked_auto_formats'] = []
gprefs.defaults['auto_add_auto_convert'] = True gprefs.defaults['auto_add_auto_convert'] = True
gprefs.defaults['widget_style'] = 'system' gprefs.defaults['ui_style'] = 'calibre' if iswindows or isosx else 'system'
gprefs.defaults['tag_browser_old_look'] = False
# }}} # }}}
NONE = QVariant() #: Null value to return from the data function of item models NONE = QVariant() #: Null value to return from the data function of item models
@ -782,7 +783,7 @@ class Application(QApplication):
font.setStretch(s) font.setStretch(s)
QApplication.setFont(font) QApplication.setFont(font)
if force_calibre_style or gprefs['widget_style'] != 'system': if force_calibre_style or gprefs['ui_style'] != 'system':
self.load_calibre_style() self.load_calibre_style()
else: else:
st = self.style() st = self.style()

View File

@ -26,37 +26,56 @@ class SimilarBooksAction(InterfaceAction):
(_('Books in this series'), 'books_in_series.png', 'series', (_('Books in this series'), 'books_in_series.png', 'series',
_('Alt+Shift+S')), _('Alt+Shift+S')),
(_('Books by this publisher'), 'publisher.png', 'publisher', _('Alt+P')), (_('Books by this publisher'), 'publisher.png', 'publisher', _('Alt+P')),
(_('Books with the same tags'), 'tags.png', 'tag', _('Alt+T')),]: (_('Books with the same tags'), 'tags.png', 'tags', _('Alt+T')),]:
ac = self.create_action(spec=(text, icon, None, shortcut), ac = self.create_action(spec=(text, icon, None, shortcut),
attr=target) attr=target)
m.addAction(ac) m.addAction(ac)
ac.triggered.connect(partial(self.show_similar_books, target)) ac.triggered.connect(partial(self.show_similar_books, target))
self.qaction.setMenu(m) self.qaction.setMenu(m)
def show_similar_books(self, type, *args): def show_similar_books(self, typ, *args):
search, join = [], ' '
idx = self.gui.library_view.currentIndex() idx = self.gui.library_view.currentIndex()
if not idx.isValid(): if not idx.isValid():
return return
db = idx.model().db
row = idx.row() row = idx.row()
if type == 'series':
series = idx.model().db.series(row) # Get the parameters for this search
if series: col = db.prefs['similar_' + typ + '_search_key']
search = ['series:"'+series+'"'] match = db.prefs['similar_' + typ + '_match_kind']
elif type == 'publisher': if match == 'match_all':
publisher = idx.model().db.publisher(row) join = ' and '
if publisher: else:
search = ['publisher:"'+publisher+'"'] join = ' or '
elif type == 'tag':
tags = idx.model().db.tags(row) # Get all the data for the current record
if tags: mi = db.get_metadata(row)
search = ['tag:"='+t+'"' for t in tags.split(',')]
elif type in ('author', 'authors'): # Get the definitive field name to use for this search. If the field
authors = idx.model().db.authors(row) # is a grouped search term, the function returns the list of fields that
if authors: # are to be searched, otherwise it returns the field name.
search = ['author:"='+a.strip().replace('|', ',')+'"' \ loc = db.field_metadata.search_term_to_field_key(icu_lower(col))
for a in authors.split(',')] if isinstance(loc, list):
join = ' or ' # Grouped search terms are a list of fields. Get all the values,
# pruning duplicates
val = set()
for f in loc:
v = mi.get(f, None)
if not v:
continue
if isinstance(v, list):
val.update(v)
else:
val.add(v)
else:
# Get the value of the requested field. Can be a list or a simple val
val = mi.get(col, None)
if not val:
return
if not isinstance(val, (list, set)):
val = [val]
search = [col + ':"='+t+'"' for t in val]
if search: if search:
self.gui.search.set_search_string(join.join(search), self.gui.search.set_search_string(join.join(search),
store_in_history=True) store_in_history=True)

View File

@ -25,11 +25,11 @@ class StoreAction(InterfaceAction):
self.qaction.triggered.connect(self.do_search) self.qaction.triggered.connect(self.do_search)
self.store_menu = self.qaction.menu() self.store_menu = self.qaction.menu()
cm = partial(self.create_menu_action, self.store_menu) cm = partial(self.create_menu_action, self.store_menu)
for x, t in [('author', _('author')), ('title', _('title')), for x, t in [('author', _('this author')), ('title', _('this title')),
('book', _('book'))]: ('book', _('this book'))]:
func = getattr(self, 'search_%s'%('author_title' if x == 'book' func = getattr(self, 'search_%s'%('author_title' if x == 'book'
else x)) else x))
ac = cm(x, _('Search for this %s')%t, triggered=func) ac = cm(x, _('Search for %s')%t, triggered=func)
setattr(self, 'action_search_by_'+x, ac) setattr(self, 'action_search_by_'+x, ac)
self.store_menu.addSeparator() self.store_menu.addSeparator()
self.store_list_menu = self.store_menu.addMenu(_('Stores')) self.store_list_menu = self.store_menu.addMenu(_('Stores'))

View File

@ -238,10 +238,11 @@ class LayoutMixin(object): # {{{
# }}} # }}}
self.status_bar = StatusBar(self) self.status_bar = StatusBar(self)
stylename = unicode(self.style().objectName())
for x in button_order: for x in button_order:
button = getattr(self, x+'_splitter').button button = getattr(self, x+'_splitter').button
button.setIconSize(QSize(24, 24)) button.setIconSize(QSize(24, 24))
if isosx: if isosx and stylename != u'Calibre':
button.setStyleSheet(''' button.setStyleSheet('''
QToolButton { background: none; border:none; padding: 0px; } QToolButton { background: none; border:none; padding: 0px; }
QToolButton:checked { background: rgba(0, 0, 0, 25%); } QToolButton:checked { background: rgba(0, 0, 0, 25%); }

View File

@ -183,10 +183,17 @@ Author matching is exact.</string>
</item> </item>
<item row="5" column="0"> <item row="5" column="0">
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <layout class="QVBoxLayout" name="verticalLayout_2">
<string>Ignore files with the following extensions when automatically adding </string> <item>
</property> <widget class="QLabel" name="label_3">
<layout class="QHBoxLayout" name="horizontalLayout_3"> <property name="text">
<string>Ignore files with the following extensions when automatically adding </string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item> <item>
<widget class="QListWidget" name="opt_blocked_auto_formats"> <widget class="QListWidget" name="opt_blocked_auto_formats">
<property name="alternatingRowColors"> <property name="alternatingRowColors">

View File

@ -101,9 +101,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('gui_layout', config, restart_required=True, choices= r('gui_layout', config, restart_required=True, choices=
[(_('Wide'), 'wide'), (_('Narrow'), 'narrow')]) [(_('Wide'), 'wide'), (_('Narrow'), 'narrow')])
r('widget_style', gprefs, restart_required=True, choices= r('ui_style', gprefs, restart_required=True, choices=
[(_('System default'), 'system'), (_('Calibre style'), [(_('System default'), 'system'), (_('Calibre style'),
'calibre')]) 'calibre')])
r('tag_browser_old_look', gprefs, restart_required=True)
r('cover_flow_queue_length', config, restart_required=True) r('cover_flow_queue_length', config, restart_required=True)

View File

@ -187,12 +187,12 @@
<string>User interface &amp;style (needs restart):</string> <string>User interface &amp;style (needs restart):</string>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>opt_widget_style</cstring> <cstring>opt_ui_style</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QComboBox" name="opt_widget_style"/> <widget class="QComboBox" name="opt_ui_style"/>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -312,6 +312,18 @@ Manage Authors. You can use the values {author} and
<string>Tag Browser</string> <string>Tag Browser</string>
</attribute> </attribute>
<layout class="QGridLayout" name="gridLayout_10"> <layout class="QGridLayout" name="gridLayout_10">
<item row="3" column="2" colspan="3">
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
<property name="toolTip">
<string>A comma-separated list of categories in which items containing
periods are displayed in the tag browser trees. For example, if
this box contains 'tags' then tags of the form 'Mystery.English'
and 'Mystery.Thriller' will be displayed with English and Thriller
both under 'Mystery'. If 'tags' is not in this box,
then the tags will be displayed each on their own line.</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2"> <item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_9"> <widget class="QLabel" name="label_9">
<property name="text"> <property name="text">
@ -354,6 +366,19 @@ up into subcategories. If the partition method is set to disable, this value is
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0" colspan="5">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>690</width>
<height>252</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2"> <item row="1" column="2">
<widget class="QLabel" name="label_8111"> <widget class="QLabel" name="label_8111">
<property name="text"> <property name="text">
@ -396,27 +421,9 @@ a few top-level elements.</string>
</widget> </widget>
</item> </item>
<item row="4" column="0" colspan="5"> <item row="4" column="0" colspan="5">
<spacer name="verticalSpacer_2"> <widget class="QCheckBox" name="opt_tag_browser_old_look">
<property name="orientation"> <property name="text">
<enum>Qt::Vertical</enum> <string>Use &amp;alternating row colors in the Tag Browser</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>690</width>
<height>252</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="2" colspan="3">
<widget class="MultiCompleteLineEdit" name="opt_categories_using_hierarchy">
<property name="toolTip">
<string>A comma-separated list of categories in which items containing
periods are displayed in the tag browser trees. For example, if
this box contains 'tags' then tags of the form 'Mystery.English'
and 'Mystery.Thriller' will be displayed with English and Thriller
both under 'Mystery'. If 'tags' is not in this box,
then the tags will be displayed each on their own line.</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -5,6 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import textwrap
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting
from calibre.gui2.preferences.misc_ui import Ui_Form from calibre.gui2.preferences.misc_ui import Ui_Form
@ -31,6 +32,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('worker_limit', config, restart_required=True, setting=WorkersSetting) r('worker_limit', config, restart_required=True, setting=WorkersSetting)
r('enforce_cpu_limit', config, restart_required=True) r('enforce_cpu_limit', config, restart_required=True)
r('worker_max_time', gprefs) r('worker_max_time', gprefs)
self.opt_worker_limit.setToolTip(textwrap.fill(
_('The maximum number of jobs that will run simultaneously in '
'the background. This refers to CPU intensive tasks like '
' conversion. Lower this number'
' if you want calibre to use less CPU.')))
self.device_detection_button.clicked.connect(self.debug_device_detection) self.device_detection_button.clicked.connect(self.debug_device_detection)
self.button_open_config_dir.clicked.connect(self.open_config_dir) self.button_open_config_dir.clicked.connect(self.open_config_dir)
self.user_defined_device_button.clicked.connect(self.user_defined_device) self.user_defined_device_button.clicked.connect(self.user_defined_device)

View File

@ -12,6 +12,7 @@ from calibre.gui2.preferences import ConfigWidgetBase, test_widget, \
from calibre.gui2.preferences.search_ui import Ui_Form from calibre.gui2.preferences.search_ui import Ui_Form
from calibre.gui2 import config, error_dialog from calibre.gui2 import config, error_dialog
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.utils.icu import sort_key
class ConfigWidget(ConfigWidgetBase, Ui_Form): class ConfigWidget(ConfigWidgetBase, Ui_Form):
@ -56,7 +57,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
"can be useful to check for duplicates, to find which column contains " "can be useful to check for duplicates, to find which column contains "
"a particular item, or to have hierarchical categories (categories " "a particular item, or to have hierarchical categories (categories "
"that contain categories).")) "that contain categories)."))
self.gst = db.prefs.get('grouped_search_terms', {}) self.gst = db.prefs.get('grouped_search_terms', {}).copy()
self.orig_gst_keys = self.gst.keys() self.orig_gst_keys = self.gst.keys()
fl = [] fl = []
@ -70,6 +71,18 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.gst_value.update_items_cache(fl) self.gst_value.update_items_cache(fl)
self.fill_gst_box(select=None) self.fill_gst_box(select=None)
self.category_fields = fl
ml = [(_('Match any'), 'match_any'), (_('Match all'), 'match_all')]
r('similar_authors_match_kind', db.prefs, choices=ml)
r('similar_tags_match_kind', db.prefs, choices=ml)
r('similar_series_match_kind', db.prefs, choices=ml)
r('similar_publisher_match_kind', db.prefs, choices=ml)
self.set_similar_fields(initial=True)
self.similar_authors_search_key.currentIndexChanged[int].connect(self.something_changed)
self.similar_tags_search_key.currentIndexChanged[int].connect(self.something_changed)
self.similar_series_search_key.currentIndexChanged[int].connect(self.something_changed)
self.similar_publisher_search_key.currentIndexChanged[int].connect(self.something_changed)
self.gst_delete_button.setEnabled(False) self.gst_delete_button.setEnabled(False)
self.gst_save_button.setEnabled(False) self.gst_save_button.setEnabled(False)
self.gst_names.currentIndexChanged[int].connect(self.gst_index_changed) self.gst_names.currentIndexChanged[int].connect(self.gst_index_changed)
@ -86,6 +99,34 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.opt_grouped_search_make_user_categories.editingFinished.connect( self.opt_grouped_search_make_user_categories.editingFinished.connect(
self.muc_box_changed) self.muc_box_changed)
def set_similar_fields(self, initial=False):
self.set_similar('similar_authors_search_key', initial=initial)
self.set_similar('similar_tags_search_key', initial=initial)
self.set_similar('similar_series_search_key', initial=initial)
self.set_similar('similar_publisher_search_key', initial=initial)
def set_similar(self, name, initial=False):
field = getattr(self, name)
if not initial:
val = field.currentText()
else:
val = self.db.prefs[name]
field.blockSignals(True)
field.clear()
choices = []
choices.extend(self.category_fields)
choices.extend(sorted(self.gst.keys(), key=sort_key))
field.addItems(choices)
dex = field.findText(val)
if dex >= 0:
field.setCurrentIndex(dex)
else:
field.setCurrentIndex(0)
field.blockSignals(False)
def something_changed(self, dex):
self.changed_signal.emit()
def muc_box_changed(self): def muc_box_changed(self):
self.muc_changed = True self.muc_changed = True
@ -121,6 +162,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.gst_changed = True self.gst_changed = True
self.gst[name] = val self.gst[name] = val
self.fill_gst_box(select=name) self.fill_gst_box(select=name)
self.set_similar_fields(initial=False)
self.changed_signal.emit() self.changed_signal.emit()
def gst_delete_clicked(self): def gst_delete_clicked(self):
@ -133,9 +175,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.fill_gst_box(select='') self.fill_gst_box(select='')
self.changed_signal.emit() self.changed_signal.emit()
self.gst_changed = True self.gst_changed = True
self.set_similar_fields(initial=False)
def fill_gst_box(self, select=None): def fill_gst_box(self, select=None):
terms = sorted(self.gst.keys()) terms = sorted(self.gst.keys(), key=sort_key)
self.opt_grouped_search_make_user_categories.update_items_cache(terms) self.opt_grouped_search_make_user_categories.update_items_cache(terms)
self.gst_names.blockSignals(True) self.gst_names.blockSignals(True)
self.gst_names.clear() self.gst_names.clear()
@ -168,6 +211,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
if self.gst_changed: if self.gst_changed:
self.db.prefs.set('grouped_search_terms', self.gst) self.db.prefs.set('grouped_search_terms', self.gst)
self.db.field_metadata.add_grouped_search_terms(self.gst) self.db.field_metadata.add_grouped_search_terms(self.gst)
self.db.prefs.set('similar_authors_search_key',
unicode(self.similar_authors_search_key.currentText()))
self.db.prefs.set('similar_tags_search_key',
unicode(self.similar_tags_search_key.currentText()))
self.db.prefs.set('similar_series_search_key',
unicode(self.similar_series_search_key.currentText()))
self.db.prefs.set('similar_publisher_search_key',
unicode(self.similar_publisher_search_key.currentText()))
return ConfigWidgetBase.commit(self) return ConfigWidgetBase.commit(self)
def refresh_gui(self, gui): def refresh_gui(self, gui):

View File

@ -201,6 +201,101 @@ to be shown as user categories</string>
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="5" column="0">
<widget class="QGroupBox" name="groupBox22">
<property name="title">
<string>What to search when searching similar books</string>
</property>
<layout class="QGridLayout" name="gridLayout_22">
<item row="0" column="0" colspan="6">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;p&gt;When you search for similar books by right clicking the
book and selecting "Similar books...",
calibre constructs a search using the column lookup names specified below.
By changing the lookup name to a grouped search term you can
search multiple columns at once.&lt;/p&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_221">
<property name="text">
<string>Similar authors: </string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="similar_authors_search_key">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="opt_similar_authors_match_kind">
</widget>
</item>
<item row="1" column="3">
<widget class="QLabel" name="label_222">
<property name="text">
<string>Similar series: </string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QComboBox" name="similar_series_search_key">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QComboBox" name="opt_similar_series_match_kind">
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_223">
<property name="text">
<string>Similar tags: </string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="similar_tags_search_key">
</widget>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="opt_similar_tags_match_kind">
</widget>
</item>
<item row="2" column="3">
<widget class="QLabel" name="label_224">
<property name="text">
<string>Similar publishers: </string>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QComboBox" name="similar_publisher_search_key">
</widget>
</item>
<item row="2" column="5">
<widget class="QComboBox" name="opt_similar_publisher_match_kind">
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>

View File

@ -388,3 +388,14 @@ class SearchDialog(QDialog, Ui_Dialog):
self.do_search() self.do_search()
return QDialog.exec_(self) return QDialog.exec_(self)
if __name__ == '__main__':
from calibre.gui2 import Application
from calibre.gui2.preferences.main import init_gui
import sys
app = Application([])
app
gui = init_gui()
s = SearchDialog(gui, query=' '.join(sys.argv[1:]))
s.exec_()

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import random import random
import re import re
import urllib2 import urllib
from contextlib import closing from contextlib import closing
from lxml import html from lxml import html
@ -32,7 +32,7 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
if random.randint(1, 10) in (1, 2, 3): if random.randint(1, 10) in (1, 2, 3):
h_click = 'click-4913808-10364500' h_click = 'click-4913808-10364500'
d_click = 'click-4913808-10281551' d_click = 'click-4913808-10281551'
url = m_url + h_click url = m_url + h_click
detail_url = None detail_url = None
if detail_item: if detail_item:
@ -47,10 +47,10 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
d.exec_() d.exec_()
def search(self, query, max_results=10, timeout=60): def search(self, query, max_results=10, timeout=60):
url = 'http://www.ebooks.com/SearchApp/SearchResults.net?term=' + urllib2.quote(query) url = 'http://www.ebooks.com/SearchApp/SearchResults.net?term=' + urllib.quote_plus(query)
br = browser() br = browser()
counter = max_results counter = max_results
with closing(br.open(url, timeout=timeout)) as f: with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read()) doc = html.fromstring(f.read())
@ -63,32 +63,29 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
if not mo: if not mo:
continue continue
id = mo.group() id = mo.group()
cover_url = ''.join(data.xpath('.//div[@class="img"]//img/@src')) cover_url = ''.join(data.xpath('.//div[@class="img"]//img/@src'))
title = '' title = ''.join(data.xpath(
author = '' 'descendant::span[@class="book-title"]/a/text()')).strip()
header_parts = data.xpath('.//div[@class="descr"]/h4//a//text()') author = ''.join(data.xpath(
if header_parts: 'descendant::span[@class="author"]/a/text()')).strip()
title = header_parts[0] if not title or not author:
header_parts = header_parts[1:] continue
if header_parts:
author = ', '.join(header_parts)
counter -= 1 counter -= 1
s = SearchResult() s = SearchResult()
s.cover_url = cover_url s.cover_url = cover_url
s.title = title.strip() s.title = title.strip()
s.author = author.strip() s.author = author.strip()
s.detail_item = '?url=http://www.ebooks.com/cj.asp?IID=' + id.strip() + '&cjsku=' + id.strip() s.detail_item = '?url=http://www.ebooks.com/cj.asp?IID=' + id.strip() + '&cjsku=' + id.strip()
yield s yield s
def get_details(self, search_result, timeout): def get_details(self, search_result, timeout):
url = 'http://www.ebooks.com/ebooks/book_display.asp?IID=' url = 'http://www.ebooks.com/ebooks/book_display.asp?IID='
mo = re.search(r'\?IID=(?P<id>\d+)', search_result.detail_item) mo = re.search(r'\?IID=(?P<id>\d+)', search_result.detail_item)
if mo: if mo:
id = mo.group('id') id = mo.group('id')
@ -99,17 +96,17 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
br = browser() br = browser()
with closing(br.open(url + id, timeout=timeout)) as nf: with closing(br.open(url + id, timeout=timeout)) as nf:
pdoc = html.fromstring(nf.read()) pdoc = html.fromstring(nf.read())
price_l = pdoc.xpath('//span[@class="price"]/text()') price_l = pdoc.xpath('//span[@class="price"]/text()')
if price_l: if price_l:
price = price_l[0] price = price_l[0]
search_result.price = price.strip() search_result.price = price.strip()
search_result.drm = SearchResult.DRM_UNLOCKED search_result.drm = SearchResult.DRM_UNLOCKED
permissions = ' '.join(pdoc.xpath('//div[@class="permissions-items"]//text()')) permissions = ' '.join(pdoc.xpath('//div[@class="permissions-items"]//text()'))
if 'off' in permissions: if 'off' in permissions:
search_result.drm = SearchResult.DRM_LOCKED search_result.drm = SearchResult.DRM_LOCKED
fdata = pdoc.xpath('//div[contains(@class, "more-links") and contains(@class, "more-links-info")]/div//span/text()') fdata = pdoc.xpath('//div[contains(@class, "more-links") and contains(@class, "more-links-info")]/div//span/text()')
if len(fdata) > 1: if len(fdata) > 1:
search_result.formats = ', '.join(fdata[1:]) search_result.formats = ', '.join(fdata[1:])

View File

@ -22,10 +22,10 @@ from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog from calibre.gui2.store.web_store_dialog import WebStoreDialog
class GutenbergStore(BasicStoreConfig, StorePlugin): class GutenbergStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False): def open(self, parent=None, detail_item=None, external=False):
url = 'http://gutenberg.org/' url = 'http://gutenberg.org/'
if detail_item: if detail_item:
detail_item = url_slash_cleaner(url + detail_item) detail_item = url_slash_cleaner(url + detail_item)
@ -39,46 +39,46 @@ class GutenbergStore(BasicStoreConfig, StorePlugin):
def search(self, query, max_results=10, timeout=60): def search(self, query, max_results=10, timeout=60):
url = 'http://m.gutenberg.org/ebooks/search.mobile/?default_prefix=all&sort_order=title&query=' + urllib.quote_plus(query) url = 'http://m.gutenberg.org/ebooks/search.mobile/?default_prefix=all&sort_order=title&query=' + urllib.quote_plus(query)
br = browser() br = browser()
counter = max_results counter = max_results
with closing(br.open(url, timeout=timeout)) as f: with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read()) doc = html.fromstring(f.read())
for data in doc.xpath('//ol[@class="results"]//li[contains(@class, "icon_title") and not(contains(@class, "toplink"))]'): for data in doc.xpath('//ol[@class="results"]/li[@class="booklink"]'):
if counter <= 0: if counter <= 0:
break break
id = ''.join(data.xpath('./a/@href')) id = ''.join(data.xpath('./a/@href'))
id = id.split('.mobile')[0] id = id.split('.mobile')[0]
title = ''.join(data.xpath('.//span[@class="title"]/text()')) title = ''.join(data.xpath('.//span[@class="title"]/text()'))
author = ''.join(data.xpath('.//span[@class="subtitle"]/text()')) author = ''.join(data.xpath('.//span[@class="subtitle"]/text()'))
counter -= 1 counter -= 1
s = SearchResult() s = SearchResult()
s.cover_url = '' s.cover_url = ''
s.detail_item = id.strip() s.detail_item = id.strip()
s.title = title.strip() s.title = title.strip()
s.author = author.strip() s.author = author.strip()
s.price = '$0.00' s.price = '$0.00'
s.drm = SearchResult.DRM_UNLOCKED s.drm = SearchResult.DRM_UNLOCKED
yield s yield s
def get_details(self, search_result, timeout): def get_details(self, search_result, timeout):
url = url_slash_cleaner('http://m.gutenberg.org/' + search_result.detail_item) url = url_slash_cleaner('http://m.gutenberg.org/' + search_result.detail_item)
br = browser() br = browser()
with closing(br.open(url, timeout=timeout)) as nf: with closing(br.open(url, timeout=timeout)) as nf:
doc = html.fromstring(nf.read()) doc = html.fromstring(nf.read())
for save_item in doc.xpath('//li[contains(@class, "icon_save")]/a'): for save_item in doc.xpath('//li[contains(@class, "icon_save")]/a'):
type = save_item.get('type') type = save_item.get('type')
href = save_item.get('href') href = save_item.get('href')
if type: if type:
ext = mimetypes.guess_extension(type) ext = mimetypes.guess_extension(type)
if ext: if ext:

View File

@ -0,0 +1,84 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import urllib
from contextlib import closing
from lxml import html, etree
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 SonyStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
if detail_item:
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_item)))
else:
d = WebStoreDialog(self.gui, 'http://ebookstore.sony.com', parent, detail_item)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://ebookstore.sony.com/search?keyword=%s'%urllib.quote_plus(
query)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for item in doc.xpath('//div[contains(@class, "searchResult")]/'
'descendant::li[contains(@class, "hreview")]'):
if counter <= 0:
break
curr = ''.join(item.xpath('descendant::div[@class="pricing"]/descendant::*[@class="currency"]/@title')).strip()
amt = ''.join(item.xpath('descendant::div[@class="pricing"]/descendant::*[@class="amount"]/text()')).strip()
s = SearchResult()
s.price = (curr+' '+amt) if (curr and amt) else _('Not Available')
title = item.xpath('descendant::h3[@class="item"]')
if not title: continue
title = etree.tostring(title[0], method='text',
encoding=unicode)
if not title: continue
s.title = title.strip()
s.author = ''.join(item.xpath(
'descendant::li[contains(@class, "author")]/'
'a[@class="fn"]/text()')).strip()
if not s.author: continue
detail_url = ''.join(item.xpath('descendant::h3[@class="item"]'
'/descendant::a[@class="fn" and @href]/@href'))
if not detail_url: continue
s.detail_item = detail_url
counter -= 1
cover_url = ''.join(item.xpath(
'descendant::li[@class="coverart"]/'
'descendant::img[@src]/@src'))
if cover_url:
if cover_url.startswith('//'):
cover_url = 'http:' + cover_url
elif cover_url.startswith('/'):
cover_url = 'http://ebookstore.sony.com'+cover_url
s.cover_url = url_slash_cleaner(cover_url)
s.drm = SearchResult.DRM_UNKNOWN
s.formats = 'Sony'
yield s

View File

@ -40,39 +40,35 @@ class VirtualoStore(BasicStoreConfig, StorePlugin):
url = 'http://virtualo.pl/?q=' + urllib.quote(query) + '&f=format_id:4,6,3' url = 'http://virtualo.pl/?q=' + urllib.quote(query) + '&f=format_id:4,6,3'
br = browser() br = browser()
drm_pattern = re.compile("ADE") no_drm_pattern = re.compile("Znak wodny")
counter = max_results counter = max_results
with closing(br.open(url, timeout=timeout)) as f: with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read()) doc = html.fromstring(f.read())
for data in doc.xpath('//div[@id="product_list"]/div/div[@class="column"]'): for data in doc.xpath('//div[@id="content"]//div[@class="list_box list_box_border"]'):
if counter <= 0: if counter <= 0:
break break
id = ''.join(data.xpath('.//table/tr[1]/td[1]/a/@href')) id = ''.join(data.xpath('.//div[@class="list_middle_left"]//a/@href'))
if not id: if not id:
continue continue
price = ''.join(data.xpath('.//span[@class="price"]/text() | .//span[@class="price abbr"]/text()')) price = ''.join(data.xpath('.//span[@class="price"]/text() | .//span[@class="price abbr"]/text()'))
cover_url = ''.join(data.xpath('.//table/tr[1]/td[1]/a/img/@src')) cover_url = ''.join(data.xpath('.//div[@class="list_middle_left"]//a/img/@src'))
title = ''.join(data.xpath('.//div[@class="title"]/a/text()')) title = ''.join(data.xpath('.//div[@class="list_title list_text_left"]/a/text()'))
title = re.sub(r'\ WM', '', title) author = ', '.join(data.xpath('.//div[@class="list_authors list_text_left"]/a/text()'))
author = ', '.join(data.xpath('.//div[@class="authors"]/a/text()')) formats = [ form.split('_')[-1].replace('.png', '') for form in data.xpath('.//div[@style="width:55%;float:left;text-align:left;height:18px;"]//img/@src')]
formats = ', '.join(data.xpath('.//span[@class="format"]/a/text()')) nodrm = no_drm_pattern.search(''.join(data.xpath('.//div[@style="width:45%;float:right;text-align:right;height:18px;"]/div/div/text()')))
formats = re.sub(r'(, )?ONLINE(, )?', '', formats)
drm = drm_pattern.search(formats)
formats = re.sub(r'(, )?ADE(, )?', '', formats)
formats = re.sub(r'\ WM', '', formats)
counter -= 1 counter -= 1
s = SearchResult() s = SearchResult()
s.cover_url = cover_url.split('.jpg')[0] + '.jpg' s.cover_url = cover_url.split('.jpg')[0] + '.jpg'
s.title = title.strip() + ' ' + formats s.title = title.strip()
s.author = author.strip() s.author = author.strip()
s.price = price + '' s.price = price + ''
s.detail_item = 'http://virtualo.pl' + id.strip().split('http://')[0] s.detail_item = 'http://virtualo.pl' + id.strip().split('http://')[0]
s.formats = formats.upper().strip() s.formats = ', '.join(formats).upper()
s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED s.drm = SearchResult.DRM_UNLOCKED if nodrm else SearchResult.DRM_UNKNOWN
yield s yield s

View File

@ -22,16 +22,26 @@ from calibre.utils.icu import sort_key
class TagDelegate(QStyledItemDelegate): # {{{ class TagDelegate(QStyledItemDelegate): # {{{
def __init__(self, *args, **kwargs):
QStyledItemDelegate.__init__(self, *args, **kwargs)
self.old_look = gprefs['tag_browser_old_look']
def paint(self, painter, option, index): def paint(self, painter, option, index):
item = index.data(Qt.UserRole).toPyObject() item = index.data(Qt.UserRole).toPyObject()
QStyledItemDelegate.paint(self, painter, option, index) QStyledItemDelegate.paint(self, painter, option, index)
widget = self.parent()
style = QApplication.style() if widget is None else widget.style()
self.initStyleOption(option, index)
if item.boxed:
r = style.subElementRect(style.SE_ItemViewItemFocusRect, option,
widget)
painter.save()
painter.drawLine(r.bottomLeft(), r.bottomRight())
painter.restore()
if item.type != TagTreeItem.TAG: if item.type != TagTreeItem.TAG:
return return
if (item.tag.state == 0 and config['show_avg_rating'] and if (item.tag.state == 0 and config['show_avg_rating'] and
item.tag.avg_rating is not None): item.tag.avg_rating is not None):
self.initStyleOption(option, index)
widget = self.parent()
style = QApplication.style() if widget is None else widget.style()
r = style.subElementRect(style.SE_ItemViewItemDecoration, r = style.subElementRect(style.SE_ItemViewItemDecoration,
option, widget) option, widget)
icon = option.icon icon = option.icon
@ -40,7 +50,12 @@ class TagDelegate(QStyledItemDelegate): # {{{
nr = r.adjusted(0, 0, 0, 0) nr = r.adjusted(0, 0, 0, 0)
nr.setBottom(r.bottom()-int(r.height()*(rating/5.0))) nr.setBottom(r.bottom()-int(r.height()*(rating/5.0)))
painter.setClipRect(nr) painter.setClipRect(nr)
painter.fillRect(r, widget.palette().window()) bg = option.palette.window()
if self.old_look:
bg = (option.palette.alternateBase() if
option.features&option.Alternate else
option.palette.base())
painter.fillRect(r, bg)
style.proxy().drawPrimitive(style.PE_PanelItemViewItem, option, style.proxy().drawPrimitive(style.PE_PanelItemViewItem, option,
painter, widget) painter, widget)
painter.setOpacity(0.3) painter.setOpacity(0.3)
@ -48,6 +63,7 @@ class TagDelegate(QStyledItemDelegate): # {{{
icon.On) icon.On)
painter.restore() painter.restore()
# }}} # }}}
class TagsView(QTreeView): # {{{ class TagsView(QTreeView): # {{{
@ -101,13 +117,14 @@ class TagsView(QTreeView): # {{{
self._model.user_categories_edited.connect(self.user_categories_edited, self._model.user_categories_edited.connect(self.user_categories_edited,
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
self._model.drag_drop_finished.connect(self.drag_drop_finished) self._model.drag_drop_finished.connect(self.drag_drop_finished)
self.setStyleSheet(''' stylish_tb = '''
QTreeView { QTreeView {
background-color: palette(window); background-color: palette(window);
color: palette(text); color: palette(window-text);
border: none; border: none;
} }
'''
self.setStyleSheet('''
QTreeView::item { QTreeView::item {
border: 1px solid transparent; border: 1px solid transparent;
padding-top:0.9ex; padding-top:0.9ex;
@ -119,7 +136,9 @@ class TagsView(QTreeView): # {{{
border: 1px solid #bfcde4; border: 1px solid #bfcde4;
border-radius: 6px; border-radius: 6px;
} }
''') ''' + ('' if gprefs['tag_browser_old_look'] else stylish_tb))
if gprefs['tag_browser_old_look']:
self.setAlternatingRowColors(True)
@property @property
def hidden_categories(self): def hidden_categories(self):

View File

@ -532,6 +532,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
if self.content_server is not None: if self.content_server is not None:
self.content_server.set_database(db) self.content_server.set_database(db)
self.library_path = newloc self.library_path = newloc
prefs['library_path'] = self.library_path
self.book_on_device(None, reset=True) self.book_on_device(None, reset=True)
db.set_book_on_device_func(self.book_on_device) db.set_book_on_device_func(self.book_on_device)
self.library_view.set_database(db) self.library_view.set_database(db)
@ -541,7 +542,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.search.clear() self.search.clear()
self.saved_search.clear() self.saved_search.clear()
self.book_details.reset_info() self.book_details.reset_info()
prefs['library_path'] = self.library_path
#self.library_view.model().count_changed() #self.library_view.model().count_changed()
db = self.library_view.model().db db = self.library_view.model().db
self.iactions['Choose Library'].count_changed(db.count()) self.iactions['Choose Library'].count_changed(db.count())

View File

@ -20,7 +20,7 @@ class TOCView(QTreeView):
self.setStyleSheet(''' self.setStyleSheet('''
QTreeView { QTreeView {
background-color: palette(window); background-color: palette(window);
color: palette(text); color: palette(window-text);
border: none; border: none;
} }
QTreeView::item { QTreeView::item {

View File

@ -21,7 +21,7 @@ from calibre.gui2 import (NONE, error_dialog, pixmap_to_data, gprefs,
from calibre.gui2.filename_pattern_ui import Ui_Form from calibre.gui2.filename_pattern_ui import Ui_Form
from calibre import fit_image from calibre import fit_image
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
from calibre.utils.config import prefs, XMLConfig, tweaks from calibre.utils.config import prefs, XMLConfig
from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files, from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files,
IMAGE_EXTENSIONS, dnd_has_extension, DownloadDialog) IMAGE_EXTENSIONS, dnd_has_extension, DownloadDialog)
@ -1000,13 +1000,6 @@ class SplitterHandle(QSplitterHandle):
if oh != self.highlight: if oh != self.highlight:
self.update() self.update()
def paintEvent(self, ev):
QSplitterHandle.paintEvent(self, ev)
if self.highlight and tweaks['draw_hidden_section_indicators']:
painter = QPainter(self)
painter.setClipRect(ev.rect())
painter.fillRect(self.rect(), Qt.yellow)
def mouseDoubleClickEvent(self, ev): def mouseDoubleClickEvent(self, ev):
self.double_clicked.emit(self) self.double_clicked.emit(self)

View File

@ -574,6 +574,9 @@ def command_set_metadata(args, dbpath):
if len(args) > 2: if len(args) > 2:
opf = args[2] opf = args[2]
if not os.path.exists(opf):
prints(_('The OPF file %s does not exist')%opf, file=sys.stderr)
return 1
do_set_metadata(db, book_id, opf) do_set_metadata(db, book_id, opf)
if opts.field: if opts.field:

View File

@ -236,6 +236,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
defs['categories_using_hierarchy'] = [] defs['categories_using_hierarchy'] = []
defs['column_color_rules'] = [] defs['column_color_rules'] = []
defs['grouped_search_make_user_categories'] = [] defs['grouped_search_make_user_categories'] = []
defs['similar_authors_search_key'] = 'authors'
defs['similar_authors_match_kind'] = 'match_any'
defs['similar_publisher_search_key'] = 'publisher'
defs['similar_publisher_match_kind'] = 'match_any'
defs['similar_tags_search_key'] = 'tags'
defs['similar_tags_match_kind'] = 'match_all'
defs['similar_series_search_key'] = 'series'
defs['similar_series_match_kind'] = 'match_any'
# Migrate the bool tristate tweak # Migrate the bool tristate tweak
defs['bools_are_tristate'] = \ defs['bools_are_tristate'] = \

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More