mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
0.8.56
This commit is contained in:
commit
f7b93c1554
@ -20,6 +20,57 @@
|
||||
# - 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
|
||||
date: 2012-06-08
|
||||
|
||||
|
@ -172,7 +172,7 @@ You can see the ``prefs`` object being used in main.py:
|
||||
: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|.
|
||||
|
@ -5,11 +5,11 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
||||
timefmt = ''
|
||||
__author__ = 'Dave Asbury'
|
||||
cover_url = 'http://1.bp.blogspot.com/_GwWyq5eGw9M/S9BHPHxW55I/AAAAAAAAB6Q/iGCWl0egGzg/s320/Birmingham+post+Lite+front.JPG'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 20
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 12
|
||||
remove_empty_feeds = True
|
||||
remove_javascript = True
|
||||
auto_cleanup = True
|
||||
#auto_cleanup = True
|
||||
language = 'en_GB'
|
||||
|
||||
|
||||
@ -17,9 +17,12 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
||||
|
||||
|
||||
keep_only_tags = [
|
||||
#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(name='p')
|
||||
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(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'})
|
||||
]
|
||||
remove_tags = [
|
||||
@ -28,7 +31,7 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
||||
]
|
||||
feeds = [
|
||||
#(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'Sports',u'http://www.birminghampost.net/midlands-birmingham-sport/rss.xml'),
|
||||
(u'Bloggs & Comments',u'http://www.birminghampost.net/comment/rss.xml')
|
||||
|
@ -4,6 +4,7 @@ __copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
www.csmonitor.com
|
||||
'''
|
||||
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class CSMonitor(BasicNewsRecipe):
|
||||
@ -40,13 +41,15 @@ class CSMonitor(BasicNewsRecipe):
|
||||
|
||||
remove_tags = [
|
||||
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']})
|
||||
]
|
||||
keep_only_tags = [
|
||||
dict(name='h1', attrs={'class':'head'})
|
||||
,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']
|
||||
|
||||
@ -74,10 +77,10 @@ class CSMonitor(BasicNewsRecipe):
|
||||
if nexttag:
|
||||
nurl = 'http://www.csmonitor.com' + nexttag['href']
|
||||
soup2 = self.index_to_soup(nurl)
|
||||
texttag = soup2.find(attrs={'class':'sBody'})
|
||||
texttag = soup2.find(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
|
||||
if texttag:
|
||||
appendtag = soup.find(attrs={'class':'sBody'})
|
||||
for citem in texttag.findAll(attrs={'class':['podStoryRel','bottom-rel','hide']}):
|
||||
appendtag = soup.find(attrs={'class':re.compile('(^|| )sBody($|| )', re.DOTALL)})
|
||||
for citem in texttag.findAll(attrs={'class':[re.compile('(^|| )podStoryRel($|| )', re.DOTALL),'bottom-rel','hide']}):
|
||||
citem.extract()
|
||||
self.append_page(soup2)
|
||||
texttag.extract()
|
||||
|
@ -7,7 +7,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
description = 'News as provided by The Daily Mirror -UK'
|
||||
|
||||
__author__ = 'Dave Asbury'
|
||||
# last updated 28/4/12
|
||||
# last updated 8/6/12
|
||||
language = 'en_GB'
|
||||
#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' : 'styleGroup clearfix'}),
|
||||
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(attrs={'class' : ['article-attr','byline append-1','published']}),
|
||||
@ -37,6 +37,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
|
||||
|
||||
remove_tags = [
|
||||
dict(attrs={'class' : ['article sa-teaser type-opinion','image-gallery','gallery-caption']}),
|
||||
dict(attrs={'class' : 'comment'}),
|
||||
dict(name='title'),
|
||||
dict(name='ul',attrs={'class' : 'clearfix breadcrumbs '}),
|
||||
@ -89,6 +90,3 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
#cover_url = cov2
|
||||
#cover_url = 'http://www.thesun.co.uk/img/global/new-masthead-logo.png'
|
||||
return cover_url
|
||||
|
||||
|
||||
|
||||
|
@ -15,6 +15,10 @@ class TheIndependentNew(BasicNewsRecipe):
|
||||
#Flag to enable/disable image fetching (not business)
|
||||
_FETCH_IMAGES = True
|
||||
|
||||
#Set max gallery images here (respects _FETCH_IMAGES)
|
||||
# -1 for infinite
|
||||
_MAX_GALLERY_IMAGES = -1
|
||||
|
||||
|
||||
#used for converting rating to stars
|
||||
_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={'class' : ['autoplay','openBiogPopup']}),
|
||||
dict(name='img',attrs={'alt' : ['Get Adobe Flash player']}),
|
||||
dict(name='img',attrs={'alt' : ['view gallery']}),
|
||||
dict(attrs={'style' : re.compile('.*')}),
|
||||
]
|
||||
|
||||
@ -171,25 +176,43 @@ class TheIndependentNew(BasicNewsRecipe):
|
||||
for item in element.findAll('a',attrs={'href' : re.compile('.*')}):
|
||||
if item.img is not None:
|
||||
#use full size image
|
||||
images = []
|
||||
|
||||
img = item.findNext('img')
|
||||
|
||||
img['src'] = item['href']
|
||||
|
||||
#insert caption if available
|
||||
if img.get('title') and (len(img['title']) > 1):
|
||||
if not '?action=gallery' in item['href']:
|
||||
img['src'] = item['href']
|
||||
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)
|
||||
|
||||
#picture before text
|
||||
images.append((img, tag))
|
||||
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()
|
||||
item.insert(0,img)
|
||||
item.insert(1,tag)
|
||||
for (img, title) in images:
|
||||
#insert caption if available
|
||||
if title:
|
||||
#picture before text
|
||||
img.extract()
|
||||
item.insert(0,img)
|
||||
item.insert(1,title)
|
||||
|
||||
# remove link
|
||||
item.name = "div"
|
||||
item["class"]='image'
|
||||
del item["href"]
|
||||
# remove link
|
||||
item.name = "div"
|
||||
item["class"]='image'
|
||||
del item["href"]
|
||||
|
||||
|
||||
#remove empty subtitles
|
||||
@ -324,6 +347,44 @@ class TheIndependentNew(BasicNewsRecipe):
|
||||
|
||||
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(
|
||||
self,
|
||||
item,
|
||||
|
@ -4,6 +4,7 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
||||
description = 'News as provide by The Metro -UK'
|
||||
#timefmt = ''
|
||||
__author__ = 'Dave Asbury'
|
||||
#last update 9/6/12
|
||||
cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/276636_117118184990145_2132092232_n.jpg'
|
||||
#no_stylesheets = True
|
||||
oldest_article = 1
|
||||
@ -11,7 +12,7 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
|
||||
remove_empty_feeds = True
|
||||
remove_javascript = True
|
||||
auto_cleanup = True
|
||||
|
||||
encoding = 'UTF-8'
|
||||
|
||||
language = 'en_GB'
|
||||
masthead_url = 'http://e-edition.metro.co.uk/images/metro_logo.gif'
|
||||
|
@ -1,23 +1,47 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
from calibre import browser
|
||||
class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
title = u'New Musical Express Magazine'
|
||||
__author__ = "scissors"
|
||||
language = 'en'
|
||||
description = 'Author D.Asbury. UK Rock & Pop Mag. '
|
||||
__author__ = 'Dave Asbury'
|
||||
# last updated 9/6/12
|
||||
remove_empty_feeds = True
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
cover_url = 'http://tawanda3000.files.wordpress.com/2011/02/nme-logo.jpg'
|
||||
max_articles_per_feed = 20
|
||||
#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 = [
|
||||
dict( attrs={'class':'clear_icons'}),
|
||||
dict( attrs={'class':'clear_icons'}),
|
||||
dict( attrs={'class':'share_links'}),
|
||||
dict( attrs={'id':'right_panel'}),
|
||||
dict( attrs={'class':'today box'})
|
||||
dict( attrs={'class':'today box'}),
|
||||
|
||||
]
|
||||
|
||||
]
|
||||
|
||||
keep_only_tags = [
|
||||
|
||||
@ -28,7 +52,9 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
dict(attrs={'class' : 'bPosts'}),
|
||||
dict(attrs={'class' : 'text'}),
|
||||
dict(attrs={'id' : 'article_gallery'}),
|
||||
#dict(attrs={'class' : 'image'}),
|
||||
dict(attrs={'class' : 'article_text'})
|
||||
|
||||
]
|
||||
|
||||
|
||||
@ -36,7 +62,8 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
|
||||
feeds = [
|
||||
(u'NME News', u'http://feeds2.feedburner.com/nmecom/rss/newsxml'),
|
||||
(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://feeds2.feedburner.com/nme/SdML'),
|
||||
(u'Reviews',u'http://feed43.com/4138608576351646.xml'),
|
||||
(u'Bloggs',u'http://feed43.com/3326754333186048.xml'),
|
||||
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
__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
|
||||
@ -10,7 +10,7 @@ from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class Nzz(BasicNewsRecipe):
|
||||
title = 'NZZ Online'
|
||||
__author__ = 'Darko Miletic'
|
||||
__author__ = 'Darko Miletic, Bernd Leinfelder'
|
||||
description = 'Laufend aktualisierte Nachrichten, Analysen und Hintergruende zu Politik, Wirtschaft, Kultur und Sport'
|
||||
publisher = 'NZZ AG'
|
||||
category = 'news, politics, nachrichten, Switzerland'
|
||||
@ -41,31 +41,31 @@ class Nzz(BasicNewsRecipe):
|
||||
,'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_tags_before = dict(id='main')
|
||||
remove_tags_after = dict(id='articleBodyText')
|
||||
remove_tags = [
|
||||
dict(name=['object','link','base','meta','iframe'])
|
||||
,dict(attrs={'id':'content_rectangle_1'})
|
||||
,dict(attrs={'class':['weiterfuehrendeLinks','fussnote','video']})
|
||||
,dict(id='social-media-floater')
|
||||
,dict(name='div',attrs={'class':['box']})
|
||||
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'International' , u'http://www.nzz.ch/nachrichten/international?rss=true')
|
||||
,(u'Schweiz' , u'http://www.nzz.ch/nachrichten/schweiz?rss=true')
|
||||
,(u'Wirtschaft' , u'http://www.nzz.ch/nachrichten/wirtschaft/aktuell?rss=true')
|
||||
,(u'Finanzmaerkte' , u'http://www.nzz.ch/finanzen/nachrichten?rss=true')
|
||||
,(u'Zuerich' , u'http://www.nzz.ch/nachrichten/zuerich?rss=true')
|
||||
,(u'Sport' , u'http://www.nzz.ch/nachrichten/sport?rss=true')
|
||||
,(u'Panorama' , u'http://www.nzz.ch/nachrichten/panorama?rss=true')
|
||||
,(u'Kultur' , u'http://www.nzz.ch/nachrichten/kultur/aktuell?rss=true')
|
||||
,(u'Wissenschaft' , u'http://www.nzz.ch/nachrichten/wissenschaft?rss=true')
|
||||
,(u'Medien' , u'http://www.nzz.ch/nachrichten/medien?rss=true')
|
||||
,(u'Reisen' , u'http://www.nzz.ch/magazin/reisen?rss=true')
|
||||
(u'International' , u'http://www.nzz.ch/aktuell/international.rss')
|
||||
,(u'Schweiz' , u'http://www.nzz.ch/aktuell/schweiz.rss')
|
||||
,(u'Wirtschaft' , u'http://www.nzz.ch/aktuell/wirtschaft/uebersicht.rss')
|
||||
,(u'Finanzmaerkte' , u'http://www.nzz.ch/finanzen/uebersicht/finanznachrichten.rss')
|
||||
,(u'Zuerich' , u'http://www.nzz.ch/aktuell/zuerich/uebersicht.rss')
|
||||
,(u'Sport' , u'http://www.nzz.ch/aktuell/sport/uebersicht.rss')
|
||||
,(u'Panorama' , u'http://www.nzz.ch/aktuell/panorama.rss')
|
||||
,(u'Kultur' , u'http://www.nzz.ch/aktuell/feuilleton/uebersicht.rss')
|
||||
,(u'Wissenschaft' , u'http://www.nzz.ch/wissen/uebersicht.rss')
|
||||
,(u'Reisen' , u'http://www.nzz.ch/lebensart/reisen-freizeit.rss')
|
||||
,(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)
|
||||
|
90
recipes/nzz_webpaper.recipe
Normal file
90
recipes/nzz_webpaper.recipe
Normal 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
|
||||
|
||||
|
@ -1,26 +1,42 @@
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||
|
||||
class SmithsonianMagazine(BasicNewsRecipe):
|
||||
title = u'Smithsonian Magazine'
|
||||
language = 'en'
|
||||
__author__ = 'Krittika Goyal'
|
||||
__author__ = 'Krittika Goyal and TerminalVeracity'
|
||||
oldest_article = 31#days
|
||||
max_articles_per_feed = 50
|
||||
use_embedded_content = False
|
||||
#encoding = 'latin1'
|
||||
recursions = 1
|
||||
cover_url = 'http://sphotos.xx.fbcdn.net/hphotos-snc7/431147_10150602715983253_764313347_n.jpg'
|
||||
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_tags_before = dict(name='h1', attrs={'class':'heading'})
|
||||
remove_tags_after = dict(name='p', attrs={'id':'articlePaginationWrapper'})
|
||||
remove_tags_after = dict(name='div', attrs={'class':['post','articlePaginationWrapper']})
|
||||
remove_tags = [
|
||||
dict(name='iframe'),
|
||||
dict(name='div', attrs={'class':'article_sidebar_border'}),
|
||||
dict(name='div', attrs={'id':['article_sidebar_border', 'most-popular_large', 'most-popular-body_large']}),
|
||||
##dict(name='ul', attrs={'class':'article-tools'}),
|
||||
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','comment_section','article-related']}),
|
||||
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):
|
||||
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>')
|
||||
body = soup.find(name='body')
|
||||
body.insert(0, story)
|
||||
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
|
||||
|
@ -1,5 +1,5 @@
|
||||
__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
|
||||
'''
|
||||
@ -24,7 +24,17 @@ class Vreme(BasicNewsRecipe):
|
||||
language = 'sr'
|
||||
publication_type = 'magazine'
|
||||
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 = {
|
||||
'comment' : description
|
||||
@ -58,9 +68,12 @@ class Vreme(BasicNewsRecipe):
|
||||
for item in soup.findAll(['h3','h4']):
|
||||
description = u''
|
||||
title_prefix = u''
|
||||
feed_link = item.find('a')
|
||||
if feed_link and feed_link.has_key('href') and feed_link['href'].startswith('/cms/view.php'):
|
||||
url = self.INDEX + feed_link['href']
|
||||
feed_link = item.find('a', href=True)
|
||||
if feed_link and (feed_link['href'].startswith('cms/view.php') or feed_link['href'].startswith('/cms/view.php')):
|
||||
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)
|
||||
date = strftime(self.timefmt)
|
||||
articles.append({
|
||||
|
@ -442,12 +442,6 @@ metadata_edit_custom_column_order = []
|
||||
# calibre.
|
||||
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
|
||||
# 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
|
||||
|
@ -15,7 +15,7 @@ function show_reference_panel(ref) {
|
||||
panel = $("#calibre_reference_panel");
|
||||
}
|
||||
$("> 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);
|
||||
}
|
||||
|
||||
|
@ -482,6 +482,10 @@ class Py2App(object):
|
||||
shutil.rmtree(tdir)
|
||||
shutil.rmtree(os.path.join(self.site_packages, 'calibre', 'plugins'))
|
||||
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
|
||||
def add_modules_from_dir(self, src):
|
||||
@ -551,6 +555,15 @@ class Py2App(object):
|
||||
if dest2.endswith('.so'):
|
||||
self.fix_dependencies_in_lib(dest2)
|
||||
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
|
||||
def remove_bytecode(self, dest):
|
||||
|
@ -14,7 +14,7 @@ from setup.build_environment import msvc, MT, RC
|
||||
from setup.installer.windows.wix import WixMixIn
|
||||
|
||||
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']
|
||||
QTCURVE = r'C:\plugins\styles'
|
||||
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
||||
|
@ -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
|
||||
|
||||
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.
|
||||
|
||||
|
@ -18,14 +18,14 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
|
||||
"devel@lists.alioth.debian.org>\n"
|
||||
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
|
||||
"PO-Revision-Date: 2012-05-29 09:12+0000\n"
|
||||
"Last-Translator: Moritz Höwer <moritzhoewermail@gmx.de>\n"
|
||||
"PO-Revision-Date: 2012-06-10 11:16+0000\n"
|
||||
"Last-Translator: SimonFS <simonschuette@arcor.de>\n"
|
||||
"Language-Team: German <debian-l10n-german@lists.debian.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-05-30 05:20+0000\n"
|
||||
"X-Generator: Launchpad (build 15316)\n"
|
||||
"X-Launchpad-Export-Date: 2012-06-11 04:46+0000\n"
|
||||
"X-Generator: Launchpad (build 15376)\n"
|
||||
"Language: de\n"
|
||||
|
||||
#. name for aaa
|
||||
@ -139,7 +139,7 @@ msgstr ""
|
||||
|
||||
#. name for abe
|
||||
msgid "Abnaki; Western"
|
||||
msgstr ""
|
||||
msgstr "Abnaki; Westlich"
|
||||
|
||||
#. name for abf
|
||||
msgid "Abai Sungai"
|
||||
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 8, 55)
|
||||
numeric_version = (0, 8, 56)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
|
@ -1177,6 +1177,16 @@ class StoreAmazonKindleStore(StoreBase):
|
||||
formats = ['KINDLE']
|
||||
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):
|
||||
name = 'Amazon DE Kindle'
|
||||
author = 'Charles Haley'
|
||||
@ -1623,7 +1633,7 @@ plugins += [
|
||||
StoreAmazonITKindleStore,
|
||||
StoreAmazonUKKindleStore,
|
||||
StoreBaenWebScriptionStore,
|
||||
StoreBNStore,
|
||||
StoreBNStore, StoreSonyStore,
|
||||
StoreBeamEBooksDEStore,
|
||||
StoreBeWriteStore,
|
||||
StoreBiblioStore,
|
||||
|
@ -72,6 +72,7 @@ class ANDROID(USBMS):
|
||||
# Sony Ericsson
|
||||
0xfce : {
|
||||
0xd12e : [0x0100],
|
||||
0xe156 : [0x226],
|
||||
0xe15d : [0x226],
|
||||
0xe14f : [0x0226],
|
||||
0x614f : [0x0226, 0x100],
|
||||
|
@ -53,6 +53,7 @@ class KF8Writer(object):
|
||||
|
||||
self.log('\tGenerating KF8 markup...')
|
||||
self.dup_data()
|
||||
self.cleanup_markup()
|
||||
self.replace_resource_links()
|
||||
self.extract_css_into_flows()
|
||||
self.extract_svg_into_flows()
|
||||
@ -89,6 +90,15 @@ class KF8Writer(object):
|
||||
def data(self, item):
|
||||
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):
|
||||
''' Replace links to resources (raster images/fonts) with pointers to
|
||||
the MOBI record containing the resource. The pointers are of the form:
|
||||
|
@ -33,7 +33,8 @@ aid_able_tags = {'a', 'abbr', 'address', 'article', 'aside', 'audio', 'b',
|
||||
'video'}
|
||||
|
||||
_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)
|
||||
|
||||
def close_self_closing_tags(raw):
|
||||
@ -118,6 +119,7 @@ class Skeleton(object):
|
||||
def render(self, root):
|
||||
raw = tostring(root, xml_declaration=True)
|
||||
raw = raw.replace(b'<html', bytes('<html xmlns="%s"'%XHTML_NS), 1)
|
||||
raw = close_self_closing_tags(raw)
|
||||
return raw
|
||||
|
||||
def calculate_metrics(self, root):
|
||||
@ -372,6 +374,11 @@ class Chunker(object):
|
||||
# the chunk immediately after
|
||||
pos_fid = (chunk.sequence_number, 0, offset)
|
||||
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:
|
||||
raise ValueError('Could not find chunk for aid: %r'%
|
||||
match.group(1))
|
||||
|
@ -73,7 +73,7 @@ class TOCAdder(object):
|
||||
id, href = oeb.manifest.generate('contents', 'contents.xhtml')
|
||||
item = self.generated_item = oeb.manifest.add(id, href, XHTML_MIME,
|
||||
data=root)
|
||||
if opts.mobi_toc_at_start == 'end':
|
||||
if self.at_start:
|
||||
oeb.spine.insert(0, item, linear=True)
|
||||
else:
|
||||
oeb.spine.add(item, linear=False)
|
||||
|
@ -106,7 +106,8 @@ gprefs.defaults['auto_add_path'] = None
|
||||
gprefs.defaults['auto_add_check_for_duplicates'] = False
|
||||
gprefs.defaults['blocked_auto_formats'] = []
|
||||
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
|
||||
@ -782,7 +783,7 @@ class Application(QApplication):
|
||||
font.setStretch(s)
|
||||
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()
|
||||
else:
|
||||
st = self.style()
|
||||
|
@ -26,37 +26,56 @@ class SimilarBooksAction(InterfaceAction):
|
||||
(_('Books in this series'), 'books_in_series.png', 'series',
|
||||
_('Alt+Shift+S')),
|
||||
(_('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),
|
||||
attr=target)
|
||||
m.addAction(ac)
|
||||
ac.triggered.connect(partial(self.show_similar_books, target))
|
||||
self.qaction.setMenu(m)
|
||||
|
||||
def show_similar_books(self, type, *args):
|
||||
search, join = [], ' '
|
||||
def show_similar_books(self, typ, *args):
|
||||
idx = self.gui.library_view.currentIndex()
|
||||
if not idx.isValid():
|
||||
return
|
||||
db = idx.model().db
|
||||
row = idx.row()
|
||||
if type == 'series':
|
||||
series = idx.model().db.series(row)
|
||||
if series:
|
||||
search = ['series:"'+series+'"']
|
||||
elif type == 'publisher':
|
||||
publisher = idx.model().db.publisher(row)
|
||||
if publisher:
|
||||
search = ['publisher:"'+publisher+'"']
|
||||
elif type == 'tag':
|
||||
tags = idx.model().db.tags(row)
|
||||
if tags:
|
||||
search = ['tag:"='+t+'"' for t in tags.split(',')]
|
||||
elif type in ('author', 'authors'):
|
||||
authors = idx.model().db.authors(row)
|
||||
if authors:
|
||||
search = ['author:"='+a.strip().replace('|', ',')+'"' \
|
||||
for a in authors.split(',')]
|
||||
join = ' or '
|
||||
|
||||
# Get the parameters for this search
|
||||
col = db.prefs['similar_' + typ + '_search_key']
|
||||
match = db.prefs['similar_' + typ + '_match_kind']
|
||||
if match == 'match_all':
|
||||
join = ' and '
|
||||
else:
|
||||
join = ' or '
|
||||
|
||||
# Get all the data for the current record
|
||||
mi = db.get_metadata(row)
|
||||
|
||||
# Get the definitive field name to use for this search. If the field
|
||||
# is a grouped search term, the function returns the list of fields that
|
||||
# are to be searched, otherwise it returns the field name.
|
||||
loc = db.field_metadata.search_term_to_field_key(icu_lower(col))
|
||||
if isinstance(loc, list):
|
||||
# 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:
|
||||
self.gui.search.set_search_string(join.join(search),
|
||||
store_in_history=True)
|
||||
|
@ -25,11 +25,11 @@ class StoreAction(InterfaceAction):
|
||||
self.qaction.triggered.connect(self.do_search)
|
||||
self.store_menu = self.qaction.menu()
|
||||
cm = partial(self.create_menu_action, self.store_menu)
|
||||
for x, t in [('author', _('author')), ('title', _('title')),
|
||||
('book', _('book'))]:
|
||||
for x, t in [('author', _('this author')), ('title', _('this title')),
|
||||
('book', _('this book'))]:
|
||||
func = getattr(self, 'search_%s'%('author_title' if x == 'book'
|
||||
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)
|
||||
self.store_menu.addSeparator()
|
||||
self.store_list_menu = self.store_menu.addMenu(_('Stores'))
|
||||
|
@ -238,10 +238,11 @@ class LayoutMixin(object): # {{{
|
||||
# }}}
|
||||
|
||||
self.status_bar = StatusBar(self)
|
||||
stylename = unicode(self.style().objectName())
|
||||
for x in button_order:
|
||||
button = getattr(self, x+'_splitter').button
|
||||
button.setIconSize(QSize(24, 24))
|
||||
if isosx:
|
||||
if isosx and stylename != u'Calibre':
|
||||
button.setStyleSheet('''
|
||||
QToolButton { background: none; border:none; padding: 0px; }
|
||||
QToolButton:checked { background: rgba(0, 0, 0, 25%); }
|
||||
|
@ -183,10 +183,17 @@ Author matching is exact.</string>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Ignore files with the following extensions when automatically adding </string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_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>
|
||||
<widget class="QListWidget" name="opt_blocked_auto_formats">
|
||||
<property name="alternatingRowColors">
|
||||
|
@ -101,9 +101,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
|
||||
r('gui_layout', config, restart_required=True, choices=
|
||||
[(_('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'),
|
||||
'calibre')])
|
||||
r('tag_browser_old_look', gprefs, restart_required=True)
|
||||
|
||||
r('cover_flow_queue_length', config, restart_required=True)
|
||||
|
||||
|
@ -187,12 +187,12 @@
|
||||
<string>User interface &style (needs restart):</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_widget_style</cstring>
|
||||
<cstring>opt_ui_style</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="opt_widget_style"/>
|
||||
<widget class="QComboBox" name="opt_ui_style"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -312,6 +312,18 @@ Manage Authors. You can use the values {author} and
|
||||
<string>Tag Browser</string>
|
||||
</attribute>
|
||||
<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">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
@ -354,6 +366,19 @@ up into subcategories. If the partition method is set to disable, this value is
|
||||
</property>
|
||||
</widget>
|
||||
</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">
|
||||
<widget class="QLabel" name="label_8111">
|
||||
<property name="text">
|
||||
@ -396,27 +421,9 @@ a few top-level elements.</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" 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="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>
|
||||
<widget class="QCheckBox" name="opt_tag_browser_old_look">
|
||||
<property name="text">
|
||||
<string>Use &alternating row colors in the Tag Browser</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -5,6 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import textwrap
|
||||
|
||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting
|
||||
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('enforce_cpu_limit', config, restart_required=True)
|
||||
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.button_open_config_dir.clicked.connect(self.open_config_dir)
|
||||
self.user_defined_device_button.clicked.connect(self.user_defined_device)
|
||||
|
@ -12,6 +12,7 @@ from calibre.gui2.preferences import ConfigWidgetBase, test_widget, \
|
||||
from calibre.gui2.preferences.search_ui import Ui_Form
|
||||
from calibre.gui2 import config, error_dialog
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.utils.icu import sort_key
|
||||
|
||||
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 "
|
||||
"a particular item, or to have hierarchical categories (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()
|
||||
|
||||
fl = []
|
||||
@ -70,6 +71,18 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.gst_value.update_items_cache(fl)
|
||||
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_save_button.setEnabled(False)
|
||||
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.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):
|
||||
self.muc_changed = True
|
||||
|
||||
@ -121,6 +162,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.gst_changed = True
|
||||
self.gst[name] = val
|
||||
self.fill_gst_box(select=name)
|
||||
self.set_similar_fields(initial=False)
|
||||
self.changed_signal.emit()
|
||||
|
||||
def gst_delete_clicked(self):
|
||||
@ -133,9 +175,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.fill_gst_box(select='')
|
||||
self.changed_signal.emit()
|
||||
self.gst_changed = True
|
||||
self.set_similar_fields(initial=False)
|
||||
|
||||
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.gst_names.blockSignals(True)
|
||||
self.gst_names.clear()
|
||||
@ -168,6 +211,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
if self.gst_changed:
|
||||
self.db.prefs.set('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)
|
||||
|
||||
def refresh_gui(self, gui):
|
||||
|
@ -201,6 +201,101 @@ to be shown as user categories</string>
|
||||
</layout>
|
||||
</widget>
|
||||
</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><p>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.</p></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>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
@ -388,3 +388,14 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
self.do_search()
|
||||
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_()
|
||||
|
||||
|
@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import random
|
||||
import re
|
||||
import urllib2
|
||||
import urllib
|
||||
from contextlib import closing
|
||||
|
||||
from lxml import html
|
||||
@ -47,7 +47,7 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
|
||||
d.exec_()
|
||||
|
||||
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()
|
||||
|
||||
@ -66,15 +66,12 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
cover_url = ''.join(data.xpath('.//div[@class="img"]//img/@src'))
|
||||
|
||||
title = ''
|
||||
author = ''
|
||||
header_parts = data.xpath('.//div[@class="descr"]/h4//a//text()')
|
||||
if header_parts:
|
||||
title = header_parts[0]
|
||||
header_parts = header_parts[1:]
|
||||
if header_parts:
|
||||
author = ', '.join(header_parts)
|
||||
|
||||
title = ''.join(data.xpath(
|
||||
'descendant::span[@class="book-title"]/a/text()')).strip()
|
||||
author = ''.join(data.xpath(
|
||||
'descendant::span[@class="author"]/a/text()')).strip()
|
||||
if not title or not author:
|
||||
continue
|
||||
|
||||
counter -= 1
|
||||
|
||||
|
@ -45,7 +45,7 @@ class GutenbergStore(BasicStoreConfig, StorePlugin):
|
||||
counter = max_results
|
||||
with closing(br.open(url, timeout=timeout)) as f:
|
||||
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:
|
||||
break
|
||||
|
||||
|
84
src/calibre/gui2/store/stores/sony_plugin.py
Normal file
84
src/calibre/gui2/store/stores/sony_plugin.py
Normal 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
|
@ -40,39 +40,35 @@ class VirtualoStore(BasicStoreConfig, StorePlugin):
|
||||
url = 'http://virtualo.pl/?q=' + urllib.quote(query) + '&f=format_id:4,6,3'
|
||||
|
||||
br = browser()
|
||||
drm_pattern = re.compile("ADE")
|
||||
no_drm_pattern = re.compile("Znak wodny")
|
||||
|
||||
counter = max_results
|
||||
with closing(br.open(url, timeout=timeout)) as f:
|
||||
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:
|
||||
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:
|
||||
continue
|
||||
|
||||
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'))
|
||||
title = ''.join(data.xpath('.//div[@class="title"]/a/text()'))
|
||||
title = re.sub(r'\ WM', '', title)
|
||||
author = ', '.join(data.xpath('.//div[@class="authors"]/a/text()'))
|
||||
formats = ', '.join(data.xpath('.//span[@class="format"]/a/text()'))
|
||||
formats = re.sub(r'(, )?ONLINE(, )?', '', formats)
|
||||
drm = drm_pattern.search(formats)
|
||||
formats = re.sub(r'(, )?ADE(, )?', '', formats)
|
||||
formats = re.sub(r'\ WM', '', formats)
|
||||
cover_url = ''.join(data.xpath('.//div[@class="list_middle_left"]//a/img/@src'))
|
||||
title = ''.join(data.xpath('.//div[@class="list_title list_text_left"]/a/text()'))
|
||||
author = ', '.join(data.xpath('.//div[@class="list_authors list_text_left"]/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')]
|
||||
nodrm = no_drm_pattern.search(''.join(data.xpath('.//div[@style="width:45%;float:right;text-align:right;height:18px;"]/div/div/text()')))
|
||||
|
||||
counter -= 1
|
||||
|
||||
s = SearchResult()
|
||||
s.cover_url = cover_url.split('.jpg')[0] + '.jpg'
|
||||
s.title = title.strip() + ' ' + formats
|
||||
s.title = title.strip()
|
||||
s.author = author.strip()
|
||||
s.price = price + ' zł'
|
||||
s.detail_item = 'http://virtualo.pl' + id.strip().split('http://')[0]
|
||||
s.formats = formats.upper().strip()
|
||||
s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED
|
||||
s.formats = ', '.join(formats).upper()
|
||||
s.drm = SearchResult.DRM_UNLOCKED if nodrm else SearchResult.DRM_UNKNOWN
|
||||
|
||||
yield s
|
||||
|
@ -22,16 +22,26 @@ from calibre.utils.icu import sort_key
|
||||
|
||||
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):
|
||||
item = index.data(Qt.UserRole).toPyObject()
|
||||
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:
|
||||
return
|
||||
if (item.tag.state == 0 and config['show_avg_rating'] and
|
||||
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,
|
||||
option, widget)
|
||||
icon = option.icon
|
||||
@ -40,7 +50,12 @@ class TagDelegate(QStyledItemDelegate): # {{{
|
||||
nr = r.adjusted(0, 0, 0, 0)
|
||||
nr.setBottom(r.bottom()-int(r.height()*(rating/5.0)))
|
||||
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,
|
||||
painter, widget)
|
||||
painter.setOpacity(0.3)
|
||||
@ -48,6 +63,7 @@ class TagDelegate(QStyledItemDelegate): # {{{
|
||||
icon.On)
|
||||
painter.restore()
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
class TagsView(QTreeView): # {{{
|
||||
@ -101,13 +117,14 @@ class TagsView(QTreeView): # {{{
|
||||
self._model.user_categories_edited.connect(self.user_categories_edited,
|
||||
type=Qt.QueuedConnection)
|
||||
self._model.drag_drop_finished.connect(self.drag_drop_finished)
|
||||
self.setStyleSheet('''
|
||||
stylish_tb = '''
|
||||
QTreeView {
|
||||
background-color: palette(window);
|
||||
color: palette(text);
|
||||
color: palette(window-text);
|
||||
border: none;
|
||||
}
|
||||
|
||||
'''
|
||||
self.setStyleSheet('''
|
||||
QTreeView::item {
|
||||
border: 1px solid transparent;
|
||||
padding-top:0.9ex;
|
||||
@ -119,7 +136,9 @@ class TagsView(QTreeView): # {{{
|
||||
border: 1px solid #bfcde4;
|
||||
border-radius: 6px;
|
||||
}
|
||||
''')
|
||||
''' + ('' if gprefs['tag_browser_old_look'] else stylish_tb))
|
||||
if gprefs['tag_browser_old_look']:
|
||||
self.setAlternatingRowColors(True)
|
||||
|
||||
@property
|
||||
def hidden_categories(self):
|
||||
|
@ -532,6 +532,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
if self.content_server is not None:
|
||||
self.content_server.set_database(db)
|
||||
self.library_path = newloc
|
||||
prefs['library_path'] = self.library_path
|
||||
self.book_on_device(None, reset=True)
|
||||
db.set_book_on_device_func(self.book_on_device)
|
||||
self.library_view.set_database(db)
|
||||
@ -541,7 +542,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
self.search.clear()
|
||||
self.saved_search.clear()
|
||||
self.book_details.reset_info()
|
||||
prefs['library_path'] = self.library_path
|
||||
#self.library_view.model().count_changed()
|
||||
db = self.library_view.model().db
|
||||
self.iactions['Choose Library'].count_changed(db.count())
|
||||
|
@ -20,7 +20,7 @@ class TOCView(QTreeView):
|
||||
self.setStyleSheet('''
|
||||
QTreeView {
|
||||
background-color: palette(window);
|
||||
color: palette(text);
|
||||
color: palette(window-text);
|
||||
border: none;
|
||||
}
|
||||
QTreeView::item {
|
||||
|
@ -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 import fit_image
|
||||
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.dnd import (dnd_has_image, dnd_get_image, dnd_get_files,
|
||||
IMAGE_EXTENSIONS, dnd_has_extension, DownloadDialog)
|
||||
@ -1000,13 +1000,6 @@ class SplitterHandle(QSplitterHandle):
|
||||
if oh != self.highlight:
|
||||
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):
|
||||
self.double_clicked.emit(self)
|
||||
|
||||
|
@ -574,6 +574,9 @@ def command_set_metadata(args, dbpath):
|
||||
|
||||
if len(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)
|
||||
|
||||
if opts.field:
|
||||
|
@ -236,6 +236,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
defs['categories_using_hierarchy'] = []
|
||||
defs['column_color_rules'] = []
|
||||
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
|
||||
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
Loading…
x
Reference in New Issue
Block a user