Pull from trunk

This commit is contained in:
Kovid Goyal 2010-09-08 11:23:31 -06:00
commit 923cf9b95a
50 changed files with 1642 additions and 694 deletions

View File

@ -6,7 +6,7 @@ p.title {
text-align:center; text-align:center;
font-style:italic; font-style:italic;
font-size:xx-large; font-size:xx-large;
border-bottom: solid black 4px; border-bottom: solid black 2px;
} }
p.author { p.author {
@ -17,6 +17,15 @@ p.author {
font-size:large; font-size:large;
} }
p.author_index {
font-size:large;
font-weight:bold;
text-align:left;
margin-top:0px;
margin-bottom:-2px;
text-indent: 0em;
}
p.tags { p.tags {
margin-top:0em; margin-top:0em;
margin-bottom:0em; margin-bottom:0em;
@ -47,19 +56,12 @@ p.letter_index {
margin-bottom:0px; margin-bottom:0px;
} }
p.author_index {
font-size:large;
text-align:left;
margin-top:0px;
margin-bottom:0px;
text-indent: 0em;
}
p.series { p.series {
text-align: left; font-style:italic;
margin-top:0px; margin-top:2px;
margin-bottom:0px; margin-bottom:0px;
margin-left:2em; margin-left:2em;
text-align:left;
text-indent:-2em; text-indent:-2em;
} }
@ -87,11 +89,13 @@ p.date_read {
text-indent:-6em; text-indent:-6em;
} }
hr.series_divider { hr.description_divider {
width:50%; width:90%;
margin-left:1em; margin-left:5%;
margin-top:0em; border-top: solid white 0px;
margin-bottom:0em; border-right: solid white 0px;
border-bottom: solid black 1px;
border-left: solid white 0px;
} }
hr.annotations_divider { hr.annotations_divider {

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

View File

@ -0,0 +1,42 @@
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag
class AdvancedUserRecipe1282101454(BasicNewsRecipe):
title = 'BuckMasters In The Kitchen'
language = 'en'
__author__ = 'TonytheBookworm & Starson17'
description = 'Learn how to cook all those outdoor varments'
publisher = 'BuckMasters.com'
category = 'food,cooking,recipes'
oldest_article = 365
max_articles_per_feed = 100
conversion_options = {'linearize_tables' : True}
masthead_url = 'http://www.buckmasters.com/Portals/_default/Skins/BM_10/images/header_bg.jpg'
keep_only_tags = [
dict(name='table', attrs={'class':['containermaster_black']})
]
remove_tags_after = [dict(name='div', attrs={'align':['left']})]
feeds = [
('Recipes', 'http://www.buckmasters.com/DesktopModules/DnnForge%20-%20NewsArticles/RSS.aspx?TabID=292&ModuleID=658&MaxCount=25'),
]
def preprocess_html(self, soup):
item = soup.find('a', attrs={'class':['MenuTopSelected']})
if item:
item.parent.extract()
for img_tag in soup.findAll('img'):
parent_tag = img_tag.parent
if parent_tag.name == 'a':
new_tag = Tag(soup,'p')
new_tag.insert(0,img_tag)
parent_tag.replaceWith(new_tag)
elif parent_tag.name == 'p':
if not self.tag_to_string(parent_tag) == '':
new_div = Tag(soup,'div')
new_tag = Tag(soup,'p')
new_tag.insert(0,img_tag)
parent_tag.replaceWith(new_div)
new_div.insert(0,new_tag)
new_div.insert(1,parent_tag)
return soup

View File

@ -0,0 +1,64 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__author__ = 'somedayson & TonytheBookworm, revised by Cynthia Clavey'
__copyright__ = '2010, Cynthia Clavey cynvision@yahoo.com'
__version__ = '1.02'
__date__ = '05, september 2010'
__docformat__ = 'restructuredtext en'
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1283666183(BasicNewsRecipe):
title = u'Journal Gazette Ft. Wayne IN'
__author__ = 'cynvision'
oldest_article = 1
max_articles_per_feed = 8
no_stylesheets = True
remove_javascript = True
use_embedded_content = False
keep_only_tags = [dict(name='div', attrs={'id':'mainContent'})]
extra_css = '#copyinfo { font-size: 6 ;} \n #photocredit { font-size: 6 ;} \n .pubinfo { font-size: 6 ;}'
masthead_url = 'http://www.journalgazette.net/img/icons/jgmini.gif'
# cover_url = 'http://www.journalgazette.net/img/icons/jgmini.gif'
encoding = 'cp1252'
feeds = [(u'Opinion', u'http://journalgazette.net/apps/pbcs.dll/section?Category=EDIT&template=blogrss&mime=xml'),
(u'Local News',u'http://journalgazette.net/apps/pbcs.dll/section?Category=LOCAL&template=blogrss&mime=xml') ,
(u'Sports',u'http://journalgazette.net/apps/pbcs.dll/section?Category=SPORTS&template=blogrss&mime=xml' ),
(u'Features',u'http://journalgazette.net/apps/pbcs.dll/section?Category=FEAT&template=blogrss&mime=xml'),
(u'Business',u'http://journalgazette.net/apps/pbcs.dll/section?Category=BIZ&template=blogrss&mime=xml'),
(u'Ice Chips',u'http://journalgazette.net/apps/pbcs.dll/section?Category=BLOGS11&template=blogrss&mime=xml '),
(u'Entertainment',u'http://journalgazette.net/apps/pbcs.dll/section?Category=ENT&template=blogrss&mime=xml'),
(u'Food',u'http://journalgazette.net/apps/pbcs.dll/section?Category=FOOD&template=blogrss&mime=xml')
]
def print_version(self, url):
split1 = url.split("/")
#print 'THE SPLIT IS: ', split1
#url1 = split1[0]
#url2 = split1[1]
url3 = split1[2]
#url4 = split1[3]
url5 = split1[4]
url6 = split1[5]
url7 = split1[6]
#url8 = split1[7]
#need to convert to print_version
#originalversion is : http://www.journalgazette.net/article/20100905/EDIT10/309059959/1021/EDIT
#printversion should be: http://www.journalgazette.net/apps/pbcs.dll/article?AID=/20100905/EDIT10/309059959/-1/EDIT01&template=printart
#results of the split
#THE SPLIT IS: [u'http:', u'', u'www.journalgazette.net', u'article', u'20100905', u'EDIT10', u'309059959', u'1021', u'EDIT']
print_url = 'http://' + url3 + '/apps/pbcs.dll/article?AID=/' + url5 + '/' + url6 + '/' + url7 + '/-1/EDIT01&template=printart'
#print 'THIS URL WILL PRINT: ', print_url # this is a test string to see what the url is it will return
return print_url
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -1,15 +1,16 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>, Rogelio Domínguez <rogelio.dominguez@gmail.com>'
''' '''
www.jornada.unam.mx www.jornada.unam.mx
''' '''
import re
from calibre import strftime from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class LaJornada_mx(BasicNewsRecipe): class LaJornada_mx(BasicNewsRecipe):
title = 'La Jornada (Mexico)' title = 'La Jornada (Mexico)'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic/Rogelio Domínguez'
description = 'Noticias del diario mexicano La Jornada' description = 'Noticias del diario mexicano La Jornada'
publisher = 'DEMOS, Desarrollo de Medios, S.A. de C.V.' publisher = 'DEMOS, Desarrollo de Medios, S.A. de C.V.'
category = 'news, Mexico' category = 'news, Mexico'
@ -20,12 +21,26 @@ class LaJornada_mx(BasicNewsRecipe):
use_embedded_content = False use_embedded_content = False
language = 'es' language = 'es'
remove_empty_feeds = True remove_empty_feeds = True
cover_url = strftime("http://www.jornada.unam.mx/%Y/%m/%d/planitas/portadita.jpg") cover_url = strftime("http://www.jornada.unam.mx/%Y/%m/%d/portada.pdf")
masthead_url = 'http://www.jornada.unam.mx/v7.0/imagenes/la-jornada-trans.png' masthead_url = 'http://www.jornada.unam.mx/v7.0/imagenes/la-jornada-trans.png'
publication_type = 'newspaper'
extra_css = """ extra_css = """
body{font-family: "Times New Roman",serif } body{font-family: "Times New Roman",serif }
.cabeza{font-size: xx-large; font-weight: bold } .cabeza{font-size: xx-large; font-weight: bold }
.credito-articulo{font-size: 1.3em} .documentFirstHeading{font-size: xx-large; font-weight: bold }
.credito-articulo{font-variant: small-caps; font-weight: bold }
.foto{text-align: center}
.pie-foto{font-size: 0.9em}
.credito{font-weight: bold; margin-left: 1em}
.credito-autor{font-variant: small-caps; font-weight: bold }
.credito-titulo{text-align: right}
.hemero{text-align: right; font-size: 0.9em; margin-bottom: 0.5em }
.loc{font-weight: bold}
.carton{text-align: center}
.credit{font-weight: bold}
.text{margin-top: 1.4em}
p.inicial{display: inline; font-size: xx-large; font-weight: bold}
p.s-s{display: inline; text-indent: 0}
""" """
conversion_options = { conversion_options = {
@ -35,15 +50,21 @@ class LaJornada_mx(BasicNewsRecipe):
, 'language' : language , 'language' : language
} }
preprocess_regexps = [
(re.compile( r'<div class="inicial">(.*)</div><p class="s-s">'
,re.DOTALL|re.IGNORECASE)
,lambda match: '<p class="inicial">' + match.group(1) + '</p><p class="s-s">')
]
keep_only_tags = [ keep_only_tags = [
dict(name='div', attrs={'class':['documentContent','cabeza','sumarios','text']}) dict(name='div', attrs={'class':['documentContent','cabeza','sumarios','credito-articulo','text','carton']})
,dict(name='div', attrs={'id':'renderComments'}) ,dict(name='div', attrs={'id':'renderComments'})
] ]
remove_tags = [dict(name='div', attrs={'class':'buttonbar'})] remove_tags = [dict(name='div', attrs={'class':['buttonbar','comment-cont']})]
feeds = [ feeds = [
(u'Ultimas noticias' , u'http://www.jornada.unam.mx/ultimas/news/RSS' ) (u'Opinion' , u'http://www.jornada.unam.mx/rss/opinion.xml' )
,(u'Opinion' , u'http://www.jornada.unam.mx/rss/opinion.xml' ) ,(u'Cartones' , u'http://www.jornada.unam.mx/rss/cartones.xml' )
,(u'Politica' , u'http://www.jornada.unam.mx/rss/politica.xml' ) ,(u'Politica' , u'http://www.jornada.unam.mx/rss/politica.xml' )
,(u'Economia' , u'http://www.jornada.unam.mx/rss/economia.xml' ) ,(u'Economia' , u'http://www.jornada.unam.mx/rss/economia.xml' )
,(u'Mundo' , u'http://www.jornada.unam.mx/rss/mundo.xml' ) ,(u'Mundo' , u'http://www.jornada.unam.mx/rss/mundo.xml' )
@ -55,6 +76,7 @@ class LaJornada_mx(BasicNewsRecipe):
,(u'Gastronomia' , u'http://www.jornada.unam.mx/rss/gastronomia.xml' ) ,(u'Gastronomia' , u'http://www.jornada.unam.mx/rss/gastronomia.xml' )
,(u'Espectaculos' , u'http://www.jornada.unam.mx/rss/espectaculos.xml' ) ,(u'Espectaculos' , u'http://www.jornada.unam.mx/rss/espectaculos.xml' )
,(u'Deportes' , u'http://www.jornada.unam.mx/rss/deportes.xml' ) ,(u'Deportes' , u'http://www.jornada.unam.mx/rss/deportes.xml' )
,(u'Ultimas noticias' , u'http://www.jornada.unam.mx/ultimas/news/RSS' )
] ]
def preprocess_html(self, soup): def preprocess_html(self, soup):
@ -62,3 +84,7 @@ class LaJornada_mx(BasicNewsRecipe):
del item['style'] del item['style']
return soup return soup
def get_article_url(self, article):
rurl = article.get('link', None)
return rurl.rpartition('&partner=')[0]

View File

@ -22,10 +22,19 @@ class NrcNextRecipe(BasicNewsRecipe):
remove_tags = [] remove_tags = []
remove_tags.append(dict(name = 'div', attrs = {'class' : 'meta'})) remove_tags.append(dict(name = 'div', attrs = {'class' : 'meta'}))
remove_tags.append(dict(name = 'p', attrs = {'class' : 'meta'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'datumlabel'})) remove_tags.append(dict(name = 'div', attrs = {'class' : 'datumlabel'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'sharing-is-caring'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'navigation'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'reageer'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'comment odd alt thread-odd thread-alt depth-1 reactie '}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'comment even thread-even depth-1 reactie '}))
remove_tags.append(dict(name = 'ul', attrs = {'class' : 'cats single'})) remove_tags.append(dict(name = 'ul', attrs = {'class' : 'cats single'}))
remove_tags.append(dict(name = 'ul', attrs = {'class' : 'cats onderwerpen'})) remove_tags.append(dict(name = 'ul', attrs = {'class' : 'cats onderwerpen'}))
remove_tags.append(dict(name = 'ul', attrs = {'class' : 'cats rubrieken'})) remove_tags.append(dict(name = 'ul', attrs = {'class' : 'cats rubrieken'}))
remove_tags.append(dict(name = 'h3', attrs = {'class' : 'reacties'}))
extra_css = ''' extra_css = '''
body {font-family: verdana, arial, helvetica, geneva, sans-serif; text-align: left;} body {font-family: verdana, arial, helvetica, geneva, sans-serif; text-align: left;}
@ -41,20 +50,18 @@ class NrcNextRecipe(BasicNewsRecipe):
feeds[u'koken'] = u'http://www.nrcnext.nl/koken/' feeds[u'koken'] = u'http://www.nrcnext.nl/koken/'
feeds[u'geld & werk'] = u'http://www.nrcnext.nl/geld-en-werk/' feeds[u'geld & werk'] = u'http://www.nrcnext.nl/geld-en-werk/'
feeds[u'vandaag'] = u'http://www.nrcnext.nl' feeds[u'vandaag'] = u'http://www.nrcnext.nl'
feeds[u'city life in afrika'] = u'http://www.nrcnext.nl/city-life-in-afrika/' # feeds[u'city life in afrika'] = u'http://www.nrcnext.nl/city-life-in-afrika/'
answer = [] answer = []
articles = {} articles = {}
indices = [] indices = []
for index, feed in feeds.items() : for index, feed in feeds.items() :
soup = self.index_to_soup(feed) soup = self.index_to_soup(feed)
for post in soup.findAll(True, attrs={'class' : 'post '}) :
for post in soup.findAll(True, attrs={'class' : 'post'}) :
# Find the links to the actual articles and rember the location they're pointing to and the title # Find the links to the actual articles and rember the location they're pointing to and the title
a = post.find('a', attrs={'rel' : 'bookmark'}) a = post.find('a', attrs={'rel' : 'bookmark'})
href = a['href'] href = a['href']
title = self.tag_to_string(a) title = self.tag_to_string(a)
if index == 'columnisten' : if index == 'columnisten' :
# In this feed/page articles can be written by more than one author. # In this feed/page articles can be written by more than one author.
# It is nice to see their names in the titles. # It is nice to see their names in the titles.
@ -74,7 +81,8 @@ class NrcNextRecipe(BasicNewsRecipe):
indices.append(index) indices.append(index)
# Now, sort the temporary list of feeds in the order they appear on the website # Now, sort the temporary list of feeds in the order they appear on the website
indices = self.sort_index_by(indices, {u'columnisten' : 1, u'koken' : 3, u'geld & werk' : 2, u'vandaag' : 0, u'city life in afrika' : 4}) # indices = self.sort_index_by(indices, {u'columnisten' : 1, u'koken' : 3, u'geld & werk' : 2, u'vandaag' : 0, u'city life in afrika' : 4})
indices = self.sort_index_by(indices, {u'columnisten' : 1, u'koken' : 3, u'geld & werk' : 2, u'vandaag' : 0})
# Apply this sort order to the actual list of feeds and articles # Apply this sort order to the actual list of feeds and articles
answer = [(key, articles[key]) for key in indices if articles.has_key(key)] answer = [(key, articles[key]) for key in indices if articles.has_key(key)]

View File

@ -26,13 +26,13 @@ class NYTimes(BasicNewsRecipe):
#TO LOGIN #TO LOGIN
def get_browser(self): def get_browser(self):
br = BasicNewsRecipe.get_browser() br = BasicNewsRecipe.get_browser()
br.open('http://content.nejm.org/cgi/login?uri=/') br.open('http://www.nejm.org/action/showLogin?uri=http://www.nejm.org/')
br.select_form(nr=0) br.select_form(name='frmLogin')
br['username'] = self.username br['login'] = self.username
br['code'] = self.password br['password'] = self.password
response = br.submit() response = br.submit()
raw = response.read() raw = response.read()
if '<strong>Welcome' not in raw: if '>Sign Out<' not in raw:
raise Exception('Login failed. Check your username and password') raise Exception('Login failed. Check your username and password')
return br return br

View File

@ -0,0 +1,46 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1282101454(BasicNewsRecipe):
title = 'The Walrus Mag'
language = 'en'
__author__ = 'TonytheBookworm'
description = 'national general interest magazine about Canada and its place in the world'
publisher = 'Tony Stegall'
category = 'Canada, news'
oldest_article = 365
max_articles_per_feed = 100
masthead_url = 'http://www.walrusmagazine.com/images/wordmark.png'
keep_only_tags = [
dict(name='h1'),
dict(name='div', attrs={'id':['prbody']})
# ,dict(attrs={'id':['cxArticleText','cxArticleBodyText']})
]
feeds = [
('Walrus Magazine', 'http://feeds.feedburner.com/WalrusFeatureArticles?format=xml'),
]
def print_version(self, url):
split1 = url.split("/articles/")
#print 'THE SPLIT IS: ', split1
url1 = split1[0]
#print 'url1 is: ',url1
url2 = split1[1]
#print 'url2 is: ',url2
#need to convert to print_version
#originalversion is : http://www.walrusmagazine.com/articles/2010.09-frontier-no-one-can-hear-you-scream/
#printversion should be: http://www.walrusmagazine.com/print/2010.09-frontier-no-one-can-hear-you-scream/
print_url = url1 + '/print/' + url2
#print 'THIS URL WILL PRINT: ', print_url # this is a test string to see what the url is it will return
return print_url

View File

@ -0,0 +1,153 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
from calibre.web.feeds.news import BasicNewsRecipe
import copy
class WallStreetJournal(BasicNewsRecipe):
title = 'Wall Street Journal (free)'
__author__ = 'Kovid Goyal, Sujata Raman, Joshua Oster-Morris, Starson17'
description = 'News and current affairs'
language = 'en'
cover_url = 'http://dealbreaker.com/images/thumbs/Wall%20Street%20Journal%20A1.JPG'
max_articles_per_feed = 1000
timefmt = ' [%a, %b %d, %Y]'
no_stylesheets = True
extra_css = '''h1{color:#093D72 ; font-size:large ; font-family:Georgia,"Century Schoolbook","Times New Roman",Times,serif; }
h2{color:#474537; font-family:Georgia,"Century Schoolbook","Times New Roman",Times,serif; font-size:small; font-style:italic;}
.subhead{color:gray; font-family:Georgia,"Century Schoolbook","Times New Roman",Times,serif; font-size:small; font-style:italic;}
.insettipUnit {color:#666666; font-family:Arial,Sans-serif;font-size:xx-small }
.targetCaption{ font-size:x-small; color:#333333; font-family:Arial,Helvetica,sans-serif}
.article{font-family :Arial,Helvetica,sans-serif; font-size:x-small}
.tagline {color:#333333; font-size:xx-small}
.dateStamp {color:#666666; font-family:Arial,Helvetica,sans-serif}
h3{color:blue ;font-family:Arial,Helvetica,sans-serif; font-size:xx-small}
.byline{color:blue;font-family:Arial,Helvetica,sans-serif; font-size:xx-small}
h6{color:#333333; font-family:Georgia,"Century Schoolbook","Times New Roman",Times,serif; font-size:small;font-style:italic; }
.paperLocation{color:#666666; font-size:xx-small}'''
remove_tags_before = dict(name='h1')
remove_tags = [
dict(id=["articleTabs_tab_article", "articleTabs_tab_comments", "articleTabs_tab_interactive","articleTabs_tab_video","articleTabs_tab_map","articleTabs_tab_slideshow"]),
{'class':['footer_columns','network','insetCol3wide','interactive','video','slideshow','map','insettip','insetClose','more_in', "insetContent", 'articleTools_bottom', 'aTools', "tooltip", "adSummary", "nav-inline"]},
dict(name='div', attrs={'data-flash-settings':True}),
{'class':['insetContent embedType-interactive insetCol3wide','insetCol6wide','insettipUnit']},
dict(rel='shortcut icon'),
]
remove_tags_after = [dict(id="article_story_body"), {'class':"article story"},]
def postprocess_html(self, soup, first):
for tag in soup.findAll(name=['table', 'tr', 'td']):
tag.name = 'div'
for tag in soup.findAll('div', dict(id=["articleThumbnail_1", "articleThumbnail_2", "articleThumbnail_3", "articleThumbnail_4", "articleThumbnail_5", "articleThumbnail_6", "articleThumbnail_7"])):
tag.extract()
return soup
def wsj_get_index(self):
return self.index_to_soup('http://online.wsj.com/itp')
def wsj_add_feed(self,feeds,title,url):
self.log('Found section:', title)
if url.endswith('whatsnews'):
articles = self.wsj_find_wn_articles(url)
else:
articles = self.wsj_find_articles(url)
if articles:
feeds.append((title, articles))
return feeds
def parse_index(self):
soup = self.wsj_get_index()
date = soup.find('span', attrs={'class':'date-date'})
if date is not None:
self.timefmt = ' [%s]'%self.tag_to_string(date)
feeds = []
div = soup.find('div', attrs={'class':'itpHeader'})
div = div.find('ul', attrs={'class':'tab'})
for a in div.findAll('a', href=lambda x: x and '/itp/' in x):
pageone = a['href'].endswith('pageone')
if pageone:
title = 'Front Section'
url = 'http://online.wsj.com' + a['href']
feeds = self.wsj_add_feed(feeds,title,url)
title = 'What''s News'
url = url.replace('pageone','whatsnews')
feeds = self.wsj_add_feed(feeds,title,url)
else:
title = self.tag_to_string(a)
url = 'http://online.wsj.com' + a['href']
feeds = self.wsj_add_feed(feeds,title,url)
return feeds
def wsj_find_wn_articles(self, url):
soup = self.index_to_soup(url)
articles = []
whats_news = soup.find('div', attrs={'class':lambda x: x and 'whatsNews-simple' in x})
if whats_news is not None:
for a in whats_news.findAll('a', href=lambda x: x and '/article/' in x):
container = a.findParent(['p'])
meta = a.find(attrs={'class':'meta_sectionName'})
if meta is not None:
meta.extract()
title = self.tag_to_string(a).strip()
url = a['href']
desc = ''
if container is not None:
desc = self.tag_to_string(container)
articles.append({'title':title, 'url':url,
'description':desc, 'date':''})
self.log('\tFound WN article:', title)
return articles
def wsj_find_articles(self, url):
soup = self.index_to_soup(url)
whats_news = soup.find('div', attrs={'class':lambda x: x and 'whatsNews-simple' in x})
if whats_news is not None:
whats_news.extract()
articles = []
flavorarea = soup.find('div', attrs={'class':lambda x: x and 'ahed' in x})
if flavorarea is not None:
flavorstory = flavorarea.find('a', href=lambda x: x and x.startswith('/article'))
if flavorstory is not None:
flavorstory['class'] = 'mjLinkItem'
metapage = soup.find('span', attrs={'class':lambda x: x and 'meta_sectionName' in x})
if metapage is not None:
flavorstory.append( copy.copy(metapage) ) #metapage should always be A1 because that should be first on the page
for a in soup.findAll('a', attrs={'class':'mjLinkItem'}, href=True):
container = a.findParent(['li', 'div'])
meta = a.find(attrs={'class':'meta_sectionName'})
if meta is not None:
meta.extract()
title = self.tag_to_string(a).strip() + ' [%s]'%self.tag_to_string(meta)
url = 'http://online.wsj.com'+a['href']
desc = ''
p = container.find('p')
if p is not None:
desc = self.tag_to_string(p)
articles.append({'title':title, 'url':url,
'description':desc, 'date':''})
self.log('\tFound article:', title)
return articles
def cleanup(self):
self.browser.open('http://online.wsj.com/logout?url=http://online.wsj.com')

View File

@ -294,7 +294,7 @@ class CatalogPlugin(Plugin): # {{{
# Return a list of requested fields, with opts.sort_by first # Return a list of requested fields, with opts.sort_by first
all_fields = set( all_fields = set(
['author_sort','authors','comments','cover','formats', ['author_sort','authors','comments','cover','formats',
'id','isbn','pubdate','publisher','rating', 'id','isbn','ondevice','pubdate','publisher','rating',
'series_index','series','size','tags','timestamp', 'series_index','series','size','tags','timestamp',
'title','uuid']) 'title','uuid'])
@ -306,6 +306,9 @@ class CatalogPlugin(Plugin): # {{{
else: else:
fields = list(all_fields) fields = list(all_fields)
if not opts.connected_device['is_device_connected'] and 'ondevice' in fields:
fields.pop(int(fields.index('ondevice')))
fields.sort() fields.sort()
if opts.sort_by and opts.sort_by in fields: if opts.sort_by and opts.sort_by in fields:
fields.insert(0,fields.pop(int(fields.index(opts.sort_by)))) fields.insert(0,fields.pop(int(fields.index(opts.sort_by))))
@ -371,6 +374,13 @@ class InterfaceActionBase(Plugin): # {{{
class PreferencesPlugin(Plugin): # {{{ class PreferencesPlugin(Plugin): # {{{
'''
A plugin representing a widget displayed in the Preferences dialog.
This plugin has only one important method :meth:`create_widget`. The
various fields of the plugin control how it is categorized in the UI.
'''
supported_platforms = ['windows', 'osx', 'linux'] supported_platforms = ['windows', 'osx', 'linux']
author = 'Kovid Goyal' author = 'Kovid Goyal'
type = _('Preferences') type = _('Preferences')
@ -406,7 +416,8 @@ class PreferencesPlugin(Plugin): # {{{
def create_widget(self, parent=None): def create_widget(self, parent=None):
''' '''
Create and return the actual Qt widget used for setting this group of Create and return the actual Qt widget used for setting this group of
preferences. The widget must implement the ConfigWidgetInterface. preferences. The widget must implement the
:class:`calibre.gui2.preferences.ConfigWidgetInterface`.
The default implementation uses :attr:`config_widget` to instantiate The default implementation uses :attr:`config_widget` to instantiate
the widget. the widget.

View File

@ -461,7 +461,7 @@ from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK
from calibre.devices.edge.driver import EDGE from calibre.devices.edge.driver import EDGE
from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS
from calibre.devices.sne.driver import SNE from calibre.devices.sne.driver import SNE
from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, GEMEI
from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG
from calibre.devices.kobo.driver import KOBO from calibre.devices.kobo.driver import KOBO
@ -570,6 +570,7 @@ plugins += [
KOGAN, KOGAN,
PDNOVEL, PDNOVEL,
SPECTRA, SPECTRA,
GEMEI,
ITUNES, ITUNES,
] ]
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ plugins += [x for x in list(locals().values()) if isinstance(x, type) and \

View File

@ -82,7 +82,7 @@ class ITUNES(DriverBase):
''' '''
name = 'Apple device interface' name = 'Apple device interface'
gui_name = 'Apple device' gui_name = _('Apple device')
icon = I('devices/ipad.png') icon = I('devices/ipad.png')
description = _('Communicate with iTunes/iBooks.') description = _('Communicate with iTunes/iBooks.')
supported_platforms = ['osx','windows'] supported_platforms = ['osx','windows']
@ -2303,9 +2303,9 @@ class ITUNES(DriverBase):
# Delete existing from Device|Books, add to self.update_list # Delete existing from Device|Books, add to self.update_list
# for deletion from booklist[0] during add_books_to_metadata # for deletion from booklist[0] during add_books_to_metadata
for book in self.cached_books: for book in self.cached_books:
if self.cached_books[book]['uuid'] == metadata.uuid and \ if self.cached_books[book]['uuid'] == metadata.uuid or \
self.cached_books[book]['title'] == metadata.title and \ (self.cached_books[book]['title'] == metadata.title and \
self.cached_books[book]['author'] == metadata.authors[0]: self.cached_books[book]['author'] == metadata.authors[0]):
self.update_list.append(self.cached_books[book]) self.update_list.append(self.cached_books[book])
self._remove_from_device(self.cached_books[book]) self._remove_from_device(self.cached_books[book])
if DEBUG: if DEBUG:
@ -2322,9 +2322,9 @@ class ITUNES(DriverBase):
# Delete existing from Library|Books, add to self.update_list # Delete existing from Library|Books, add to self.update_list
# for deletion from booklist[0] during add_books_to_metadata # for deletion from booklist[0] during add_books_to_metadata
for book in self.cached_books: for book in self.cached_books:
if self.cached_books[book]['uuid'] == metadata.uuid and \ if self.cached_books[book]['uuid'] == metadata.uuid or \
self.cached_books[book]['title'] == metadata.title and \ (self.cached_books[book]['title'] == metadata.title and \
self.cached_books[book]['author'] == metadata.authors[0]: self.cached_books[book]['author'] == metadata.authors[0]):
self.update_list.append(self.cached_books[book]) self.update_list.append(self.cached_books[book])
self._remove_from_iTunes(self.cached_books[book]) self._remove_from_iTunes(self.cached_books[book])
if DEBUG: if DEBUG:
@ -2488,7 +2488,7 @@ class ITUNES(DriverBase):
zf_opf.close() zf_opf.close()
# If 'News' in tags, tweak the title/author for friendlier display in iBooks # If 'News' in tags, tweak the title/author for friendlier display in iBooks
if _('News') in metadata.tags: if _('News') or _('Catalog') in metadata.tags:
if metadata.title.find('[') > 0: if metadata.title.find('[') > 0:
metadata.title = metadata.title[:metadata.title.find('[')-1] metadata.title = metadata.title[:metadata.title.find('[')-1]
date_as_author = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) date_as_author = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y'))

View File

@ -108,4 +108,23 @@ class PDNOVEL(USBMS):
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
coverfile.write(coverdata[2]) coverfile.write(coverdata[2])
class GEMEI(USBMS):
name = 'Gemei Device Interface'
gui_name = 'GM2000'
description = _('Communicate with the GM2000')
author = 'Kovid Goyal'
supported_platforms = ['windows', 'osx', 'linux']
# Ordered list of supported formats
FORMATS = ['epub', 'chm', 'html', 'pdb', 'pdf', 'txt']
VENDOR_ID = [0x07c4]
PRODUCT_ID = [0xa4a5]
BCD = None
VENDOR_NAME = 'CHINA'
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'CHIP'
EBOOK_DIR_MAIN = 'eBooks'
SUPPORTS_SUB_DIRS = True

View File

@ -26,14 +26,18 @@ class GenerateCatalogAction(InterfaceAction):
rows = xrange(self.gui.library_view.model().rowCount(QModelIndex())) rows = xrange(self.gui.library_view.model().rowCount(QModelIndex()))
ids = map(self.gui.library_view.model().id, rows) ids = map(self.gui.library_view.model().id, rows)
dbspec = None
if not ids: if not ids:
return error_dialog(self.gui, _('No books selected'), return error_dialog(self.gui, _('No books selected'),
_('No books selected to generate catalog for'), _('No books selected to generate catalog for'),
show=True) show=True)
db = self.gui.library_view.model().db
dbspec = {}
for id in ids:
dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)}
# Calling gui2.tools:generate_catalog() # Calling gui2.tools:generate_catalog()
ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager.device) ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager)
if ret is None: if ret is None:
return return

View File

@ -177,7 +177,16 @@ class ChooseLibraryAction(InterfaceAction):
return error_dialog(self.gui, _('Already exists'), return error_dialog(self.gui, _('Already exists'),
_('The folder %s already exists. Delete it first.') % _('The folder %s already exists. Delete it first.') %
newloc, show=True) newloc, show=True)
os.rename(loc, newloc) try:
os.rename(loc, newloc)
except:
import traceback
error_dialog(self.gui, _('Rename failed'),
_('Failed to rename the library at %s. '
'The most common cause for this is if one of the files'
' in the library is open in another program.') % loc,
det_msg=traceback.format_exc(), show=True)
return
self.stats.rename(location, newloc) self.stats.rename(location, newloc)
self.build_menus() self.build_menus()

View File

@ -159,7 +159,7 @@ class DeleteAction(InterfaceAction):
if self.gui.stack.currentIndex() == 0: if self.gui.stack.currentIndex() == 0:
if not confirm('<p>'+_('The selected books will be ' if not confirm('<p>'+_('The selected books will be '
'<b>permanently deleted</b> and the files ' '<b>permanently deleted</b> and the files '
'removed from your computer. Are you sure?') 'removed from your calibre library. Are you sure?')
+'</p>', 'library_delete_books', self.gui): +'</p>', 'library_delete_books', self.gui):
return return
ci = view.currentIndex() ci = view.currentIndex()

View File

@ -51,7 +51,8 @@ class EditMetadataAction(InterfaceAction):
self.merge_books) self.merge_books)
mb.addSeparator() mb.addSeparator()
mb.addAction(_('Merge into first selected book - keep others'), mb.addAction(_('Merge into first selected book - keep others'),
partial(self.merge_books, safe_merge=True)) partial(self.merge_books, safe_merge=True),
Qt.AltModifier+Qt.Key_M)
self.merge_menu = mb self.merge_menu = mb
self.action_merge.setMenu(mb) self.action_merge.setMenu(mb)
md.addSeparator() md.addSeparator()

View File

@ -42,15 +42,4 @@ class PreferencesAction(InterfaceAction):
d = Preferences(self.gui, initial_plugin=initial_plugin) d = Preferences(self.gui, initial_plugin=initial_plugin)
d.show() d.show()
if d.committed:
self.gui.must_restart_before_config = d.must_restart
self.gui.tags_view.set_new_model() # in case columns changed
self.gui.tags_view.recount()
self.gui.create_device_menu()
self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
self.gui.tool_bar.build_bar()
self.gui.build_context_menus()
self.gui.tool_bar.apply_settings()

View File

@ -19,6 +19,7 @@ class PluginWidget(QWidget,Ui_Form):
OPTION_FIELDS = [('exclude_genre','\[.+\]'), OPTION_FIELDS = [('exclude_genre','\[.+\]'),
('exclude_tags','~,'+_('Catalog')), ('exclude_tags','~,'+_('Catalog')),
('generate_titles', True), ('generate_titles', True),
('generate_series', True),
('generate_recently_added', True), ('generate_recently_added', True),
('note_tag','*'), ('note_tag','*'),
('numbers_as_text', False), ('numbers_as_text', False),
@ -40,7 +41,7 @@ class PluginWidget(QWidget,Ui_Form):
# Update dialog fields from stored options # Update dialog fields from stored options
for opt in self.OPTION_FIELDS: for opt in self.OPTION_FIELDS:
opt_value = gprefs.get(self.name + '_' + opt[0], opt[1]) opt_value = gprefs.get(self.name + '_' + opt[0], opt[1])
if opt[0] in ['numbers_as_text','generate_titles','generate_recently_added']: if opt[0] in ['numbers_as_text','generate_titles','generate_series','generate_recently_added']:
getattr(self, opt[0]).setChecked(opt_value) getattr(self, opt[0]).setChecked(opt_value)
else: else:
getattr(self, opt[0]).setText(opt_value) getattr(self, opt[0]).setText(opt_value)
@ -52,13 +53,13 @@ class PluginWidget(QWidget,Ui_Form):
# others store as lists # others store as lists
opts_dict = {} opts_dict = {}
for opt in self.OPTION_FIELDS: for opt in self.OPTION_FIELDS:
if opt[0] in ['numbers_as_text','generate_titles','generate_recently_added']: if opt[0] in ['numbers_as_text','generate_titles','generate_series','generate_recently_added']:
opt_value = getattr(self,opt[0]).isChecked() opt_value = getattr(self,opt[0]).isChecked()
else: else:
opt_value = unicode(getattr(self, opt[0]).text()) opt_value = unicode(getattr(self, opt[0]).text())
gprefs.set(self.name + '_' + opt[0], opt_value) gprefs.set(self.name + '_' + opt[0], opt_value)
if opt[0] in ['exclude_genre','numbers_as_text','generate_titles','generate_recently_added']: if opt[0] in ['exclude_genre','numbers_as_text','generate_titles','generate_series','generate_recently_added']:
opts_dict[opt[0]] = opt_value opts_dict[opt[0]] = opt_value
else: else:
opts_dict[opt[0]] = opt_value.split(',') opts_dict[opt[0]] = opt_value.split(',')

View File

@ -108,20 +108,27 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="0"> <item row="10" column="0">
<widget class="QCheckBox" name="generate_recently_added"> <widget class="QCheckBox" name="generate_recently_added">
<property name="text"> <property name="text">
<string>Include 'Recently Added' Section</string> <string>Include 'Recently Added' Section</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="10" column="0"> <item row="11" column="0">
<widget class="QCheckBox" name="numbers_as_text"> <widget class="QCheckBox" name="numbers_as_text">
<property name="text"> <property name="text">
<string>Sort numbers as text</string> <string>Sort numbers as text</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="0">
<widget class="QCheckBox" name="generate_series">
<property name="text">
<string>Include 'Series' Section</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View File

@ -58,7 +58,8 @@ class BulkConfig(Config):
output_path = 'dummy.'+output_format output_path = 'dummy.'+output_format
log = Log() log = Log()
log.outputs = [] log.outputs = []
self.plumber = Plumber(input_path, output_path, log) self.plumber = Plumber(input_path, output_path, log,
merge_plugin_recs=False)
def widget_factory(cls): def widget_factory(cls):
return cls(self.stack, self.plumber.get_option_by_name, return cls(self.stack, self.plumber.get_option_by_name,

View File

@ -27,13 +27,9 @@ def gui_catalog(fmt, title, dbspec, ids, out_file_name, sync, fmt_options, conne
notification=DummyReporter(), log=None): notification=DummyReporter(), log=None):
if log is None: if log is None:
log = Log() log = Log()
if dbspec is None: from calibre.library import db
from calibre.utils.config import prefs db = db()
from calibre.library.database2 import LibraryDatabase2 db.catalog_plugin_on_device_temp_mapping = dbspec
dbpath = prefs['library_path']
db = LibraryDatabase2(dbpath)
else: # To be implemented in the future
pass
# Create a minimal OptionParser that we can append to # Create a minimal OptionParser that we can append to
parser = OptionParser() parser = OptionParser()

View File

@ -1306,16 +1306,26 @@ class DeviceMixin(object): # {{{
self.library_view.model().refresh_ids(list(changed)) self.library_view.model().refresh_ids(list(changed))
def book_on_device(self, id, format=None, reset=False): def book_on_device(self, id, format=None, reset=False):
loc = [None, None, None] '''
Return an indication of whether the given book represented by its db id
is on the currently connected device. It returns a 4 element list. The
first three elements represent memory locations main, carda, and cardb,
and are true if the book is identifiably in that memory. The fourth
is the a count of how many instances of the book were found across all
the memory locations.
'''
loc = [None, None, None, 0]
if reset: if reset:
self.book_db_title_cache = None self.book_db_title_cache = None
self.book_db_uuid_cache = None self.book_db_uuid_cache = None
self.book_db_id_counts = None
return return
if self.book_db_title_cache is None: if self.book_db_title_cache is None:
self.book_db_title_cache = [] self.book_db_title_cache = []
self.book_db_uuid_cache = [] self.book_db_uuid_cache = []
self.book_db_id_counts = {}
for i, l in enumerate(self.booklists()): for i, l in enumerate(self.booklists()):
self.book_db_title_cache.append({}) self.book_db_title_cache.append({})
self.book_db_uuid_cache.append(set()) self.book_db_uuid_cache.append(set())
@ -1333,6 +1343,10 @@ class DeviceMixin(object): # {{{
db_id = book.db_id db_id = book.db_id
if db_id is not None: if db_id is not None:
self.book_db_title_cache[i][book_title]['db_ids'].add(db_id) self.book_db_title_cache[i][book_title]['db_ids'].add(db_id)
# increment the count of books on the device with this
# db_id.
c = self.book_db_id_counts.get(db_id, 0)
self.book_db_id_counts[db_id] = c + 1
uuid = getattr(book, 'uuid', None) uuid = getattr(book, 'uuid', None)
if uuid is not None: if uuid is not None:
self.book_db_uuid_cache[i].add(uuid) self.book_db_uuid_cache[i].add(uuid)
@ -1351,7 +1365,13 @@ class DeviceMixin(object): # {{{
if mi.authors and \ if mi.authors and \
re.sub('(?u)\W|[_]', '', authors_to_string(mi.authors).lower()) \ re.sub('(?u)\W|[_]', '', authors_to_string(mi.authors).lower()) \
in cache['authors']: in cache['authors']:
# We really shouldn't get here, because set_books_in_library
# should have set the db_ids for the books, and therefore
# the if just above should have found them. Mark the book
# anyway, and print a message about the situation
loc[i] = True loc[i] = True
prints('book_on_device: matched title/author but not db_id!',
mi.title, authors_to_string(mi.authors))
continue continue
# Also check author sort, because it can be used as author in # Also check author sort, because it can be used as author in
# some formats # some formats
@ -1360,9 +1380,16 @@ class DeviceMixin(object): # {{{
in cache['authors']: in cache['authors']:
loc[i] = True loc[i] = True
continue continue
loc[3] = self.book_db_id_counts.get(id, 0)
return loc return loc
def set_books_in_library(self, booklists, reset=False): def set_books_in_library(self, booklists, reset=False):
'''
Set the ondevice indications in the device database.
This method should be called before book_on_device is called, because
it sets the application_id for matched books. Book_on_device uses that
to both speed up matching and to count matches.
'''
# Force a reset if the caches are not initialized # Force a reset if the caches are not initialized
if reset or not hasattr(self, 'db_book_title_cache'): if reset or not hasattr(self, 'db_book_title_cache'):
# It might be possible to get here without having initialized the # It might be possible to get here without having initialized the
@ -1393,10 +1420,12 @@ class DeviceMixin(object): # {{{
self.db_book_uuid_cache[mi.uuid] = mi self.db_book_uuid_cache[mi.uuid] = mi
# Now iterate through all the books on the device, setting the # Now iterate through all the books on the device, setting the
# in_library field Fastest and most accurate key is the uuid. Second is # in_library field. Fastest and most accurate key is the uuid. Second is
# the application_id, which is really the db key, but as this can # the application_id, which is really the db key, but as this can
# accidentally match across libraries we also verify the title. The # accidentally match across libraries we also verify the title. The
# db_id exists on Sony devices. Fallback is title and author match # db_id exists on Sony devices. Fallback is title and author match.
# We set the application ID so that we can reproduce book matching,
# necessary for identifying copies of books.
update_metadata = prefs['manage_device_metadata'] == 'on_connect' update_metadata = prefs['manage_device_metadata'] == 'on_connect'
for booklist in booklists: for booklist in booklists:
@ -1418,12 +1447,15 @@ class DeviceMixin(object): # {{{
if d is not None: if d is not None:
if getattr(book, 'application_id', None) in d['db_ids']: if getattr(book, 'application_id', None) in d['db_ids']:
book.in_library = True book.in_library = True
# application already matches db_id, so no need to set it
if update_metadata: if update_metadata:
book.smart_update(d['db_ids'][book.application_id], book.smart_update(d['db_ids'][book.application_id],
replace_metadata=True) replace_metadata=True)
continue continue
if book.db_id in d['db_ids']: if book.db_id in d['db_ids']:
book.in_library = True book.in_library = True
book.application_id = \
d['db_ids'][book.db_id].application_id
if update_metadata: if update_metadata:
book.smart_update(d['db_ids'][book.db_id], book.smart_update(d['db_ids'][book.db_id],
replace_metadata=True) replace_metadata=True)
@ -1435,11 +1467,15 @@ class DeviceMixin(object): # {{{
book_authors = re.sub('(?u)\W|[_]', '', book_authors) book_authors = re.sub('(?u)\W|[_]', '', book_authors)
if book_authors in d['authors']: if book_authors in d['authors']:
book.in_library = True book.in_library = True
book.application_id = \
d['authors'][book_authors].application_id
if update_metadata: if update_metadata:
book.smart_update(d['authors'][book_authors], book.smart_update(d['authors'][book_authors],
replace_metadata=True) replace_metadata=True)
elif book_authors in d['author_sort']: elif book_authors in d['author_sort']:
book.in_library = True book.in_library = True
book.application_id = \
d['author_sort'][book_authors].application_id
if update_metadata: if update_metadata:
book.smart_update(d['author_sort'][book_authors], book.smart_update(d['author_sort'][book_authors],
replace_metadata=True) replace_metadata=True)

View File

@ -646,7 +646,7 @@
<item> <item>
<widget class="QPushButton" name="fetch_cover_button"> <widget class="QPushButton" name="fetch_cover_button">
<property name="text"> <property name="text">
<string>Download &amp;cover</string> <string>Download co&amp;ver</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -32,6 +32,9 @@ class LibraryViewMixin(object): # {{{
def __init__(self, db): def __init__(self, db):
self.library_view.files_dropped.connect(self.iactions['Add Books'].files_dropped, type=Qt.QueuedConnection) self.library_view.files_dropped.connect(self.iactions['Add Books'].files_dropped, type=Qt.QueuedConnection)
self.library_view.add_column_signal.connect(partial(self.iactions['Preferences'].do_config,
initial_plugin=('Interface', 'Custom Columns')),
type=Qt.QueuedConnection)
for func, args in [ for func, args in [
('connect_to_search_box', (self.search, ('connect_to_search_box', (self.search,
self.search_done)), self.search_done)),
@ -145,20 +148,23 @@ class StatusBar(QStatusBar): # {{{
self._font = QFont() self._font = QFont()
self._font.setBold(True) self._font.setBold(True)
self.setFont(self._font) self.setFont(self._font)
self.defmsg = QLabel(self.default_message)
self.defmsg.setFont(self._font)
self.addWidget(self.defmsg)
def initialize(self, systray=None): def initialize(self, systray=None):
self.systray = systray self.systray = systray
self.notifier = get_notifier(systray) self.notifier = get_notifier(systray)
self.messageChanged.connect(self.message_changed,
type=Qt.QueuedConnection)
self.message_changed('')
def device_connected(self, devname): def device_connected(self, devname):
self.device_string = _('Connected ') + devname self.device_string = _('Connected ') + devname
self.defmsg.setText(self.default_message + ' ..::.. ' +
self.device_string)
self.clearMessage() self.clearMessage()
def device_disconnected(self): def device_disconnected(self):
self.device_string = '' self.device_string = ''
self.defmsg.setText(self.default_message)
self.clearMessage() self.clearMessage()
def new_version_available(self, ver, url): def new_version_available(self, ver, url):
@ -188,15 +194,6 @@ class StatusBar(QStatusBar): # {{{
def clear_message(self): def clear_message(self):
self.clearMessage() self.clearMessage()
def message_changed(self, msg):
if not msg or msg.isEmpty() or msg.isNull() or \
not unicode(msg).strip():
extra = ''
if self.device_string:
extra = ' ..::.. ' + self.device_string
self.showMessage(self.default_message + extra)
# }}} # }}}
class LayoutMixin(object): # {{{ class LayoutMixin(object): # {{{

View File

@ -9,7 +9,7 @@ import os
from functools import partial from functools import partial
from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \ from PyQt4.Qt import QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, \
QModelIndex QModelIndex, QIcon
from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \ from calibre.gui2.library.delegates import RatingDelegate, PubDateDelegate, \
TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \ TextDelegate, DateDelegate, TagsDelegate, CcTextDelegate, \
@ -23,6 +23,7 @@ from calibre.gui2.library import DEFAULT_SORT
class BooksView(QTableView): # {{{ class BooksView(QTableView): # {{{
files_dropped = pyqtSignal(object) files_dropped = pyqtSignal(object)
add_column_signal = pyqtSignal()
def __init__(self, parent, modelcls=BooksModel): def __init__(self, parent, modelcls=BooksModel):
QTableView.__init__(self, parent) QTableView.__init__(self, parent)
@ -54,6 +55,7 @@ class BooksView(QTableView): # {{{
self.selectionModel().currentRowChanged.connect(self._model.current_changed) self.selectionModel().currentRowChanged.connect(self._model.current_changed)
# {{{ Column Header setup # {{{ Column Header setup
self.can_add_columns = True
self.was_restored = False self.was_restored = False
self.column_header = self.horizontalHeader() self.column_header = self.horizontalHeader()
self.column_header.setMovable(True) self.column_header.setMovable(True)
@ -93,6 +95,8 @@ class BooksView(QTableView): # {{{
self.sortByColumn(idx, Qt.DescendingOrder) self.sortByColumn(idx, Qt.DescendingOrder)
elif action == 'defaults': elif action == 'defaults':
self.apply_state(self.get_default_state()) self.apply_state(self.get_default_state())
elif action == 'addcustcol':
self.add_column_signal.emit()
elif action.startswith('align_'): elif action.startswith('align_'):
alignment = action.partition('_')[-1] alignment = action.partition('_')[-1]
self._model.change_alignment(column, alignment) self._model.change_alignment(column, alignment)
@ -166,6 +170,13 @@ class BooksView(QTableView): # {{{
partial(self.column_header_context_handler, partial(self.column_header_context_handler,
action='defaults', column=col)) action='defaults', column=col))
if self.can_add_columns:
self.column_header_context_menu.addAction(
QIcon(I('column.png')),
_('Add your own columns'),
partial(self.column_header_context_handler,
action='addcustcol', column=col))
self.column_header_context_menu.popup(self.column_header.mapToGlobal(pos)) self.column_header_context_menu.popup(self.column_header.mapToGlobal(pos))
# }}} # }}}
@ -494,6 +505,7 @@ class DeviceBooksView(BooksView): # {{{
def __init__(self, parent): def __init__(self, parent):
BooksView.__init__(self, parent, DeviceBooksModel) BooksView.__init__(self, parent, DeviceBooksModel)
self.can_add_columns = False
self.columns_resized = False self.columns_resized = False
self.resize_on_select = False self.resize_on_select = False
self.rating_delegate = None self.rating_delegate = None

View File

@ -18,18 +18,48 @@ class AbortCommit(Exception):
class ConfigWidgetInterface(object): class ConfigWidgetInterface(object):
'''
This class defines the interface that all widgets displayed in the
Preferences dialog must implement. See :class:`ConfigWidgetBase` for
a base class that implements this interface and defines various conveninece
methods as well.
'''
#: This signal must be emitted whenever the user changes a value in this
#: widget
changed_signal = None changed_signal = None
#: Set to True iff the :meth:`restore_to_defaults` method is implemented.
supports_restoring_to_defaults = True supports_restoring_to_defaults = True
#: The tooltip for the Restore to defaults button
restore_defaults_desc = _('Restore settings to default values. ' restore_defaults_desc = _('Restore settings to default values. '
'You have to click Apply to actually save the default settings.') 'You have to click Apply to actually save the default settings.')
#: If True the Preferences dialog will not allow the user to set any more
#: preferences. Only has effect if :meth:`commit` returns True.
restart_critical = False
def genesis(self, gui): def genesis(self, gui):
'''
Called once before the widget is displayed, should perform any
necessary setup.
:param gui: The main calibre graphical user interface
'''
raise NotImplementedError() raise NotImplementedError()
def initialize(self): def initialize(self):
'''
Should set all config values to their initial values (the values
stored in the config files).
'''
raise NotImplementedError() raise NotImplementedError()
def restore_defaults(self): def restore_defaults(self):
'''
Should set all config values to their defaults.
'''
pass pass
def commit(self): def commit(self):
@ -37,11 +67,17 @@ class ConfigWidgetInterface(object):
Save any changed settings. Return True if the changes require a Save any changed settings. Return True if the changes require a
restart, False otherwise. Raise an :class:`AbortCommit` exception restart, False otherwise. Raise an :class:`AbortCommit` exception
to indicate that an error occurred. You are responsible for giving the to indicate that an error occurred. You are responsible for giving the
suer feedback about what the error is and how to correct it. user feedback about what the error is and how to correct it.
''' '''
return False return False
def refresh_gui(self, gui): def refresh_gui(self, gui):
'''
Called once after this widget is committed. Responsible for causing the
gui to reread any changed settings. Note that by default the GUI
re-initializes various elements anyway, so most widgets won't need to
use this method.
'''
pass pass
class Setting(object): class Setting(object):
@ -170,8 +206,23 @@ class CommaSeparatedList(Setting):
class ConfigWidgetBase(QWidget, ConfigWidgetInterface): class ConfigWidgetBase(QWidget, ConfigWidgetInterface):
'''
Base class that contains code to easily add standard config widgets like
checkboxes, combo boxes, text fields and so on. See the :meth:`register`
method.
This class automatically handles change notification, resetting to default,
translation between gui objects and config objects, etc. for registered
settings.
If your config widget inherits from this class but includes setting that
are not registered, you should override the :class:`ConfigWidgetInterface` methods
and call the base class methods inside the overrides.
'''
changed_signal = pyqtSignal() changed_signal = pyqtSignal()
supports_restoring_to_defaults = True supports_restoring_to_defaults = True
restart_critical = False
def __init__(self, parent=None): def __init__(self, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
@ -181,6 +232,21 @@ class ConfigWidgetBase(QWidget, ConfigWidgetInterface):
def register(self, name, config_obj, gui_name=None, choices=None, def register(self, name, config_obj, gui_name=None, choices=None,
restart_required=False, empty_string_is_None=True, setting=Setting): restart_required=False, empty_string_is_None=True, setting=Setting):
'''
Register a setting.
:param name: The setting name
:param config: The config object that reads/writes the setting
:param gui_name: The name of the GUI object that presents an interface
to change the setting. By default it is assumed to be
``'opt_' + name``.
:param choices: If this setting is a multiple choice (combobox) based
setting, the list of choices. The list is a list of two
element tuples of the form: ``[(gui name, value), ...]``
:param setting: The class responsible for managing this setting. The
default class handles almost all cases, so this param
is rarely used.
'''
setting = setting(name, config_obj, self, gui_name=gui_name, setting = setting(name, config_obj, self, gui_name=gui_name,
choices=choices, restart_required=restart_required, choices=choices, restart_required=restart_required,
empty_string_is_None=empty_string_is_None) empty_string_is_None=empty_string_is_None)

View File

@ -16,6 +16,8 @@ from calibre.gui2 import error_dialog, question_dialog, ALL_COLUMNS
class ConfigWidget(ConfigWidgetBase, Ui_Form): class ConfigWidget(ConfigWidgetBase, Ui_Form):
restart_critical = True
def genesis(self, gui): def genesis(self, gui):
self.gui = gui self.gui = gui
db = self.gui.library_view.model().db db = self.gui.library_view.model().db
@ -103,7 +105,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
return return
self.opt_columns.item(idx).setCheckState(False) self.opt_columns.item(idx).setCheckState(False)
self.opt_columns.takeItem(idx) self.opt_columns.takeItem(idx)
self.custcols[col]['*deleteme'] = True if self.custcols[col]['colnum'] is None:
del self.custcols[col] # A newly-added column was deleted
else:
self.custcols[col]['*deleteme'] = True
self.changed_signal.emit() self.changed_signal.emit()
def add_custcol(self): def add_custcol(self):

View File

@ -161,7 +161,6 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
else: else:
idx = self.parent.opt_columns.currentRow() idx = self.parent.opt_columns.currentRow()
item = self.parent.opt_columns.item(idx) item = self.parent.opt_columns.item(idx)
item.setData(Qt.UserRole, QVariant(key))
item.setText(col_heading) item.setText(col_heading)
self.parent.custcols[self.orig_column_name]['label'] = col self.parent.custcols[self.orig_column_name]['label'] = col
self.parent.custcols[self.orig_column_name]['name'] = col_heading self.parent.custcols[self.orig_column_name]['name'] = col_heading

View File

@ -9,16 +9,19 @@ import textwrap
from functools import partial from functools import partial
from PyQt4.Qt import QMainWindow, Qt, QIcon, QStatusBar, QFont, QWidget, \ from PyQt4.Qt import QMainWindow, Qt, QIcon, QStatusBar, QFont, QWidget, \
QScrollArea, QStackedWidget, QVBoxLayout, QLabel, QFrame, \ QScrollArea, QStackedWidget, QVBoxLayout, QLabel, QFrame, QKeySequence, \
QToolBar, QSize, pyqtSignal, QSizePolicy, QToolButton QToolBar, QSize, pyqtSignal, QPixmap, QToolButton, QAction, \
QDialogButtonBox, QHBoxLayout
from calibre.constants import __appname__, __version__ from calibre.constants import __appname__, __version__, islinux
from calibre.gui2 import gprefs, min_available_height, available_width, \ from calibre.gui2 import gprefs, min_available_height, available_width, \
warning_dialog warning_dialog
from calibre.gui2.preferences import init_gui, AbortCommit, get_plugin from calibre.gui2.preferences import init_gui, AbortCommit, get_plugin
from calibre.customize.ui import preferences_plugins from calibre.customize.ui import preferences_plugins
from calibre.utils.ordered_dict import OrderedDict from calibre.utils.ordered_dict import OrderedDict
ICON_SIZE = 32
class StatusBar(QStatusBar): # {{{ class StatusBar(QStatusBar): # {{{
def __init__(self, parent=None): def __init__(self, parent=None):
@ -30,18 +33,41 @@ class StatusBar(QStatusBar): # {{{
self._font.setBold(True) self._font.setBold(True)
self.setFont(self._font) self.setFont(self._font)
self.messageChanged.connect(self.message_changed, self.w = QLabel(self.default_message)
type=Qt.QueuedConnection) self.w.setFont(self._font)
self.message_changed('') self.addWidget(self.w)
def message_changed(self, msg):
if not msg or msg.isEmpty() or msg.isNull() or \
not unicode(msg).strip():
self.showMessage(self.default_message)
# }}} # }}}
class Category(QWidget): class BarTitle(QWidget): # {{{
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self._layout = QHBoxLayout()
self.setLayout(self._layout)
self._layout.addStretch(10)
self.icon = QLabel('')
self._layout.addWidget(self.icon)
self.title = QLabel('')
self.title.setStyleSheet('QLabel { font-weight: bold }')
self.title.setAlignment(Qt.AlignLeft | Qt.AlignCenter)
self._layout.addWidget(self.title)
self._layout.addStretch(10)
def show_plugin(self, plugin):
self.pmap = QPixmap(plugin.icon).scaled(ICON_SIZE, ICON_SIZE,
Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.icon.setPixmap(self.pmap)
self.title.setText(plugin.gui_name)
tt = plugin.description
self.setStatusTip(tt)
tt = textwrap.fill(tt)
self.setToolTip(tt)
self.setWhatsThis(tt)
# }}}
class Category(QWidget): # {{{
plugin_activated = pyqtSignal(object) plugin_activated = pyqtSignal(object)
@ -82,8 +108,9 @@ class Category(QWidget):
def triggered(self, plugin, *args): def triggered(self, plugin, *args):
self.plugin_activated.emit(plugin) self.plugin_activated.emit(plugin)
# }}}
class Browser(QScrollArea): class Browser(QScrollArea): # {{{
show_plugin = pyqtSignal(object) show_plugin = pyqtSignal(object)
@ -116,6 +143,7 @@ class Browser(QScrollArea):
self.container = QWidget(self) self.container = QWidget(self)
self.container.setLayout(self._layout) self.container.setLayout(self._layout)
self.setWidget(self.container) self.setWidget(self.container)
for name, plugins in self.category_map.items(): for name, plugins in self.category_map.items():
w = Category(name, plugins, self) w = Category(name, plugins, self)
self.widgets.append(w) self.widgets.append(w)
@ -123,6 +151,7 @@ class Browser(QScrollArea):
w.plugin_activated.connect(self.show_plugin.emit) w.plugin_activated.connect(self.show_plugin.emit)
# }}}
class Preferences(QMainWindow): class Preferences(QMainWindow):
@ -132,7 +161,7 @@ class Preferences(QMainWindow):
self.must_restart = False self.must_restart = False
self.committed = False self.committed = False
self.resize(900, 700) self.resize(900, 720)
nh, nw = min_available_height()-25, available_width()-10 nh, nw = min_available_height()-25, available_width()-10
if nh < 0: if nh < 0:
nh = 800 nh = 800
@ -141,11 +170,19 @@ class Preferences(QMainWindow):
nh = min(self.height(), nh) nh = min(self.height(), nh)
nw = min(self.width(), nw) nw = min(self.width(), nw)
self.resize(nw, nh) self.resize(nw, nh)
self.esc_action = QAction(self)
self.addAction(self.esc_action)
self.esc_action.setShortcut(QKeySequence(Qt.Key_Escape))
self.esc_action.triggered.connect(self.esc)
geom = gprefs.get('preferences_window_geometry', None) geom = gprefs.get('preferences_window_geometry', None)
if geom is not None: if geom is not None:
self.restoreGeometry(geom) self.restoreGeometry(geom)
# Center
if islinux:
self.move(gui.rect().center() - self.rect().center())
self.setWindowModality(Qt.WindowModal) self.setWindowModality(Qt.WindowModal)
self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowTitle(__appname__ + ' - ' + _('Preferences'))
self.setWindowIcon(QIcon(I('config.png'))) self.setWindowIcon(QIcon(I('config.png')))
@ -154,7 +191,13 @@ class Preferences(QMainWindow):
self.setStatusBar(self.status_bar) self.setStatusBar(self.status_bar)
self.stack = QStackedWidget(self) self.stack = QStackedWidget(self)
self.setCentralWidget(self.stack) self.cw = QWidget(self)
self.cw.setLayout(QVBoxLayout())
self.cw.layout().addWidget(self.stack)
self.bb = QDialogButtonBox(QDialogButtonBox.Close)
self.cw.layout().addWidget(self.bb)
self.bb.rejected.connect(self.close, type=Qt.QueuedConnection)
self.setCentralWidget(self.cw)
self.browser = Browser(self) self.browser = Browser(self)
self.browser.show_plugin.connect(self.show_plugin) self.browser.show_plugin.connect(self.show_plugin)
self.stack.addWidget(self.browser) self.stack.addWidget(self.browser)
@ -165,7 +208,7 @@ class Preferences(QMainWindow):
self.bar = QToolBar(self) self.bar = QToolBar(self)
self.addToolBar(self.bar) self.addToolBar(self.bar)
self.bar.setVisible(False) self.bar.setVisible(False)
self.bar.setIconSize(QSize(32, 32)) self.bar.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
self.bar.setMovable(False) self.bar.setMovable(False)
self.bar.setFloatable(False) self.bar.setFloatable(False)
self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
@ -173,13 +216,8 @@ class Preferences(QMainWindow):
self.commit) self.commit)
self.cancel_action = self.bar.addAction(QIcon(I('window-close.png')), self.cancel_action = self.bar.addAction(QIcon(I('window-close.png')),
_('&Cancel'), self.cancel) _('&Cancel'), self.cancel)
self.bar_filler = QLabel('') self.bar_title = BarTitle(self.bar)
self.bar_filler.setSizePolicy(QSizePolicy.Expanding, self.bar.addWidget(self.bar_title)
QSizePolicy.Preferred)
self.bar_filler.setStyleSheet(
'QLabel { font-weight: bold }')
self.bar_filler.setAlignment(Qt.AlignHCenter | Qt.AlignCenter)
self.bar.addWidget(self.bar_filler)
self.restore_action = self.bar.addAction(QIcon(I('clear_left.png')), self.restore_action = self.bar.addAction(QIcon(I('clear_left.png')),
_('Restore &defaults'), self.restore_defaults) _('Restore &defaults'), self.restore_defaults)
for ac, tt in [('apply', _('Save changes')), for ac, tt in [('apply', _('Save changes')),
@ -223,9 +261,10 @@ class Preferences(QMainWindow):
self.restore_action.setToolTip(textwrap.fill(tt)) self.restore_action.setToolTip(textwrap.fill(tt))
self.restore_action.setWhatsThis(textwrap.fill(tt)) self.restore_action.setWhatsThis(textwrap.fill(tt))
self.restore_action.setStatusTip(tt) self.restore_action.setStatusTip(tt)
self.bar_filler.setText(plugin.gui_name) self.bar_title.show_plugin(plugin)
self.setWindowIcon(QIcon(plugin.icon)) self.setWindowIcon(QIcon(plugin.icon))
self.bar.setVisible(True) self.bar.setVisible(True)
self.bb.setVisible(False)
def hide_plugin(self): def hide_plugin(self):
@ -235,21 +274,37 @@ class Preferences(QMainWindow):
self.bar.setVisible(False) self.bar.setVisible(False)
self.stack.setCurrentIndex(0) self.stack.setCurrentIndex(0)
self.setWindowIcon(QIcon(I('config.png'))) self.setWindowIcon(QIcon(I('config.png')))
self.bb.setVisible(True)
def esc(self, *args):
if self.stack.currentIndex() == 1:
self.hide_plugin()
elif self.stack.currentIndex() == 0:
self.close()
def commit(self, *args): def commit(self, *args):
try: try:
must_restart = self.showing_widget.commit() must_restart = self.showing_widget.commit()
except AbortCommit: except AbortCommit:
return return
rc = self.showing_widget.restart_critical
self.committed = True self.committed = True
if must_restart: if must_restart:
self.must_restart = True self.must_restart = True
warning_dialog(self, _('Restart needed'), msg = _('Some of the changes you made require a restart.'
_('Some of the changes you made require a restart.' ' Please restart calibre as soon as possible.')
' Please restart calibre as soon as possible.'), if rc:
show=True) msg = _('The changes you have made require calibre be '
'restarted immediately. You will not be allowed '
'set any more preferences, until you restart.')
warning_dialog(self, _('Restart needed'), msg, show=True,
show_copy_button=False)
self.showing_widget.refresh_gui(self.gui) self.showing_widget.refresh_gui(self.gui)
self.hide_plugin() self.hide_plugin()
if must_restart and rc:
self.close()
def cancel(self, *args): def cancel(self, *args):
@ -261,6 +316,16 @@ class Preferences(QMainWindow):
def closeEvent(self, *args): def closeEvent(self, *args):
gprefs.set('preferences_window_geometry', gprefs.set('preferences_window_geometry',
bytearray(self.saveGeometry())) bytearray(self.saveGeometry()))
if self.committed:
self.gui.must_restart_before_config = self.must_restart
self.gui.tags_view.set_new_model() # in case columns changed
self.gui.tags_view.recount()
self.gui.create_device_menu()
self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
self.gui.tool_bar.build_bar()
self.gui.build_context_menus()
self.gui.tool_bar.apply_settings()
return QMainWindow.closeEvent(self, *args) return QMainWindow.closeEvent(self, *args)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -47,7 +47,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
det_msg=traceback.format_exc(), show=True) det_msg=traceback.format_exc(), show=True)
raise AbortCommit('abort') raise AbortCommit('abort')
write_tweaks(raw) write_tweaks(raw)
return ConfigWidgetBase.commit(self) ConfigWidgetBase.commit(self)
return True
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -17,7 +17,7 @@
<item> <item>
<widget class="QLabel" name="label_18"> <widget class="QLabel" name="label_18">
<property name="text"> <property name="text">
<string>Values for the tweaks are shown below. Edit them to change the behavior of calibre</string> <string>Values for the tweaks are shown below. Edit them to change the behavior of calibre. Your changes will only take effect after a restart of calibre.</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>

View File

@ -424,10 +424,8 @@ class TagsModel(QAbstractItemModel): # {{{
self.categories = [] self.categories = []
# Reconstruct the user categories, putting them into metadata # Reconstruct the user categories, putting them into metadata
self.db.field_metadata.remove_dynamic_categories()
tb_cats = self.db.field_metadata tb_cats = self.db.field_metadata
for k in tb_cats.keys():
if tb_cats[k]['kind'] in ['user', 'search']:
del tb_cats[k]
for user_cat in sorted(self.db.prefs.get('user_categories', {}).keys()): for user_cat in sorted(self.db.prefs.get('user_categories', {}).keys()):
cat_name = user_cat+':' # add the ':' to avoid name collision cat_name = user_cat+':' # add the ':' to avoid name collision
tb_cats.add_user_category(label=cat_name, name=user_cat) tb_cats.add_user_category(label=cat_name, name=user_cat)

View File

@ -238,7 +238,7 @@ def fetch_scheduled_recipe(arg):
return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt] return 'gui_convert', args, _('Fetch news from ')+arg['title'], fmt.upper(), [pt]
def generate_catalog(parent, dbspec, ids, device): def generate_catalog(parent, dbspec, ids, device_manager):
from calibre.gui2.dialogs.catalog import Catalog from calibre.gui2.dialogs.catalog import Catalog
# Build the Catalog dialog in gui2.dialogs.catalog # Build the Catalog dialog in gui2.dialogs.catalog
@ -252,9 +252,18 @@ def generate_catalog(parent, dbspec, ids, device):
# Profile the connected device # Profile the connected device
# Parallel initialization in calibre.library.cli:command_catalog() # Parallel initialization in calibre.library.cli:command_catalog()
connected_device = { 'storage':None,'serial':None,'save_template':None,'name':None} connected_device = {
'is_device_connected': device_manager.is_device_connected,
'kind': device_manager.connected_device_kind,
'name': None,
'save_template': None,
'serial': None,
'storage': None
}
if device: if device_manager.is_device_connected:
device = device_manager.device
connected_device['name'] = device.gui_name
try: try:
storage = [] storage = []
if device._main_prefix: if device._main_prefix:
@ -263,11 +272,10 @@ def generate_catalog(parent, dbspec, ids, device):
storage.append(os.path.join(device._card_a_prefix, device.EBOOK_DIR_CARD_A)) storage.append(os.path.join(device._card_a_prefix, device.EBOOK_DIR_CARD_A))
if device._card_b_prefix: if device._card_b_prefix:
storage.append(os.path.join(device._card_b_prefix, device.EBOOK_DIR_CARD_B)) storage.append(os.path.join(device._card_b_prefix, device.EBOOK_DIR_CARD_B))
connected_device = { 'storage': storage, connected_device['storage'] = storage
'serial': device.detected_device.serial if \ connected_device['serial'] = device.detected_device.serial if \
hasattr(device.detected_device,'serial') else None, hasattr(device.detected_device,'serial') else None
'save_template': device.save_template(), connected_device['save_template'] = device.save_template()
'name': device.gui_name}
except: except:
pass pass

View File

@ -522,6 +522,16 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
def shutdown(self, write_settings=True): def shutdown(self, write_settings=True):
try:
db = self.library_view.model().db
cf = db.clean
except:
pass
else:
cf()
# Save the current field_metadata for applications like calibre2opds
# Goes here, because if cf is valid, db is valid.
db.prefs['field_metadata'] = db.field_metadata.all_metadata()
for action in self.iactions.values(): for action in self.iactions.values():
if not action.shutting_down(): if not action.shutting_down():
return return

File diff suppressed because it is too large Load Diff

View File

@ -324,6 +324,7 @@ def do_remove(db, ids):
db.delete_book(y) db.delete_book(y)
send_message() send_message()
db.clean()
def remove_option_parser(): def remove_option_parser():
return get_parser(_( return get_parser(_(
@ -449,6 +450,7 @@ def command_show_metadata(args, dbpath):
def do_set_metadata(db, id, stream): def do_set_metadata(db, id, stream):
mi = OPF(stream) mi = OPF(stream)
db.set_metadata(id, mi) db.set_metadata(id, mi)
db.clean()
do_show_metadata(db, id, False) do_show_metadata(db, id, False)
send_message() send_message()
@ -574,6 +576,9 @@ def command_add_custom_column(args, dbpath):
return 1 return 1
do_add_custom_column(get_db(dbpath, opts), args[0], args[1], args[2], do_add_custom_column(get_db(dbpath, opts), args[0], args[1], args[2],
opts.is_multiple, json.loads(opts.display)) opts.is_multiple, json.loads(opts.display))
# Re-open the DB so that field_metadata is reflects the column changes
db = get_db(dbpath, opts)
db.prefs['field_metadata'] = db.field_metadata.all_metadata()
return 0 return 0
def catalog_option_parser(args): def catalog_option_parser(args):
@ -672,7 +677,14 @@ def command_catalog(args, dbpath):
# No support for connected device in CLI environment # No support for connected device in CLI environment
# Parallel initialization in calibre.gui2.tools:generate_catalog() # Parallel initialization in calibre.gui2.tools:generate_catalog()
opts.connected_device = { 'storage':None,'serial':None,'save_template':None,'name':None} opts.connected_device = {
'is_device_connected': False,
'kind': None,
'name': None,
'save_template': None,
'serial': None,
'storage': None,
}
with plugin: with plugin:
plugin.run(args[1], opts, get_db(dbpath, opts)) plugin.run(args[1], opts, get_db(dbpath, opts))
@ -797,6 +809,9 @@ def command_remove_custom_column(args, dbpath):
return 1 return 1
do_remove_custom_column(get_db(dbpath, opts), args[0], opts.force) do_remove_custom_column(get_db(dbpath, opts), args[0], opts.force)
# Re-open the DB so that field_metadata is reflects the column changes
db = get_db(dbpath, opts)
db.prefs['field_metadata'] = db.field_metadata.all_metadata()
return 0 return 0
def saved_searches_option_parser(): def saved_searches_option_parser():

View File

@ -144,6 +144,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.initialize_dynamic() self.initialize_dynamic()
def initialize_dynamic(self): def initialize_dynamic(self):
self.field_metadata = FieldMetadata() #Ensure we start with a clean copy
self.prefs = DBPrefs(self) self.prefs = DBPrefs(self)
defs = self.prefs.defaults defs = self.prefs.defaults
defs['gui_restriction'] = defs['cs_restriction'] = '' defs['gui_restriction'] = defs['cs_restriction'] = ''
@ -289,10 +290,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# Reconstruct the user categories, putting them into field_metadata # Reconstruct the user categories, putting them into field_metadata
# Assumption is that someone else will fix them if they change. # Assumption is that someone else will fix them if they change.
self.field_metadata.remove_dynamic_categories()
tb_cats = self.field_metadata tb_cats = self.field_metadata
for k in tb_cats.keys():
if tb_cats[k]['kind'] in ['user', 'search']:
del tb_cats[k]
for user_cat in sorted(self.prefs.get('user_categories', {}).keys()): for user_cat in sorted(self.prefs.get('user_categories', {}).keys()):
cat_name = user_cat+':' # add the ':' to avoid name collision cat_name = user_cat+':' # add the ':' to avoid name collision
tb_cats.add_user_category(label=cat_name, name=user_cat) tb_cats.add_user_category(label=cat_name, name=user_cat)
@ -331,7 +330,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn', for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn',
'publisher', 'rating', 'series', 'series_index', 'tags', 'publisher', 'rating', 'series', 'series_index', 'tags',
'title', 'timestamp', 'uuid', 'pubdate'): 'title', 'timestamp', 'uuid', 'pubdate', 'ondevice'):
setattr(self, prop, functools.partial(get_property, setattr(self, prop, functools.partial(get_property,
loc=self.FIELD_MAP['comments' if prop == 'comment' else prop])) loc=self.FIELD_MAP['comments' if prop == 'comment' else prop]))
setattr(self, 'title_sort', functools.partial(get_property, setattr(self, 'title_sort', functools.partial(get_property,
@ -639,16 +638,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def book_on_device_string(self, id): def book_on_device_string(self, id):
loc = [] loc = []
count = 0
on = self.book_on_device(id) on = self.book_on_device(id)
if on is not None: if on is not None:
m, a, b = on m, a, b, count = on
if m is not None: if m is not None:
loc.append(_('Main')) loc.append(_('Main'))
if a is not None: if a is not None:
loc.append(_('Card A')) loc.append(_('Card A'))
if b is not None: if b is not None:
loc.append(_('Card B')) loc.append(_('Card B'))
return ', '.join(loc) return ', '.join(loc) + ((' (%s books)'%count) if count > 1 else '')
def set_book_on_device_func(self, func): def set_book_on_device_func(self, func):
self.book_on_device_func = func self.book_on_device_func = func
@ -1125,7 +1125,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if not authors: if not authors:
authors = [_('Unknown')] authors = [_('Unknown')]
self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,)) self.conn.execute('DELETE FROM books_authors_link WHERE book=?',(id,))
self.conn.execute('DELETE FROM authors WHERE (SELECT COUNT(id) FROM books_authors_link WHERE author=authors.id) < 1')
for a in authors: for a in authors:
if not a: if not a:
continue continue
@ -2151,8 +2150,6 @@ books_series_link feeds
os.remove(self.dbpath) os.remove(self.dbpath)
shutil.copyfile(dest, self.dbpath) shutil.copyfile(dest, self.dbpath)
self.connect() self.connect()
self.field_metadata.remove_dynamic_categories()
self.field_metadata.remove_custom_fields()
self.initialize_dynamic() self.initialize_dynamic()
self.refresh() self.refresh()
if os.path.exists(dest): if os.path.exists(dest):

View File

@ -306,7 +306,7 @@ class FieldMetadata(dict):
self._tb_cats[k]['label'] = k self._tb_cats[k]['label'] = k
self._tb_cats[k]['display'] = {} self._tb_cats[k]['display'] = {}
self._tb_cats[k]['is_editable'] = True self._tb_cats[k]['is_editable'] = True
self._add_search_terms_to_map(k, self._tb_cats[k]['search_terms']) self._add_search_terms_to_map(k, v['search_terms'])
self.custom_field_prefix = '#' self.custom_field_prefix = '#'
self.get = self._tb_cats.get self.get = self._tb_cats.get
@ -371,6 +371,12 @@ class FieldMetadata(dict):
def get_custom_fields(self): def get_custom_fields(self):
return [l for l in self._tb_cats if self._tb_cats[l]['is_custom']] return [l for l in self._tb_cats if self._tb_cats[l]['is_custom']]
def all_metadata(self):
l = {}
for k in self._tb_cats:
l[k] = self._tb_cats[k]
return l
def get_custom_field_metadata(self): def get_custom_field_metadata(self):
l = {} l = {}
for k in self._tb_cats: for k in self._tb_cats:
@ -408,10 +414,6 @@ class FieldMetadata(dict):
self._add_search_terms_to_map(key, [key]) self._add_search_terms_to_map(key, [key])
self.custom_label_to_key_map[label+'_index'] = key self.custom_label_to_key_map[label+'_index'] = key
def remove_custom_fields(self):
for key in self.get_custom_fields():
del self._tb_cats[key]
def remove_dynamic_categories(self): def remove_dynamic_categories(self):
for key in list(self._tb_cats.keys()): for key in list(self._tb_cats.keys()):
val = self._tb_cats[key] val = self._tb_cats[key]

View File

@ -28,7 +28,7 @@ Command Line Interface
.. image:: ../images/cli.png .. image:: ../images/cli.png
On OS X you have to go to Preferences->Advanced and click install command line On OS X you have to go to Preferences->Advanced->Miscellaneous and click install command line
tools to make the command line tools available. On other platforms, just start tools to make the command line tools available. On other platforms, just start
a terminal and type the command. a terminal and type the command.

View File

@ -11,7 +11,7 @@ Customizing |app|
*recipes* to add new sources of online content to |app| in the Section :ref:`news`. Here, you will learn, *recipes* to add new sources of online content to |app| in the Section :ref:`news`. Here, you will learn,
first, how to use environment variables and *tweaks* to customize |app|'s behavior, and then how to first, how to use environment variables and *tweaks* to customize |app|'s behavior, and then how to
specify your own static resources like icons and templates to override the defaults and finally how to specify your own static resources like icons and templates to override the defaults and finally how to
use *plugins* to add funtionality to |app|. use *plugins* to add functionality to |app|.
.. contents:: .. contents::
:depth: 2 :depth: 2
@ -45,7 +45,7 @@ All static resources are stored in the resources sub-folder of the calibre insta
from the calibre website it will be :file:`/opt/calibre/resources`. These paths can change depending on where you choose to install |app|. from the calibre website it will be :file:`/opt/calibre/resources`. These paths can change depending on where you choose to install |app|.
You should not change the files in this resources folder, as your changes will get overwritten the next time you update |app|. Instead, go to You should not change the files in this resources folder, as your changes will get overwritten the next time you update |app|. Instead, go to
:guilabel:`Preferences->Advanced` and click :guilabel:`Open calibre configuration directory`. In this configuration directory, create a sub-folder called resources and place the files you want to override in it. Place the files in the appropriate sub folders, for example place images in :file:`resources/images`, etc. :guilabel:`Preferences->Advanced->Miscellaneous` and click :guilabel:`Open calibre configuration directory`. In this configuration directory, create a sub-folder called resources and place the files you want to override in it. Place the files in the appropriate sub folders, for example place images in :file:`resources/images`, etc.
|app| will automatically use your custom file in preference to the builtin one the next time it is started. |app| will automatically use your custom file in preference to the builtin one the next time it is started.
For example, if you wanted to change the icon for the :guilabel:`Remove books` action, you would first look in the builtin resources folder and see that the relevant file is For example, if you wanted to change the icon for the :guilabel:`Remove books` action, you would first look in the builtin resources folder and see that the relevant file is

View File

@ -123,7 +123,7 @@ the previously checked out calibre code directory, for example::
cd /Users/kovid/work/calibre cd /Users/kovid/work/calibre
calibre is the directory that contains the src and resources sub directories. Ensure you have installed the |app| commandline tools via Preferences->Advanced in the |app| GUI. calibre is the directory that contains the src and resources sub directories. Ensure you have installed the |app| commandline tools via :guilabel:Preferences->Advanced->Miscellaneous in the |app| GUI.
The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path to the src directory. The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path to the src directory.
So, following the example above, it would be ``/Users/kovid/work/calibre/src``. Apple So, following the example above, it would be ``/Users/kovid/work/calibre/src``. Apple

View File

@ -62,7 +62,7 @@ How do I convert my file containing non-English characters, or smart quotes?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two aspects to this problem: There are two aspects to this problem:
1. Knowing the encoding of the source file: |app| tries to guess what character encoding your source files use, but often, this is impossible, so you need to tell it what encoding to use. This can be done in the GUI via the :guilabel:`Input character encoding` field in the :guilabel:`Look & Feel` section. The command-line tools all have an :option:`--input-encoding` option. 1. Knowing the encoding of the source file: |app| tries to guess what character encoding your source files use, but often, this is impossible, so you need to tell it what encoding to use. This can be done in the GUI via the :guilabel:`Input character encoding` field in the :guilabel:`Look & Feel` section. The command-line tools all have an :option:`--input-encoding` option.
2. When adding HTML files to |app|, you may need to tell |app| what encoding the files are in. To do this go to Preferences->Plugins->File Type plugins and customize the HTML2Zip plugin, telling it what encoding your HTML files are in. Now when you add HTML files to |app| they will be correctly processed. HTML files from different sources often have different encodings, so you may have to change this setting repeatedly. A common encoding for many files from the web is ``cp1252`` and I would suggest you try that first. Note that when converting HTML files, leave the input encoding setting mentioned above blank. This is because the HTML2ZIP plugin automatically converts the HTML files to a standard encoding (utf-8). 2. When adding HTML files to |app|, you may need to tell |app| what encoding the files are in. To do this go to :guilabel:`Preferences->Advanced->Plugins->File Type plugins` and customize the HTML2Zip plugin, telling it what encoding your HTML files are in. Now when you add HTML files to |app| they will be correctly processed. HTML files from different sources often have different encodings, so you may have to change this setting repeatedly. A common encoding for many files from the web is ``cp1252`` and I would suggest you try that first. Note that when converting HTML files, leave the input encoding setting mentioned above blank. This is because the HTML2ZIP plugin automatically converts the HTML files to a standard encoding (utf-8).
3. Embedding fonts: If you are generating an LRF file to read on your SONY Reader, you are limited by the fact that the Reader only supports a few non-English characters in the fonts it comes pre-loaded with. You can work around this problem by embedding a unicode-aware font that supports the character set your file uses into the LRF file. You should embed atleast a serif and a sans-serif font. Be aware that embedding fonts significantly slows down page-turn speed on the reader. 3. Embedding fonts: If you are generating an LRF file to read on your SONY Reader, you are limited by the fact that the Reader only supports a few non-English characters in the fonts it comes pre-loaded with. You can work around this problem by embedding a unicode-aware font that supports the character set your file uses into the LRF file. You should embed atleast a serif and a sans-serif font. Be aware that embedding fonts significantly slows down page-turn speed on the reader.
@ -92,7 +92,7 @@ We just need some information from you:
* What e-book formats does your device support? * What e-book formats does your device support?
* Is there a special directory on the device in which all e-book files should be placed? * Is there a special directory on the device in which all e-book files should be placed?
* We also need information about your device that |app| will collect automatically. First, if your * We also need information about your device that |app| will collect automatically. First, if your
device supports SD cards, insert them. Then connect your device. In calibre go to Preferences->Advanced device supports SD cards, insert them. Then connect your device. In calibre go to :guilabel:`Preferences->Advanced->Miscellaneous`
and click the "Debug device detection" button. This will create some debug output. Copy it to a file and click the "Debug device detection" button. This will create some debug output. Copy it to a file
and repeat the process, this time with your device disconnected. and repeat the process, this time with your device disconnected.
* Send both the above outputs to us with the other information and we will write a device driver for your * Send both the above outputs to us with the other information and we will write a device driver for your
@ -109,11 +109,11 @@ of which books are members are shown on the device view.
When you send a book to the reader, |app| will add the book to collections based on the metadata for that book. By When you send a book to the reader, |app| will add the book to collections based on the metadata for that book. By
default, collections are created from tags and series. You can control what metadata is used by going to default, collections are created from tags and series. You can control what metadata is used by going to
Preferences->Plugins->Device Interface plugins and customizing the SONY device interface plugin. If you remove all :guilabel:`Preferences->Advanced->Plugins->Device Interface plugins` and customizing the SONY device interface plugin. If you remove all
values, |app| will not add the book to any collection. values, |app| will not add the book to any collection.
Collection management is largely controlled by the 'Metadata management' option found at Collection management is largely controlled by the 'Metadata management' option found at
Preferences->Add/Save->Sending to device. If set to 'Manual' (the default), managing collections is left to :guilabel:`Preferences->Import/Export->Sending books to devices`. If set to 'Manual' (the default), managing collections is left to
the user; |app| will not delete already existing collections for a book on your reader when you resend the the user; |app| will not delete already existing collections for a book on your reader when you resend the
book to the reader, but |app| will add the book to collections if necessary. To ensure that the collections book to the reader, but |app| will add the book to collections if necessary. To ensure that the collections
for a book are based only on current |app| metadata, first delete the books from the reader, then resend the for a book are based only on current |app| metadata, first delete the books from the reader, then resend the
@ -185,8 +185,8 @@ The easiest way to browse your |app| collection on your Apple device (iPad/iPhon
First perform the following steps in |app| First perform the following steps in |app|
* Set the Preferred Output Format in |app| to EPUB (The output format can be set under Preferences->General) * Set the Preferred Output Format in |app| to EPUB (The output format can be set under :guilabel:`Preferences->Interface->Behavior`)
* Set the output profile to iPad (this will work for iPhone/iPods as well), under Preferences->Conversion->Page Setup * Set the output profile to iPad (this will work for iPhone/iPods as well), under :guilabel:`Preferences->Conversion->Common Options->Page Setup`
* Convert the books you want to read on your iPhone to EPUB format by selecting them and clicking the Convert button. * Convert the books you want to read on your iPhone to EPUB format by selecting them and clicking the Convert button.
* Turn on the Content Server in |app|'s preferences and leave |app| running. * Turn on the Content Server in |app|'s preferences and leave |app| running.
@ -217,7 +217,7 @@ Can I access my |app| books using the web browser in my Kindle or other reading
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|app| has a *Content Server* that exports the books in |app| as a web page. You can turn it on under |app| has a *Content Server* that exports the books in |app| as a web page. You can turn it on under
Preferences->Content Server. Then just point the web browser on your device to the computer running :guilabel:`Preferences->Network->Sharing over the net`. Then just point the web browser on your device to the computer running
the Content Server and you will be able to browse your book collection. For example, if the computer running the Content Server and you will be able to browse your book collection. For example, if the computer running
the server has IP address 63.45.128.5, in the browser, you would type:: the server has IP address 63.45.128.5, in the browser, you would type::
@ -277,14 +277,14 @@ In |app|, you would instead use tags to mark genre and read status and then just
Why doesn't |app| have a column for foo? Why doesn't |app| have a column for foo?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|app| is designed to have columns for the most frequently and widely used fields. In addition, you can add any columns you like. Columns can be added via Preferences->Interface. |app| is designed to have columns for the most frequently and widely used fields. In addition, you can add any columns you like. Columns can be added via :guilabel:`Preferences->Interface->Add your own columns`.
Watch the tutorial `UI Power tips <http://calibre-ebook.com/demo#tutorials>`_ to learn how to create your own columns. Watch the tutorial `UI Power tips <http://calibre-ebook.com/demo#tutorials>`_ to learn how to create your own columns.
How do I move my |app| library from one computer to another? How do I move my |app| library from one computer to another?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Simply copy the |app| library folder from the old to the new computer. You can find out what the library folder is by clicking the calibre icon in the toolbar. The very first item is the path to the library folder. Now on the new computer, start |app| for the first time. It will run the Welcome Wizard asking you for the location of the |app| library. Point it to the previously copied folder. Simply copy the |app| library folder from the old to the new computer. You can find out what the library folder is by clicking the calibre icon in the toolbar. The very first item is the path to the library folder. Now on the new computer, start |app| for the first time. It will run the Welcome Wizard asking you for the location of the |app| library. Point it to the previously copied folder.
Note that if you are transferring between different types of computers (for example Windows to OS X) then after doing the above you should also go to Preferences->Advanced and click the Check database integrity button. It will warn you about missing files, if any, which you should then transfer by hand. Note that if you are transferring between different types of computers (for example Windows to OS X) then after doing the above you should also go to :guilabel:`Preferences->Advanced->Miscellaneous` and click the "Check database integrity button". It will warn you about missing files, if any, which you should then transfer by hand.
Content From The Web Content From The Web

View File

@ -345,6 +345,8 @@ Calibre has several keyboard shortcuts to save you time and mouse movement. Thes
- Show book details - Show book details
* - :kbd:`M` * - :kbd:`M`
- Merge selected records - Merge selected records
* - :kbd:`Alt+M`
- Merge selected records, keeping originals
* - :kbd:`O` * - :kbd:`O`
- Open containing folder - Open containing folder
* - :kbd:`S` * - :kbd:`S`

View File

@ -165,3 +165,19 @@ User Interface Actions
:members: :members:
:member-order: bysource :member-order: bysource
Preferences Plugins
--------------------------
.. autoclass:: calibre.customize.PreferencesPlugin
:show-inheritance:
:members:
:member-order: bysource
.. autoclass:: calibre.gui2.preferences.ConfigWidgetInterface
:members:
:member-order: bysource
.. autoclass:: calibre.gui2.preferences.ConfigWidgetBase
:members:
:member-order: bysource

View File

@ -46,8 +46,8 @@ The steps required to prepare the USB stick are as follows:
* Deselect the options for creating Menu shortcuts; creating a calibre shortcut on the desktop; and adding Calibre to the path * Deselect the options for creating Menu shortcuts; creating a calibre shortcut on the desktop; and adding Calibre to the path
* Create the CalibreLibrary folder inside the Calibre_Root_Folder. If you have an existing Calibre library copy it and all its contents to the CalibreLibrary folder. If you do not already have a library do not worry as a new one will be created at this location when Calibre is started. * Create the CalibreLibrary folder inside the Calibre_Root_Folder. If you have an existing Calibre library copy it and all its contents to the CalibreLibrary folder. If you do not already have a library do not worry as a new one will be created at this location when Calibre is started.
* Create the CalibreConfig folder inside the Calibre_Root_Folder. This will hold your personal Calibre configuration settings. If you have an existing Calibre installation and want to copy the current settings then copy the contents of your current configuration folder to the CalibreConfig folder. You can find the location of your current configuration folder by going to Preferences->Advanced and clicking the “Open calibre configuration Directory” button. * Create the CalibreConfig folder inside the Calibre_Root_Folder. This will hold your personal Calibre configuration settings. If you have an existing Calibre installation and want to copy the current settings then copy the contents of your current configuration folder to the CalibreConfig folder. You can find the location of your current configuration folder by going to :guilabel:`Preferences->Advanced->Miscellaneous` and clicking the “Open calibre configuration Directory” button.
* When you have started Calibre, go into Preferences->General and check that you have set the Job Priority to Low. This setting keeps single-processor Windows systems responsive without affecting Calibre performance to any noticeable degree. On multi-processor or multi-core systems this setting does not matter as much, but setting it will do no harm. * When you have started Calibre, go into :guilabel:`Preferences->Interface->Behavior` and check that you have set the Job Priority to Low. This setting keeps single-processor Windows systems responsive without affecting Calibre performance to any noticeable degree. On multi-processor or multi-core systems this setting does not matter as much, but setting it will do no harm.
Using calibre-portable.bat Using calibre-portable.bat
--------------------------- ---------------------------

View File

@ -18,7 +18,7 @@ Starting the viewer
You can view any of the books in your |app| library by selecting the book and pressing the View button. This You can view any of the books in your |app| library by selecting the book and pressing the View button. This
will open up the book in the e-book viewer. You can also launch the viewer by itself, from the Start menu in windows will open up the book in the e-book viewer. You can also launch the viewer by itself, from the Start menu in windows
or using the command :command:`ebook-viewer` in Linux and OS X (you have to install the command line tools on OS X or using the command :command:`ebook-viewer` in Linux and OS X (you have to install the command line tools on OS X
first by going to Preferences->Advanced). first by going to :guilabel:`Preferences->Advanced->Miscellaneous`).
Navigating around an e-book Navigating around an e-book
----------------------------- -----------------------------

View File

@ -5,8 +5,8 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: calibre 0.7.17\n" "Project-Id-Version: calibre 0.7.17\n"
"POT-Creation-Date: 2010-09-05 17:35+MDT\n" "POT-Creation-Date: 2010-09-07 13:11+MDT\n"
"PO-Revision-Date: 2010-09-05 17:35+MDT\n" "PO-Revision-Date: 2010-09-07 13:11+MDT\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: LANGUAGE\n" "Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -103,8 +103,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:249 #: /home/kovid/work/calibre/src/calibre/ebooks/rtf/input.py:249
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:323 #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:323
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:330 #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:330
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:289 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:290
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:292 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:293
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:137 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:137
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:144 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:144
#: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42 #: /home/kovid/work/calibre/src/calibre/gui2/convert/__init__.py:42
@ -129,13 +129,13 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:186 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:186
#: /home/kovid/work/calibre/src/calibre/library/cli.py:213 #: /home/kovid/work/calibre/src/calibre/library/cli.py:213
#: /home/kovid/work/calibre/src/calibre/library/database.py:913 #: /home/kovid/work/calibre/src/calibre/library/database.py:913
#: /home/kovid/work/calibre/src/calibre/library/database2.py:375 #: /home/kovid/work/calibre/src/calibre/library/database2.py:376
#: /home/kovid/work/calibre/src/calibre/library/database2.py:387 #: /home/kovid/work/calibre/src/calibre/library/database2.py:388
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1057 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1059
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1126 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1128
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1825 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1826
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1827 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1828
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1954 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1955
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:211 #: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:211
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:137 #: /home/kovid/work/calibre/src/calibre/library/server/opds.py:137
#: /home/kovid/work/calibre/src/calibre/library/server/opds.py:140 #: /home/kovid/work/calibre/src/calibre/library/server/opds.py:140
@ -173,12 +173,12 @@ msgstr ""
msgid "User Interface Action" msgid "User Interface Action"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:376 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:383
#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:17 #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:17
#: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:22 #: /home/kovid/work/calibre/src/calibre/gui2/actions/preferences.py:22
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:150 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:187
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:213 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:251
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:234 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:273
#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:206 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:206
msgid "Preferences" msgid "Preferences"
msgstr "" msgstr ""
@ -642,9 +642,9 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:823 #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:823
#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:851 #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:851
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:244 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:244
#: /home/kovid/work/calibre/src/calibre/library/database2.py:192 #: /home/kovid/work/calibre/src/calibre/library/database2.py:193
#: /home/kovid/work/calibre/src/calibre/library/database2.py:205 #: /home/kovid/work/calibre/src/calibre/library/database2.py:206
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1694 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1695
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:132 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:132
msgid "News" msgid "News"
msgstr "" msgstr ""
@ -2767,10 +2767,10 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31 #: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:31
#: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:86
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:115 #: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:115
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:74 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:75
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:140 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:141
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:176 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:177
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:203 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:204
#: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:91 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:91
msgid "No books selected" msgid "No books selected"
msgstr "" msgstr ""
@ -2888,7 +2888,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:186 #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:186
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:53
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:100 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:102
msgid "Are you sure?" msgid "Are you sure?"
msgstr "" msgstr ""
@ -3060,7 +3060,7 @@ msgid "Deleting books from device."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:160
msgid "The selected books will be <b>permanently deleted</b> and the files removed from your computer. Are you sure?" msgid "The selected books will be <b>permanently deleted</b> and the files removed from your calibre library. Are you sure?"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:179 #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:179
@ -3166,39 +3166,39 @@ msgstr ""
msgid "Merge into first selected book - keep others" msgid "Merge into first selected book - keep others"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:73 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:74
msgid "Cannot download metadata" msgid "Cannot download metadata"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:96 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:97
msgid "social metadata" msgid "social metadata"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:99
msgid "covers" msgid "covers"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:99
msgid "metadata" msgid "metadata"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:103 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:104
msgid "Downloading %s for %d book(s)" msgid "Downloading %s for %d book(s)"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:124 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:125
msgid "Failed to download some metadata" msgid "Failed to download some metadata"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:125 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:126
msgid "Failed to download metadata for the following:" msgid "Failed to download metadata for the following:"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:129
msgid "Failed to download metadata:" msgid "Failed to download metadata:"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:129 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:130
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:607 #: /home/kovid/work/calibre/src/calibre/gui2/device.py:607
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:65
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:112 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:112
@ -3206,29 +3206,29 @@ msgstr ""
msgid "Error" msgid "Error"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:139 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:140
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:175 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:176
msgid "Cannot edit metadata" msgid "Cannot edit metadata"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:202 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:203
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:205 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:206
msgid "Cannot merge books" msgid "Cannot merge books"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:206 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:207
msgid "At least two books must be selected for merging" msgid "At least two books must be selected for merging"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:210 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:211
msgid "All book formats and metadata from the selected books will be added to the <b>first selected book.</b><br><br> The second and subsequently selected books will not be deleted or changed.<br><br>Please confirm you want to proceed." msgid "All book formats and metadata from the selected books will be added to the <b>first selected book.</b><br><br> The second and subsequently selected books will not be deleted or changed.<br><br>Please confirm you want to proceed."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:221 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:222
msgid "All book formats and metadata from the selected books will be merged into the <b>first selected book</b>.<br><br>After merger the second and subsequently selected books will be <b>deleted</b>. <br><br>All book formats of the first selected book will be kept and any duplicate formats in the second and subsequently selected books will be permanently <b>deleted</b> from your computer.<br><br> Are you <b>sure</b> you want to proceed?" msgid "All book formats and metadata from the selected books will be merged into the <b>first selected book</b>.<br><br>After merger the second and subsequently selected books will be <b>deleted</b>. <br><br>All book formats of the first selected book will be kept and any duplicate formats in the second and subsequently selected books will be permanently <b>deleted</b> from your computer.<br><br> Are you <b>sure</b> you want to proceed?"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:233 #: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:234
msgid "You are about to merge more than 5 books. Are you <b>sure</b> you want to proceed?" msgid "You are about to merge more than 5 books. Are you <b>sure</b> you want to proceed?"
msgstr "" msgstr ""
@ -3852,8 +3852,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:20 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:20
#: /home/kovid/work/calibre/src/calibre/library/catalog.py:550 #: /home/kovid/work/calibre/src/calibre/library/catalog.py:550
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1657 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1658
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1675 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1676
msgid "Catalog" msgid "Catalog"
msgstr "" msgstr ""
@ -5738,7 +5738,7 @@ msgid "Reset cover to default"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:410
msgid "Download &cover" msgid "Download co&ver"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:411 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:411
@ -6481,12 +6481,12 @@ msgid "Shift+Alt+T"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/init.py:138 #: /home/kovid/work/calibre/src/calibre/gui2/init.py:138
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:26 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:29
msgid "version" msgid "version"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/init.py:139 #: /home/kovid/work/calibre/src/calibre/gui2/init.py:139
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:27 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:30
msgid "created by Kovid Goyal" msgid "created by Kovid Goyal"
msgstr "" msgstr ""
@ -6494,20 +6494,20 @@ msgstr ""
msgid "Connected " msgid "Connected "
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/init.py:166 #: /home/kovid/work/calibre/src/calibre/gui2/init.py:169
msgid "Update found" msgid "Update found"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/init.py:210 #: /home/kovid/work/calibre/src/calibre/gui2/init.py:204
#: /home/kovid/work/calibre/src/calibre/gui2/init.py:220 #: /home/kovid/work/calibre/src/calibre/gui2/init.py:214
msgid "Book Details" msgid "Book Details"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/init.py:212 #: /home/kovid/work/calibre/src/calibre/gui2/init.py:206
msgid "Alt+D" msgid "Alt+D"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/init.py:222 #: /home/kovid/work/calibre/src/calibre/gui2/init.py:216
msgid "Shift+Alt+D" msgid "Shift+Alt+D"
msgstr "" msgstr ""
@ -6596,7 +6596,7 @@ msgid "Show books in the main memory of the device"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:66 #: /home/kovid/work/calibre/src/calibre/gui2/layout.py:66
#: /home/kovid/work/calibre/src/calibre/library/database2.py:648 #: /home/kovid/work/calibre/src/calibre/library/database2.py:650
msgid "Card A" msgid "Card A"
msgstr "" msgstr ""
@ -6605,7 +6605,7 @@ msgid "Show books in storage card A"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68 #: /home/kovid/work/calibre/src/calibre/gui2/layout.py:68
#: /home/kovid/work/calibre/src/calibre/library/database2.py:650 #: /home/kovid/work/calibre/src/calibre/library/database2.py:652
msgid "Card B" msgid "Card B"
msgstr "" msgstr ""
@ -6946,7 +6946,7 @@ msgstr ""
msgid "No matches found for this book" msgid "No matches found for this book"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:23 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/__init__.py:36
msgid "Restore settings to default values. You have to click Apply to actually save the default settings." msgid "Restore settings to default values. You have to click Apply to actually save the default settings."
msgstr "" msgstr ""
@ -7073,15 +7073,15 @@ msgstr ""
msgid "Use internal &viewer for:" msgid "Use internal &viewer for:"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:94 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:96
msgid "You must select a column to delete it" msgid "You must select a column to delete it"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:99 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:101
msgid "The selected column is not a custom column" msgid "The selected column is not a custom column"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:101 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:103
msgid "Do you really want to delete column %s and all its data?" msgid "Do you really want to delete column %s and all its data?"
msgstr "" msgstr ""
@ -7405,37 +7405,41 @@ msgstr ""
msgid "Show &text under icons:" msgid "Show &text under icons:"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:172 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:215
msgid "&Apply" msgid "&Apply"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:175 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:218
msgid "&Cancel" msgid "&Cancel"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:184 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:222
msgid "Restore &defaults" msgid "Restore &defaults"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:185 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:223
msgid "Save changes" msgid "Save changes"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:186 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:224
msgid "Cancel and return to overview" msgid "Cancel and return to overview"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:221 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:259
msgid "Restoring to defaults not supported for" msgid "Restoring to defaults not supported for"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:247 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:294
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:120 msgid "Some of the changes you made require a restart. Please restart calibre as soon as possible."
msgid "Restart needed"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:248 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:297
msgid "Some of the changes you made require a restart. Please restart calibre as soon as possible." msgid "The changes you have made require calibre be restarted immediately. You will not be allowed set any more preferences, until you restart."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:302
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:120
msgid "Restart needed"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:49
@ -7830,7 +7834,7 @@ msgid "The tweaks you entered are invalid, try resetting the tweaks to default a
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:50 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:50
msgid "Values for the tweaks are shown below. Edit them to change the behavior of calibre" msgid "Values for the tweaks are shown below. Edit them to change the behavior of calibre. Your changes will only take effect after a restart of calibre."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:51 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks_ui.py:51
@ -7975,7 +7979,7 @@ msgid "Manage User Categories"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:435 #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:435
#: /home/kovid/work/calibre/src/calibre/library/database2.py:300 #: /home/kovid/work/calibre/src/calibre/library/database2.py:301
msgid "Searches" msgid "Searches"
msgstr "" msgstr ""
@ -8110,7 +8114,7 @@ msgstr ""
msgid "WARNING: Active jobs" msgid "WARNING: Active jobs"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/ui.py:573 #: /home/kovid/work/calibre/src/calibre/gui2/ui.py:579
msgid "will keep running in the system tray. To close it, choose <b>Quit</b> in the context menu of the system tray." msgid "will keep running in the system tray. To close it, choose <b>Quit</b> in the context menu of the system tray."
msgstr "" msgstr ""
@ -9133,33 +9137,33 @@ msgstr ""
msgid "You must specify at least one file to add" msgid "You must specify at least one file to add"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:329 #: /home/kovid/work/calibre/src/calibre/library/cli.py:330
msgid "" msgid ""
"%prog remove ids\n" "%prog remove ids\n"
"\n" "\n"
"Remove the books identified by ids from the database. ids should be a comma separated list of id numbers (you can get id numbers by using the list command). For example, 23,34,57-85\n" "Remove the books identified by ids from the database. ids should be a comma separated list of id numbers (you can get id numbers by using the list command). For example, 23,34,57-85\n"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:344 #: /home/kovid/work/calibre/src/calibre/library/cli.py:345
msgid "You must specify at least one book to remove" msgid "You must specify at least one book to remove"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:363 #: /home/kovid/work/calibre/src/calibre/library/cli.py:364
msgid "" msgid ""
"%prog add_format [options] id ebook_file\n" "%prog add_format [options] id ebook_file\n"
"\n" "\n"
"Add the ebook in ebook_file to the available formats for the logical book identified by id. You can get id by using the list command. If the format already exists, it is replaced.\n" "Add the ebook in ebook_file to the available formats for the logical book identified by id. You can get id by using the list command. If the format already exists, it is replaced.\n"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:378 #: /home/kovid/work/calibre/src/calibre/library/cli.py:379
msgid "You must specify an id and an ebook file" msgid "You must specify an id and an ebook file"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:383 #: /home/kovid/work/calibre/src/calibre/library/cli.py:384
msgid "ebook file must have an extension" msgid "ebook file must have an extension"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:391 #: /home/kovid/work/calibre/src/calibre/library/cli.py:392
msgid "" msgid ""
"\n" "\n"
"%prog remove_format [options] id fmt\n" "%prog remove_format [options] id fmt\n"
@ -9167,11 +9171,11 @@ msgid ""
"Remove the format fmt from the logical book identified by id. You can get id by using the list command. fmt should be a file extension like LRF or TXT or EPUB. If the logical book does not have fmt available, do nothing.\n" "Remove the format fmt from the logical book identified by id. You can get id by using the list command. fmt should be a file extension like LRF or TXT or EPUB. If the logical book does not have fmt available, do nothing.\n"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:408 #: /home/kovid/work/calibre/src/calibre/library/cli.py:409
msgid "You must specify an id and a format" msgid "You must specify an id and a format"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:426 #: /home/kovid/work/calibre/src/calibre/library/cli.py:427
msgid "" msgid ""
"\n" "\n"
"%prog show_metadata [options] id\n" "%prog show_metadata [options] id\n"
@ -9180,15 +9184,15 @@ msgid ""
"id is an id number from the list command.\n" "id is an id number from the list command.\n"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:434 #: /home/kovid/work/calibre/src/calibre/library/cli.py:435
msgid "Print metadata in OPF form (XML)" msgid "Print metadata in OPF form (XML)"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:443 #: /home/kovid/work/calibre/src/calibre/library/cli.py:444
msgid "You must specify an id" msgid "You must specify an id"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:456 #: /home/kovid/work/calibre/src/calibre/library/cli.py:458
msgid "" msgid ""
"\n" "\n"
"%prog set_metadata [options] id /path/to/metadata.opf\n" "%prog set_metadata [options] id /path/to/metadata.opf\n"
@ -9199,11 +9203,11 @@ msgid ""
"show_metadata command.\n" "show_metadata command.\n"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:472 #: /home/kovid/work/calibre/src/calibre/library/cli.py:474
msgid "You must specify an id and a metadata file" msgid "You must specify an id and a metadata file"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:492 #: /home/kovid/work/calibre/src/calibre/library/cli.py:494
msgid "" msgid ""
"%prog export [options] ids\n" "%prog export [options] ids\n"
"\n" "\n"
@ -9212,27 +9216,27 @@ msgid ""
"an opf file). You can get id numbers from the list command.\n" "an opf file). You can get id numbers from the list command.\n"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:500 #: /home/kovid/work/calibre/src/calibre/library/cli.py:502
msgid "Export all books in database, ignoring the list of ids." msgid "Export all books in database, ignoring the list of ids."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:502 #: /home/kovid/work/calibre/src/calibre/library/cli.py:504
msgid "Export books to the specified directory. Default is" msgid "Export books to the specified directory. Default is"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:504 #: /home/kovid/work/calibre/src/calibre/library/cli.py:506
msgid "Export all books into a single directory" msgid "Export all books into a single directory"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:511 #: /home/kovid/work/calibre/src/calibre/library/cli.py:513
msgid "Specifying this switch will turn this behavior off." msgid "Specifying this switch will turn this behavior off."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:534 #: /home/kovid/work/calibre/src/calibre/library/cli.py:536
msgid "You must specify some ids or the %s option" msgid "You must specify some ids or the %s option"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:547 #: /home/kovid/work/calibre/src/calibre/library/cli.py:549
msgid "" msgid ""
"%prog add_custom_column [options] label name datatype\n" "%prog add_custom_column [options] label name datatype\n"
"\n" "\n"
@ -9241,19 +9245,19 @@ msgid ""
"datatype is one of: {0}\n" "datatype is one of: {0}\n"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:556 #: /home/kovid/work/calibre/src/calibre/library/cli.py:558
msgid "This column stores tag like data (i.e. multiple comma separated values). Only applies if datatype is text." msgid "This column stores tag like data (i.e. multiple comma separated values). Only applies if datatype is text."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:560 #: /home/kovid/work/calibre/src/calibre/library/cli.py:562
msgid "A dictionary of options to customize how the data in this column will be interpreted." msgid "A dictionary of options to customize how the data in this column will be interpreted."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:573 #: /home/kovid/work/calibre/src/calibre/library/cli.py:575
msgid "You must specify label, name and datatype" msgid "You must specify label, name and datatype"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:631 #: /home/kovid/work/calibre/src/calibre/library/cli.py:633
msgid "" msgid ""
"\n" "\n"
" %prog catalog /path/to/destination.(csv|epub|mobi|xml ...) [options]\n" " %prog catalog /path/to/destination.(csv|epub|mobi|xml ...) [options]\n"
@ -9263,29 +9267,29 @@ msgid ""
" " " "
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:645 #: /home/kovid/work/calibre/src/calibre/library/cli.py:647
msgid "" msgid ""
"Comma-separated list of database IDs to catalog.\n" "Comma-separated list of database IDs to catalog.\n"
"If declared, --search is ignored.\n" "If declared, --search is ignored.\n"
"Default: all" "Default: all"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:649 #: /home/kovid/work/calibre/src/calibre/library/cli.py:651
msgid "" msgid ""
"Filter the results by the search query. For the format of the search query, please see the search-related documentation in the User Manual.\n" "Filter the results by the search query. For the format of the search query, please see the search-related documentation in the User Manual.\n"
"Default: no filtering" "Default: no filtering"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:655 #: /home/kovid/work/calibre/src/calibre/library/cli.py:657
#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:505 #: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:505
msgid "Show detailed output information. Useful for debugging" msgid "Show detailed output information. Useful for debugging"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:668 #: /home/kovid/work/calibre/src/calibre/library/cli.py:670
msgid "Error: You must specify a catalog output file" msgid "Error: You must specify a catalog output file"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:710 #: /home/kovid/work/calibre/src/calibre/library/cli.py:712
msgid "" msgid ""
"\n" "\n"
" %prog set_custom [options] column id value\n" " %prog set_custom [options] column id value\n"
@ -9297,15 +9301,15 @@ msgid ""
" " " "
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:721 #: /home/kovid/work/calibre/src/calibre/library/cli.py:723
msgid "If the column stores multiple values, append the specified values to the existing ones, instead of replacing them." msgid "If the column stores multiple values, append the specified values to the existing ones, instead of replacing them."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:732 #: /home/kovid/work/calibre/src/calibre/library/cli.py:734
msgid "Error: You must specify a field name, id and value" msgid "Error: You must specify a field name, id and value"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:751 #: /home/kovid/work/calibre/src/calibre/library/cli.py:753
msgid "" msgid ""
"\n" "\n"
" %prog custom_columns [options]\n" " %prog custom_columns [options]\n"
@ -9314,19 +9318,19 @@ msgid ""
" " " "
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:758 #: /home/kovid/work/calibre/src/calibre/library/cli.py:760
msgid "Show details for each column." msgid "Show details for each column."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:770 #: /home/kovid/work/calibre/src/calibre/library/cli.py:772
msgid "You will lose all data in the column: %r. Are you sure (y/n)? " msgid "You will lose all data in the column: %r. Are you sure (y/n)? "
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:772 #: /home/kovid/work/calibre/src/calibre/library/cli.py:774
msgid "y" msgid "y"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:778 #: /home/kovid/work/calibre/src/calibre/library/cli.py:780
msgid "" msgid ""
"\n" "\n"
" %prog remove_custom_column [options] label\n" " %prog remove_custom_column [options] label\n"
@ -9336,15 +9340,15 @@ msgid ""
" " " "
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:786 #: /home/kovid/work/calibre/src/calibre/library/cli.py:788
msgid "Do not ask for confirmation" msgid "Do not ask for confirmation"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:796 #: /home/kovid/work/calibre/src/calibre/library/cli.py:798
msgid "Error: You must specify a column label" msgid "Error: You must specify a column label"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:803 #: /home/kovid/work/calibre/src/calibre/library/cli.py:805
msgid "" msgid ""
"\n" "\n"
" %prog saved_searches [options] list\n" " %prog saved_searches [options] list\n"
@ -9357,39 +9361,39 @@ msgid ""
" " " "
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:821 #: /home/kovid/work/calibre/src/calibre/library/cli.py:823
msgid "Error: You must specify an action (add|remove|list)" msgid "Error: You must specify an action (add|remove|list)"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:829 #: /home/kovid/work/calibre/src/calibre/library/cli.py:831
msgid "Name:" msgid "Name:"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:830 #: /home/kovid/work/calibre/src/calibre/library/cli.py:832
msgid "Search string:" msgid "Search string:"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:836 #: /home/kovid/work/calibre/src/calibre/library/cli.py:838
msgid "Error: You must specify a name and a search string" msgid "Error: You must specify a name and a search string"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:839 #: /home/kovid/work/calibre/src/calibre/library/cli.py:841
msgid "added" msgid "added"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:844 #: /home/kovid/work/calibre/src/calibre/library/cli.py:846
msgid "Error: You must specify a name" msgid "Error: You must specify a name"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:847 #: /home/kovid/work/calibre/src/calibre/library/cli.py:849
msgid "removed" msgid "removed"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:851 #: /home/kovid/work/calibre/src/calibre/library/cli.py:853
msgid "Error: Action %s not recognized, must be one of: (add|remove|list)" msgid "Error: Action %s not recognized, must be one of: (add|remove|list)"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/cli.py:865 #: /home/kovid/work/calibre/src/calibre/library/cli.py:867
msgid "" msgid ""
"%%prog command [options] [arguments]\n" "%%prog command [options] [arguments]\n"
"\n" "\n"
@ -9413,31 +9417,31 @@ msgstr ""
msgid "%sAverage rating is %3.1f" msgid "%sAverage rating is %3.1f"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:646 #: /home/kovid/work/calibre/src/calibre/library/database2.py:648
msgid "Main" msgid "Main"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:1980 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1981
msgid "<p>Migrating old database to ebook library in %s<br><center>" msgid "<p>Migrating old database to ebook library in %s<br><center>"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2009 #: /home/kovid/work/calibre/src/calibre/library/database2.py:2010
msgid "Copying <b>%s</b>" msgid "Copying <b>%s</b>"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2026 #: /home/kovid/work/calibre/src/calibre/library/database2.py:2027
msgid "Compacting database" msgid "Compacting database"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2119 #: /home/kovid/work/calibre/src/calibre/library/database2.py:2120
msgid "Checking SQL integrity..." msgid "Checking SQL integrity..."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2160 #: /home/kovid/work/calibre/src/calibre/library/database2.py:2159
msgid "Checking for missing files." msgid "Checking for missing files."
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2182 #: /home/kovid/work/calibre/src/calibre/library/database2.py:2181
msgid "Checked id" msgid "Checked id"
msgstr "" msgstr ""
@ -9833,7 +9837,7 @@ msgstr ""
msgid "Failed to authenticate with server: %s" msgid "Failed to authenticate with server: %s"
msgstr "" msgstr ""
#: /home/kovid/work/calibre/src/calibre/utils/smtp.py:229 #: /home/kovid/work/calibre/src/calibre/utils/smtp.py:230
msgid "Control email delivery" msgid "Control email delivery"
msgstr "" msgstr ""

View File

@ -50,8 +50,9 @@ def get_mx(host, verbose=0):
if verbose: if verbose:
print 'Find mail exchanger for', host print 'Find mail exchanger for', host
answers = list(dns.resolver.query(host, 'MX')) answers = list(dns.resolver.query(host, 'MX'))
answers.sort(cmp=lambda x, y: cmp(int(x.preference), int(y.preference))) answers.sort(cmp=lambda x, y: cmp(int(getattr(x, 'preference', sys.maxint)),
return [str(x.exchange) for x in answers] int(getattr(y, 'preference', sys.maxint))))
return [str(x.exchange) for x in answers if hasattr(x, 'exchange')]
def sendmail_direct(from_, to, msg, timeout, localhost, verbose): def sendmail_direct(from_, to, msg, timeout, localhost, verbose):
import smtplib import smtplib