mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
2a90c640db
39
resources/catalog/section_list_templates.py
Normal file
39
resources/catalog/section_list_templates.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
'''
|
||||||
|
Available fields:
|
||||||
|
{title} Title of the book
|
||||||
|
{series} Series name
|
||||||
|
{series_index} Number of the book in the series
|
||||||
|
{rating} Rating
|
||||||
|
{rating_parens} Rating, in parentheses
|
||||||
|
{pubyear} Year the book was published
|
||||||
|
{pubyear_parens} Year the book was published, in parentheses
|
||||||
|
'''
|
||||||
|
# Books by Author
|
||||||
|
by_authors_normal_title_template = '{title} {pubyear_parens}'
|
||||||
|
by_authors_series_title_template = '[{series_index}] {title} {pubyear_parens}'
|
||||||
|
|
||||||
|
# Books by Title
|
||||||
|
by_titles_normal_title_template = '{title}'
|
||||||
|
by_titles_series_title_template = '{title} ({series} [{series_index}])'
|
||||||
|
|
||||||
|
# Books by Series
|
||||||
|
by_series_title_template = '[{series_index}] {title} {pubyear_parens}'
|
||||||
|
|
||||||
|
# Books by Genre
|
||||||
|
by_genres_normal_title_template = '{title} {pubyear_parens}'
|
||||||
|
by_genres_series_title_template = '{series_index}. {title} {pubyear_parens}'
|
||||||
|
|
||||||
|
# Recently Added
|
||||||
|
by_recently_added_normal_title_template = '{title}'
|
||||||
|
by_recently_added_series_title_template = '{title} ({series} [{series_index}])'
|
||||||
|
|
||||||
|
# By Month added
|
||||||
|
by_month_added_normal_title_template = '{title} {pubyear_parens}'
|
||||||
|
by_month_added_series_title_template = '[{series_index}] {title} {pubyear_parens}'
|
@ -28,7 +28,7 @@ class DilbertBig(BasicNewsRecipe):
|
|||||||
,'publisher' : publisher
|
,'publisher' : publisher
|
||||||
}
|
}
|
||||||
|
|
||||||
feeds = [(u'Dilbert', u'http://feeds.dilbert.com/DilbertDailyStrip' )]
|
feeds = [(u'Dilbert', u'http://feed.dilbert.com/dilbert/daily_strip' )]
|
||||||
|
|
||||||
def get_article_url(self, article):
|
def get_article_url(self, article):
|
||||||
return article.get('feedburner_origlink', None)
|
return article.get('feedburner_origlink', None)
|
||||||
|
@ -9,7 +9,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
|||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||||
from calibre.ebooks.BeautifulSoup import Tag, NavigableString
|
from calibre.ebooks.BeautifulSoup import Tag, NavigableString
|
||||||
|
|
||||||
import mechanize, string, urllib, time, re
|
import string, time, re
|
||||||
|
|
||||||
class Economist(BasicNewsRecipe):
|
class Economist(BasicNewsRecipe):
|
||||||
|
|
||||||
@ -18,19 +18,19 @@ class Economist(BasicNewsRecipe):
|
|||||||
|
|
||||||
__author__ = "Kovid Goyal"
|
__author__ = "Kovid Goyal"
|
||||||
INDEX = 'http://www.economist.com/printedition'
|
INDEX = 'http://www.economist.com/printedition'
|
||||||
description = ('Global news and current affairs from a European perspective.'
|
description = 'Global news and current affairs from a European perspective.'
|
||||||
' Needs a subscription from ')+INDEX
|
|
||||||
|
|
||||||
oldest_article = 7.0
|
oldest_article = 7.0
|
||||||
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
||||||
remove_tags = [dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
|
remove_tags = [dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
|
||||||
dict(attrs={'class':['dblClkTrk', 'ec-article-info']})]
|
dict(attrs={'class':['dblClkTrk', 'ec-article-info']})]
|
||||||
keep_only_tags = [dict(id='ec-article-body')]
|
keep_only_tags = [dict(id='ec-article-body')]
|
||||||
needs_subscription = True
|
needs_subscription = False
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
preprocess_regexps = [(re.compile('</html>.*', re.DOTALL),
|
preprocess_regexps = [(re.compile('</html>.*', re.DOTALL),
|
||||||
lambda x:'</html>')]
|
lambda x:'</html>')]
|
||||||
|
|
||||||
|
'''
|
||||||
def get_browser(self):
|
def get_browser(self):
|
||||||
br = BasicNewsRecipe.get_browser()
|
br = BasicNewsRecipe.get_browser()
|
||||||
br.open('http://www.economist.com')
|
br.open('http://www.economist.com')
|
||||||
@ -50,6 +50,7 @@ class Economist(BasicNewsRecipe):
|
|||||||
}))
|
}))
|
||||||
br.open(req).read()
|
br.open(req).read()
|
||||||
return br
|
return br
|
||||||
|
'''
|
||||||
|
|
||||||
def parse_index(self):
|
def parse_index(self):
|
||||||
try:
|
try:
|
||||||
|
@ -7,12 +7,12 @@ from lxml import html
|
|||||||
|
|
||||||
class Economist(BasicNewsRecipe):
|
class Economist(BasicNewsRecipe):
|
||||||
|
|
||||||
title = 'The Economist (free)'
|
title = 'The Economist (RSS)'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
|
|
||||||
__author__ = "Kovid Goyal"
|
__author__ = "Kovid Goyal"
|
||||||
description = ('Global news and current affairs from a European perspective.'
|
description = ('Global news and current affairs from a European perspective.'
|
||||||
' Much slower than the subscription based version.')
|
' Much slower than the print edition based version.')
|
||||||
|
|
||||||
oldest_article = 7.0
|
oldest_article = 7.0
|
||||||
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from calibre.ebooks.BeautifulSoup import Tag
|
||||||
import re
|
import re
|
||||||
|
|
||||||
class NatureNews(BasicNewsRecipe):
|
class NatureNews(BasicNewsRecipe):
|
||||||
@ -10,17 +11,76 @@ class NatureNews(BasicNewsRecipe):
|
|||||||
max_articles_per_feed = 50
|
max_articles_per_feed = 50
|
||||||
|
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
remove_tags_before = dict(name='h1', attrs={'class':'heading entry-title'})
|
keep_only_tags = [dict(name='div', attrs={'id':'content'})]
|
||||||
remove_tags_after = dict(name='h2', attrs={'id':'comments'})
|
# remove_tags_before = dict(name='h1', attrs={'class':'heading entry-title'})
|
||||||
|
# remove_tags_after = dict(name='h2', attrs={'id':'comments'})
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name='h2', attrs={'id':'comments'}),
|
dict(name='h2', attrs={'id':'comments'}),
|
||||||
dict(attrs={'alt':'Advertisement'}),
|
dict(attrs={'alt':'Advertisement'}),
|
||||||
dict(name='div', attrs={'class':'ad'}),
|
dict(name='div', attrs={'class':'ad'}),
|
||||||
|
dict(attrs={'class':'Z3988'}),
|
||||||
|
dict(attrs={'class':['formatpublished','type-of-article','cleardiv','disclaimer','buttons','comments xoxo']}),
|
||||||
|
dict(name='a', attrs={'href':'#comments'}),
|
||||||
|
dict(name='h2',attrs={'class':'subheading plusicon icon-add-comment'})
|
||||||
]
|
]
|
||||||
|
|
||||||
preprocess_regexps = [
|
preprocess_regexps = [
|
||||||
(re.compile(r'<p>ADVERTISEMENT</p>', re.DOTALL|re.IGNORECASE), lambda match: ''),
|
(re.compile(r'<p>ADVERTISEMENT</p>', re.DOTALL|re.IGNORECASE), lambda match: ''),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
extra_css = '''
|
||||||
|
.author { text-align: right; font-size: small; line-height:1em; margin-top:0px; margin-left:0; margin-right:0; margin-bottom: 0; }
|
||||||
|
.imagedescription { font-size: small; font-style:italic; line-height:1em; margin-top:5px; margin-left:0; margin-right:0; margin-bottom: 0; }
|
||||||
|
.imagecredit { font-size: x-small; font-style: normal; font-weight: bold}
|
||||||
|
'''
|
||||||
|
|
||||||
feeds = [('Nature News', 'http://feeds.nature.com/news/rss/most_recent')]
|
feeds = [('Nature News', 'http://feeds.nature.com/news/rss/most_recent')]
|
||||||
|
|
||||||
|
def preprocess_html(self,soup):
|
||||||
|
# The author name is slightly buried - dig it up
|
||||||
|
author = soup.find('p', {'class':'byline'})
|
||||||
|
if author:
|
||||||
|
# Find out the author's name
|
||||||
|
authornamediv = author.find('span',{'class':'author fn'})
|
||||||
|
authornamelink = authornamediv.find('a')
|
||||||
|
if authornamelink:
|
||||||
|
authorname = authornamelink.contents[0]
|
||||||
|
else:
|
||||||
|
authorname = authornamediv.contents[0]
|
||||||
|
# Stick the author's name in the byline tag
|
||||||
|
tag = Tag(soup,'div')
|
||||||
|
tag['class'] = 'author'
|
||||||
|
tag.insert(0,authorname.strip())
|
||||||
|
author.replaceWith(tag)
|
||||||
|
|
||||||
|
# Change the intro from a p to a div
|
||||||
|
intro = soup.find('p',{'class':'intro'})
|
||||||
|
if intro:
|
||||||
|
tag = Tag(soup,'div')
|
||||||
|
tag['class'] = 'intro'
|
||||||
|
tag.insert(0,intro.contents[0])
|
||||||
|
intro.replaceWith(tag)
|
||||||
|
|
||||||
|
# Change span class=imagedescription to div
|
||||||
|
descr = soup.find('span',{'class':'imagedescription'})
|
||||||
|
if descr:
|
||||||
|
tag = Tag(soup,'div')
|
||||||
|
tag['class'] = 'imagedescription'
|
||||||
|
tag.insert(0,descr.renderContents())
|
||||||
|
descr.replaceWith(tag)
|
||||||
|
|
||||||
|
# The references are in a list, let's make them simpler
|
||||||
|
reflistcont = soup.find('ul',{'id':'article-refrences'})
|
||||||
|
if reflistcont:
|
||||||
|
reflist = reflistcont.li.renderContents()
|
||||||
|
tag = Tag(soup,'div')
|
||||||
|
tag['class'] = 'article-references'
|
||||||
|
tag.insert(0,reflist)
|
||||||
|
reflistcont.replaceWith(tag)
|
||||||
|
|
||||||
|
# Within the id=content div, we need to remove all the stuff after the end of the class=entry-content
|
||||||
|
entrycontent = soup.find('div',{'class':'entry-content'})
|
||||||
|
for nextSibling in entrycontent.findNextSiblings():
|
||||||
|
nextSibling.extract()
|
||||||
|
|
||||||
|
return soup
|
||||||
|
182
resources/recipes/ihned.recipe
Normal file
182
resources/recipes/ihned.recipe
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import re, time
|
||||||
|
from calibre import strftime
|
||||||
|
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||||
|
|
||||||
|
class IHNed(BasicNewsRecipe):
|
||||||
|
|
||||||
|
|
||||||
|
stahnout_vsechny = False
|
||||||
|
#True = stahuje vsechny z homepage
|
||||||
|
#False = stahuje pouze dnesni clanky (ze dne, kdy je skript spusten)
|
||||||
|
|
||||||
|
title = 'iHNed'
|
||||||
|
__author__ = 'Karel Bílek'
|
||||||
|
language = 'cs'
|
||||||
|
description = 'Zprávy z iHNed.cz'
|
||||||
|
timefmt = ' [%a, %d %b, %Y]'
|
||||||
|
needs_subscription = False
|
||||||
|
remove_tags = [dict(attrs={'class':['borderbottom', 'web', 'foot', 'reklama', 'd-elm d-rellinks', 'd-elm']}),
|
||||||
|
dict(style=['text-align: center;']),
|
||||||
|
dict(id=['r-bfull']),
|
||||||
|
dict(name=['script', 'noscript', 'style'])]
|
||||||
|
encoding = 'windows-1250'
|
||||||
|
no_stylesheets = True
|
||||||
|
remove_tags_before = dict(attrs={'class':'d-nadtit'})
|
||||||
|
remove_tags_after = dict(attrs={'class':'like'})
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'linearize_tables' : True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
|
||||||
|
def makeurl(wat):
|
||||||
|
return "http://ihned.cz"+wat;
|
||||||
|
|
||||||
|
for h1 in soup.findAll('h1'):
|
||||||
|
a = h1.find('a')
|
||||||
|
if a:
|
||||||
|
string = a.string
|
||||||
|
if string:
|
||||||
|
soup.a.replaceWith(string)
|
||||||
|
for a in soup.findAll('a', href=True) :
|
||||||
|
cil = str(a['href'])
|
||||||
|
if cil.startswith("/") or cil.startswith("index"):
|
||||||
|
a['href'] = makeurl(cil)
|
||||||
|
return soup
|
||||||
|
|
||||||
|
|
||||||
|
def parse_index(self):
|
||||||
|
|
||||||
|
def makeurl(wat):
|
||||||
|
if wat.startswith("/") or wat.startswith("index"):
|
||||||
|
return "http://ihned.cz"+wat;
|
||||||
|
else:
|
||||||
|
return wat
|
||||||
|
|
||||||
|
|
||||||
|
articles = {} #vysledek, asi
|
||||||
|
key = None #soucasna sekce
|
||||||
|
ans = [] #vsechny sekce
|
||||||
|
|
||||||
|
articles["Hlavní"] = []
|
||||||
|
ans.append("Hlavní")
|
||||||
|
|
||||||
|
was = {}
|
||||||
|
|
||||||
|
def parse_subpage(url, name):
|
||||||
|
articles[name] = []
|
||||||
|
ans.append(name)
|
||||||
|
|
||||||
|
|
||||||
|
soup = self.index_to_soup(url)
|
||||||
|
otvirak = soup.find(True, attrs={'class':['otv']})
|
||||||
|
if otvirak:
|
||||||
|
|
||||||
|
#the code is copypasted here because I don't know python. simple as that.
|
||||||
|
a = otvirak.find('a', href=True)
|
||||||
|
title = self.tag_to_string(a, use_alt=True).strip()
|
||||||
|
txt = otvirak.find(True, attrs={'class':['txt']})
|
||||||
|
description = ''
|
||||||
|
if txt:
|
||||||
|
match = re.match(r'<div class="txt">\s*([^<]*)\s*<a', str(txt), re.L)
|
||||||
|
if match:
|
||||||
|
description = match.group(1)
|
||||||
|
|
||||||
|
pubdate = strftime('%d. %m.')
|
||||||
|
if not title in was:
|
||||||
|
articles[name].append(
|
||||||
|
dict(title=title, url=makeurl(a['href']), date=pubdate,
|
||||||
|
description=description,
|
||||||
|
content=''))
|
||||||
|
|
||||||
|
otv234 = soup.find(True, attrs={'class':['otv234', 'col2a']})
|
||||||
|
if otv234:
|
||||||
|
for ow in otv234.findAll(True, attrs={'class':['ow']}):
|
||||||
|
a = ow.find('a', href=True)
|
||||||
|
title = self.tag_to_string(a, use_alt=True).strip()
|
||||||
|
description=''
|
||||||
|
prx = ow.find(True, attrs={'class':['prx']});
|
||||||
|
if prx:
|
||||||
|
description = str(prx.string)
|
||||||
|
nfo = ow.find(True, attrs={'class':['nfo']});
|
||||||
|
pubdate = ''
|
||||||
|
if nfo:
|
||||||
|
dtime = time.localtime();
|
||||||
|
day = dtime[2]
|
||||||
|
month = dtime[1]
|
||||||
|
|
||||||
|
pubdate = strftime('%d. %m.')
|
||||||
|
|
||||||
|
match = re.search(r'([0-9]*)\.([0-9]*)\.', str(nfo))
|
||||||
|
|
||||||
|
if self.stahnout_vsechny or (int(day) == int(match.group(1)) and int(month) == int(match.group(2))):
|
||||||
|
if not title in was:
|
||||||
|
articles[name].append(
|
||||||
|
dict(title=title, url=makeurl(a['href']), date=pubdate,
|
||||||
|
description=description,
|
||||||
|
content=''))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
soup = self.index_to_soup('http://ihned.cz/')
|
||||||
|
otvirak = soup.find(True, attrs={'class':['otv']})
|
||||||
|
if otvirak:
|
||||||
|
a = otvirak.find('a', href=True)
|
||||||
|
title = self.tag_to_string(a, use_alt=True).strip()
|
||||||
|
txt = otvirak.find(True, attrs={'class':['txt']})
|
||||||
|
description = ''
|
||||||
|
if txt:
|
||||||
|
match = re.match(r'<div class="txt">\s*([^<]*)\s*<a', str(txt), re.L)
|
||||||
|
if match:
|
||||||
|
description = match.group(1)
|
||||||
|
|
||||||
|
pubdate = strftime('%d. %m.')
|
||||||
|
feed = "Hlavní"
|
||||||
|
articles[feed].append(
|
||||||
|
dict(title=title, url=(a['href']), date=pubdate,
|
||||||
|
description=description,
|
||||||
|
content=''))
|
||||||
|
was[title]=1
|
||||||
|
|
||||||
|
otvirak2345 = soup.find(True, attrs={'class':['otv2345']})
|
||||||
|
if otvirak2345:
|
||||||
|
for otv2 in otvirak2345.findAll(True, attrs={'class':['otv2-5']}):
|
||||||
|
a = otv2.find('a', attrs={'class':['tit2']}, href=True)
|
||||||
|
title = self.tag_to_string(a, use_alt=True).strip()
|
||||||
|
description=''
|
||||||
|
span = otv2.find('span');
|
||||||
|
if span:
|
||||||
|
match = re.match(r'<span>\s*([^<]*)\s*<a', str(span), re.L)
|
||||||
|
if match:
|
||||||
|
description = match.group(1)
|
||||||
|
feed = "Hlavní"
|
||||||
|
pubdate = strftime('%d. %m.')
|
||||||
|
articles[feed].append(
|
||||||
|
dict(title=title, url=(a['href']), date=pubdate,
|
||||||
|
description=description,
|
||||||
|
content=''))
|
||||||
|
was[title]=1
|
||||||
|
|
||||||
|
|
||||||
|
parse_subpage("http://komentare.ihned.cz/", "Komentáře")
|
||||||
|
parse_subpage("http://domaci.ihned.cz", "Domácí")
|
||||||
|
parse_subpage("http://ekonomika.ihned.cz", "Ekonomika")
|
||||||
|
parse_subpage("http://zahranicni.ihned.cz/", "Zahraničí");
|
||||||
|
parse_subpage("http://finweb.ihned.cz/", "Finance");
|
||||||
|
parse_subpage("http://digiweb.ihned.cz/", "DigiWeb");
|
||||||
|
parse_subpage("http://kultura.ihned.cz/", "Kultura")
|
||||||
|
parse_subpage("http://sport.ihned.cz/", "Sport");
|
||||||
|
|
||||||
|
#seradi kategorie
|
||||||
|
ans = self.sort_index_by(ans, {'Hlavni':1, 'Domácí':2, 'Ekonomika':5, 'Zahraničí':3, 'Finance':6, 'DigiWeb':7, 'Kultura':8, 'Sport':9, 'Komentáře':4})
|
||||||
|
|
||||||
|
#vrati, ale pouze, kdyz je v kategoriich...
|
||||||
|
ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
|
||||||
|
return ans
|
||||||
|
|
16
resources/recipes/kath_net.recipe
Normal file
16
resources/recipes/kath_net.recipe
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1295262156(BasicNewsRecipe):
|
||||||
|
title = u'kath.net'
|
||||||
|
__author__ = 'Bobus'
|
||||||
|
oldest_article = 7
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
|
||||||
|
feeds = [(u'kath.net', u'http://www.kath.net/2005/xml/index.xml')]
|
||||||
|
|
||||||
|
|
||||||
|
def print_version(self, url):
|
||||||
|
return url+"&print=yes"
|
||||||
|
|
||||||
|
extra_css = 'td.textb {font-size: medium;}'
|
||||||
|
|
@ -27,6 +27,9 @@ class NikkeiNet_sub_economy(BasicNewsRecipe):
|
|||||||
{'class':"JSID_basePageMove JSID_baseAsyncSubmit cmn-form_area JSID_optForm_utoken"},
|
{'class':"JSID_basePageMove JSID_baseAsyncSubmit cmn-form_area JSID_optForm_utoken"},
|
||||||
{'class':"cmn-article_keyword cmn-clearfix"},
|
{'class':"cmn-article_keyword cmn-clearfix"},
|
||||||
{'class':"cmn-print_headline cmn-clearfix"},
|
{'class':"cmn-print_headline cmn-clearfix"},
|
||||||
|
{'class':"cmn-article_list"},
|
||||||
|
dict(id="ABOUT-NIKKEI"),
|
||||||
|
{'class':"cmn-sub_market"},
|
||||||
]
|
]
|
||||||
remove_tags_after = {'class':"cmn-pr_list"}
|
remove_tags_after = {'class':"cmn-pr_list"}
|
||||||
|
|
||||||
|
@ -586,7 +586,7 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
return self.strip_anchors(soup)
|
return self.strip_anchors(soup)
|
||||||
|
|
||||||
def postprocess_html(self,soup, True):
|
def postprocess_html(self,soup, True):
|
||||||
|
try:
|
||||||
if self.one_picture_per_article:
|
if self.one_picture_per_article:
|
||||||
# Remove all images after first
|
# Remove all images after first
|
||||||
largeImg = soup.find(True, {'class':'articleSpanImage'})
|
largeImg = soup.find(True, {'class':'articleSpanImage'})
|
||||||
@ -621,10 +621,13 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
cgFirst.insert(insertLoc,firstImg)
|
cgFirst.insert(insertLoc,firstImg)
|
||||||
else:
|
else:
|
||||||
self.log(">>> No class:'columnGroup first' found <<<")
|
self.log(">>> No class:'columnGroup first' found <<<")
|
||||||
|
except:
|
||||||
|
self.log("ERROR: One picture per article in postprocess_html")
|
||||||
|
|
||||||
|
try:
|
||||||
# Change captions to italic
|
# Change captions to italic
|
||||||
for caption in soup.findAll(True, {'class':'caption'}) :
|
for caption in soup.findAll(True, {'class':'caption'}) :
|
||||||
if caption and caption.contents[0]:
|
if caption and len(caption) > 0:
|
||||||
cTag = Tag(soup, "p", [("class", "caption")])
|
cTag = Tag(soup, "p", [("class", "caption")])
|
||||||
c = self.fixChars(self.tag_to_string(caption,use_alt=False)).strip()
|
c = self.fixChars(self.tag_to_string(caption,use_alt=False)).strip()
|
||||||
mp_off = c.find("More Photos")
|
mp_off = c.find("More Photos")
|
||||||
@ -632,7 +635,10 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
c = c[:mp_off]
|
c = c[:mp_off]
|
||||||
cTag.insert(0, c)
|
cTag.insert(0, c)
|
||||||
caption.replaceWith(cTag)
|
caption.replaceWith(cTag)
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in change captions to italic")
|
||||||
|
|
||||||
|
try:
|
||||||
# Change <nyt_headline> to <h2>
|
# Change <nyt_headline> to <h2>
|
||||||
h1 = soup.find('h1')
|
h1 = soup.find('h1')
|
||||||
if h1:
|
if h1:
|
||||||
@ -653,7 +659,10 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
hrs = soup.findAll('hr')
|
hrs = soup.findAll('hr')
|
||||||
for hr in hrs:
|
for hr in hrs:
|
||||||
hr.extract()
|
hr.extract()
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in Change <nyt_headline> to <h2>")
|
||||||
|
|
||||||
|
try:
|
||||||
# Change <h1> to <h3> - used in editorial blogs
|
# Change <h1> to <h3> - used in editorial blogs
|
||||||
masthead = soup.find("h1")
|
masthead = soup.find("h1")
|
||||||
if masthead:
|
if masthead:
|
||||||
@ -663,18 +672,27 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
tag = Tag(soup, "h3")
|
tag = Tag(soup, "h3")
|
||||||
tag.insert(0, self.fixChars(masthead.contents[0]))
|
tag.insert(0, self.fixChars(masthead.contents[0]))
|
||||||
masthead.replaceWith(tag)
|
masthead.replaceWith(tag)
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in Change <h1> to <h3> - used in editorial blogs")
|
||||||
|
|
||||||
|
try:
|
||||||
# Change <span class="bold"> to <b>
|
# Change <span class="bold"> to <b>
|
||||||
for subhead in soup.findAll(True, {'class':'bold'}) :
|
for subhead in soup.findAll(True, {'class':'bold'}) :
|
||||||
if subhead.contents:
|
if subhead.contents:
|
||||||
bTag = Tag(soup, "b")
|
bTag = Tag(soup, "b")
|
||||||
bTag.insert(0, subhead.contents[0])
|
bTag.insert(0, subhead.contents[0])
|
||||||
subhead.replaceWith(bTag)
|
subhead.replaceWith(bTag)
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in Change <h1> to <h3> - used in editorial blogs")
|
||||||
|
|
||||||
|
try:
|
||||||
divTag = soup.find('div',attrs={'id':'articleBody'})
|
divTag = soup.find('div',attrs={'id':'articleBody'})
|
||||||
if divTag:
|
if divTag:
|
||||||
divTag['class'] = divTag['id']
|
divTag['class'] = divTag['id']
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in soup.find(div,attrs={id:articleBody})")
|
||||||
|
|
||||||
|
try:
|
||||||
# Add class="authorId" to <div> so we can format with CSS
|
# Add class="authorId" to <div> so we can format with CSS
|
||||||
divTag = soup.find('div',attrs={'id':'authorId'})
|
divTag = soup.find('div',attrs={'id':'authorId'})
|
||||||
if divTag and divTag.contents[0]:
|
if divTag and divTag.contents[0]:
|
||||||
@ -683,6 +701,8 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0],
|
tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0],
|
||||||
use_alt=False)))
|
use_alt=False)))
|
||||||
divTag.replaceWith(tag)
|
divTag.replaceWith(tag)
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in Add class=authorId to <div> so we can format with CSS")
|
||||||
|
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
|
@ -586,7 +586,7 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
return self.strip_anchors(soup)
|
return self.strip_anchors(soup)
|
||||||
|
|
||||||
def postprocess_html(self,soup, True):
|
def postprocess_html(self,soup, True):
|
||||||
|
try:
|
||||||
if self.one_picture_per_article:
|
if self.one_picture_per_article:
|
||||||
# Remove all images after first
|
# Remove all images after first
|
||||||
largeImg = soup.find(True, {'class':'articleSpanImage'})
|
largeImg = soup.find(True, {'class':'articleSpanImage'})
|
||||||
@ -621,10 +621,13 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
cgFirst.insert(insertLoc,firstImg)
|
cgFirst.insert(insertLoc,firstImg)
|
||||||
else:
|
else:
|
||||||
self.log(">>> No class:'columnGroup first' found <<<")
|
self.log(">>> No class:'columnGroup first' found <<<")
|
||||||
|
except:
|
||||||
|
self.log("ERROR: One picture per article in postprocess_html")
|
||||||
|
|
||||||
|
try:
|
||||||
# Change captions to italic
|
# Change captions to italic
|
||||||
for caption in soup.findAll(True, {'class':'caption'}) :
|
for caption in soup.findAll(True, {'class':'caption'}) :
|
||||||
if caption and caption.contents[0]:
|
if caption and len(caption) > 0:
|
||||||
cTag = Tag(soup, "p", [("class", "caption")])
|
cTag = Tag(soup, "p", [("class", "caption")])
|
||||||
c = self.fixChars(self.tag_to_string(caption,use_alt=False)).strip()
|
c = self.fixChars(self.tag_to_string(caption,use_alt=False)).strip()
|
||||||
mp_off = c.find("More Photos")
|
mp_off = c.find("More Photos")
|
||||||
@ -632,7 +635,10 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
c = c[:mp_off]
|
c = c[:mp_off]
|
||||||
cTag.insert(0, c)
|
cTag.insert(0, c)
|
||||||
caption.replaceWith(cTag)
|
caption.replaceWith(cTag)
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in change captions to italic")
|
||||||
|
|
||||||
|
try:
|
||||||
# Change <nyt_headline> to <h2>
|
# Change <nyt_headline> to <h2>
|
||||||
h1 = soup.find('h1')
|
h1 = soup.find('h1')
|
||||||
if h1:
|
if h1:
|
||||||
@ -653,7 +659,10 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
hrs = soup.findAll('hr')
|
hrs = soup.findAll('hr')
|
||||||
for hr in hrs:
|
for hr in hrs:
|
||||||
hr.extract()
|
hr.extract()
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in Change <nyt_headline> to <h2>")
|
||||||
|
|
||||||
|
try:
|
||||||
# Change <h1> to <h3> - used in editorial blogs
|
# Change <h1> to <h3> - used in editorial blogs
|
||||||
masthead = soup.find("h1")
|
masthead = soup.find("h1")
|
||||||
if masthead:
|
if masthead:
|
||||||
@ -663,18 +672,27 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
tag = Tag(soup, "h3")
|
tag = Tag(soup, "h3")
|
||||||
tag.insert(0, self.fixChars(masthead.contents[0]))
|
tag.insert(0, self.fixChars(masthead.contents[0]))
|
||||||
masthead.replaceWith(tag)
|
masthead.replaceWith(tag)
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in Change <h1> to <h3> - used in editorial blogs")
|
||||||
|
|
||||||
|
try:
|
||||||
# Change <span class="bold"> to <b>
|
# Change <span class="bold"> to <b>
|
||||||
for subhead in soup.findAll(True, {'class':'bold'}) :
|
for subhead in soup.findAll(True, {'class':'bold'}) :
|
||||||
if subhead.contents:
|
if subhead.contents:
|
||||||
bTag = Tag(soup, "b")
|
bTag = Tag(soup, "b")
|
||||||
bTag.insert(0, subhead.contents[0])
|
bTag.insert(0, subhead.contents[0])
|
||||||
subhead.replaceWith(bTag)
|
subhead.replaceWith(bTag)
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in Change <h1> to <h3> - used in editorial blogs")
|
||||||
|
|
||||||
|
try:
|
||||||
divTag = soup.find('div',attrs={'id':'articleBody'})
|
divTag = soup.find('div',attrs={'id':'articleBody'})
|
||||||
if divTag:
|
if divTag:
|
||||||
divTag['class'] = divTag['id']
|
divTag['class'] = divTag['id']
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in soup.find(div,attrs={id:articleBody})")
|
||||||
|
|
||||||
|
try:
|
||||||
# Add class="authorId" to <div> so we can format with CSS
|
# Add class="authorId" to <div> so we can format with CSS
|
||||||
divTag = soup.find('div',attrs={'id':'authorId'})
|
divTag = soup.find('div',attrs={'id':'authorId'})
|
||||||
if divTag and divTag.contents[0]:
|
if divTag and divTag.contents[0]:
|
||||||
@ -683,6 +701,8 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0],
|
tag.insert(0, self.fixChars(self.tag_to_string(divTag.contents[0],
|
||||||
use_alt=False)))
|
use_alt=False)))
|
||||||
divTag.replaceWith(tag)
|
divTag.replaceWith(tag)
|
||||||
|
except:
|
||||||
|
self.log("ERROR: Problem in Add class=authorId to <div> so we can format with CSS")
|
||||||
|
|
||||||
return soup
|
return soup
|
||||||
def populate_article_metadata(self, article, soup, first):
|
def populate_article_metadata(self, article, soup, first):
|
||||||
|
@ -33,6 +33,6 @@ class SNE(USBMS):
|
|||||||
STORAGE_CARD_VOLUME_LABEL = 'SNE Storage Card'
|
STORAGE_CARD_VOLUME_LABEL = 'SNE Storage Card'
|
||||||
|
|
||||||
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'Books'
|
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'Books'
|
||||||
SUPPORTS_SUB_DIRS = False
|
SUPPORTS_SUB_DIRS = True
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,7 +104,11 @@ class FB2Input(InputFormatPlugin):
|
|||||||
entries = [(f, guess_type(f)[0]) for f in os.listdir('.')]
|
entries = [(f, guess_type(f)[0]) for f in os.listdir('.')]
|
||||||
opf.create_manifest(entries)
|
opf.create_manifest(entries)
|
||||||
opf.create_spine(['index.xhtml'])
|
opf.create_spine(['index.xhtml'])
|
||||||
|
if mi.cover_data and mi.cover_data[1]:
|
||||||
|
with open('fb2_cover_calibre_mi.jpg', 'wb') as f:
|
||||||
|
f.write(mi.cover_data[1])
|
||||||
|
opf.guide.set_cover(os.path.abspath('fb2_cover_calibre_mi.jpg'))
|
||||||
|
else:
|
||||||
for img in doc.xpath('//f:coverpage/f:image', namespaces=NAMESPACES):
|
for img in doc.xpath('//f:coverpage/f:image', namespaces=NAMESPACES):
|
||||||
href = img.get('{%s}href'%XLINK_NS, img.get('href', None))
|
href = img.get('{%s}href'%XLINK_NS, img.get('href', None))
|
||||||
if href is not None:
|
if href is not None:
|
||||||
|
@ -251,7 +251,7 @@ class Serializer(object):
|
|||||||
tag = prefixname(elem.tag, nsrmap)
|
tag = prefixname(elem.tag, nsrmap)
|
||||||
# Previous layers take care of @name
|
# Previous layers take care of @name
|
||||||
id = elem.attrib.pop('id', None)
|
id = elem.attrib.pop('id', None)
|
||||||
if id is not None:
|
if id:
|
||||||
href = '#'.join((item.href, id))
|
href = '#'.join((item.href, id))
|
||||||
offset = self.anchor_offset or buffer.tell()
|
offset = self.anchor_offset or buffer.tell()
|
||||||
self.id_offsets[urlnormalize(href)] = offset
|
self.id_offsets[urlnormalize(href)] = offset
|
||||||
|
@ -227,7 +227,7 @@ class EbookIterator(object):
|
|||||||
self.log.warn('Missing spine item:', repr(spath))
|
self.log.warn('Missing spine item:', repr(spath))
|
||||||
|
|
||||||
cover = self.opf.cover
|
cover = self.opf.cover
|
||||||
if self.ebook_ext in ('lit', 'mobi', 'prc', 'opf') and cover:
|
if self.ebook_ext in ('lit', 'mobi', 'prc', 'opf', 'fb2') and cover:
|
||||||
cfile = os.path.join(self.base, 'calibre_iterator_cover.html')
|
cfile = os.path.join(self.base, 'calibre_iterator_cover.html')
|
||||||
chtml = (TITLEPAGE%os.path.relpath(cover, self.base).replace(os.sep,
|
chtml = (TITLEPAGE%os.path.relpath(cover, self.base).replace(os.sep,
|
||||||
'/')).encode('utf-8')
|
'/')).encode('utf-8')
|
||||||
|
@ -91,13 +91,14 @@ class AddAction(InterfaceAction):
|
|||||||
self.gui.library_view.model().db.import_book(MetaInformation(None), [])
|
self.gui.library_view.model().db.import_book(MetaInformation(None), [])
|
||||||
self.gui.library_view.model().books_added(num)
|
self.gui.library_view.model().books_added(num)
|
||||||
|
|
||||||
def add_isbns(self, books):
|
def add_isbns(self, books, add_tags=[]):
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
ids = set([])
|
ids = set([])
|
||||||
|
db = self.gui.library_view.model().db
|
||||||
|
|
||||||
for x in books:
|
for x in books:
|
||||||
mi = MetaInformation(None)
|
mi = MetaInformation(None)
|
||||||
mi.isbn = x['isbn']
|
mi.isbn = x['isbn']
|
||||||
db = self.gui.library_view.model().db
|
|
||||||
if x['path'] is not None:
|
if x['path'] is not None:
|
||||||
ids.add(db.import_book(mi, [x['path']]))
|
ids.add(db.import_book(mi, [x['path']]))
|
||||||
else:
|
else:
|
||||||
@ -109,6 +110,8 @@ class AddAction(InterfaceAction):
|
|||||||
self.gui.iactions['Edit Metadata'].do_download_metadata(ids)
|
self.gui.iactions['Edit Metadata'].do_download_metadata(ids)
|
||||||
finally:
|
finally:
|
||||||
config['overwrite_author_title_metadata'] = orig
|
config['overwrite_author_title_metadata'] = orig
|
||||||
|
if add_tags and ids:
|
||||||
|
db.bulk_modify_tags(ids, add=add_tags)
|
||||||
|
|
||||||
|
|
||||||
def files_dropped(self, paths):
|
def files_dropped(self, paths):
|
||||||
@ -166,7 +169,7 @@ class AddAction(InterfaceAction):
|
|||||||
from calibre.gui2.dialogs.add_from_isbn import AddFromISBN
|
from calibre.gui2.dialogs.add_from_isbn import AddFromISBN
|
||||||
d = AddFromISBN(self.gui)
|
d = AddFromISBN(self.gui)
|
||||||
if d.exec_() == d.Accepted:
|
if d.exec_() == d.Accepted:
|
||||||
self.add_isbns(d.books)
|
self.add_isbns(d.books, add_tags=d.set_tags)
|
||||||
|
|
||||||
def add_books(self, *args):
|
def add_books(self, *args):
|
||||||
'''
|
'''
|
||||||
|
@ -5,11 +5,11 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, shutil
|
import re, os, shutil
|
||||||
|
|
||||||
from PyQt4.Qt import QModelIndex
|
from PyQt4.Qt import QModelIndex
|
||||||
|
|
||||||
from calibre.gui2 import error_dialog, choose_dir
|
from calibre.gui2 import choose_dir, error_dialog, warning_dialog
|
||||||
from calibre.gui2.tools import generate_catalog
|
from calibre.gui2.tools import generate_catalog
|
||||||
from calibre.utils.config import dynamic
|
from calibre.utils.config import dynamic
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
@ -55,10 +55,18 @@ class GenerateCatalogAction(InterfaceAction):
|
|||||||
|
|
||||||
def catalog_generated(self, job):
|
def catalog_generated(self, job):
|
||||||
if job.result:
|
if job.result:
|
||||||
# Error during catalog generation
|
# Problems during catalog generation
|
||||||
return error_dialog(self.gui, _('Catalog generation terminated'),
|
# jobs.results is a list - the first entry is the intended title for the dialog
|
||||||
job.result,
|
# Subsequent strings are error messages
|
||||||
show=True)
|
dialog_title = job.result.pop(0)
|
||||||
|
if re.match('warning:', job.result[0].lower()):
|
||||||
|
job.result.append("Catalog generation complete.")
|
||||||
|
warning_dialog(self.gui, dialog_title, '\n'.join(job.result), show=True)
|
||||||
|
else:
|
||||||
|
job.result.append("Catalog generation terminated.")
|
||||||
|
error_dialog(self.gui, dialog_title,'\n'.join(job.result),show=True)
|
||||||
|
return
|
||||||
|
|
||||||
if job.failed:
|
if job.failed:
|
||||||
return self.gui.job_exception(job)
|
return self.gui.job_exception(job)
|
||||||
id = self.gui.library_view.model().add_catalog(job.catalog_file_path, job.catalog_title)
|
id = self.gui.library_view.model().add_catalog(job.catalog_file_path, job.catalog_title)
|
||||||
|
@ -593,6 +593,11 @@ class Editor(QWidget): # {{{
|
|||||||
def code_dirtied(self, *args):
|
def code_dirtied(self, *args):
|
||||||
self.source_dirty = True
|
self.source_dirty = True
|
||||||
|
|
||||||
|
def hide_toolbars(self):
|
||||||
|
self.toolbar1.setVisible(False)
|
||||||
|
self.toolbar2.setVisible(False)
|
||||||
|
self.toolbar3.setVisible(False)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -18,6 +18,7 @@ from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
|||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.gui2.convert import Widget
|
from calibre.gui2.convert import Widget
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
|
from calibre.library.comments import comments_to_html
|
||||||
|
|
||||||
def create_opf_file(db, book_id):
|
def create_opf_file(db, book_id):
|
||||||
mi = db.get_metadata(book_id, index_is_id=True)
|
mi = db.get_metadata(book_id, index_is_id=True)
|
||||||
@ -57,6 +58,7 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
self.initialize_metadata_options()
|
self.initialize_metadata_options()
|
||||||
self.initialize_options(get_option, get_help, db, book_id)
|
self.initialize_options(get_option, get_help, db, book_id)
|
||||||
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
|
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
|
||||||
|
self.comment.hide_toolbars()
|
||||||
|
|
||||||
def deduce_author_sort(self, *args):
|
def deduce_author_sort(self, *args):
|
||||||
au = unicode(self.author.currentText())
|
au = unicode(self.author.currentText())
|
||||||
@ -79,7 +81,7 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
self.author_sort.setText(mi.author_sort if mi.author_sort else '')
|
self.author_sort.setText(mi.author_sort if mi.author_sort else '')
|
||||||
self.tags.setText(', '.join(mi.tags if mi.tags else []))
|
self.tags.setText(', '.join(mi.tags if mi.tags else []))
|
||||||
self.tags.update_items_cache(self.db.all_tags())
|
self.tags.update_items_cache(self.db.all_tags())
|
||||||
self.comment.setPlainText(mi.comments if mi.comments else '')
|
self.comment.html = comments_to_html(mi.comments) if mi.comments else ''
|
||||||
if mi.series:
|
if mi.series:
|
||||||
self.series.setCurrentIndex(self.series.findText(mi.series))
|
self.series.setCurrentIndex(self.series.findText(mi.series))
|
||||||
if mi.series_index is not None:
|
if mi.series_index is not None:
|
||||||
@ -154,7 +156,7 @@ class MetadataWidget(Widget, Ui_Form):
|
|||||||
author_sort = unicode(self.author_sort.text()).strip()
|
author_sort = unicode(self.author_sort.text()).strip()
|
||||||
if author_sort:
|
if author_sort:
|
||||||
mi.author_sort = author_sort
|
mi.author_sort = author_sort
|
||||||
comments = unicode(self.comment.toPlainText()).strip()
|
comments = self.comment.html
|
||||||
if comments:
|
if comments:
|
||||||
mi.comments = comments
|
mi.comments = comments
|
||||||
mi.series_index = float(self.series_index.value())
|
mi.series_index = float(self.series_index.value())
|
||||||
|
@ -22,6 +22,12 @@
|
|||||||
<layout class="QGridLayout" name="_2">
|
<layout class="QGridLayout" name="_2">
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<layout class="QVBoxLayout" name="_4">
|
<layout class="QVBoxLayout" name="_4">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -34,6 +40,12 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="_5">
|
<layout class="QHBoxLayout" name="_5">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="cover_path">
|
<widget class="QLineEdit" name="cover_path">
|
||||||
<property name="readOnly">
|
<property name="readOnly">
|
||||||
@ -50,7 +62,7 @@
|
|||||||
<string>...</string>
|
<string>...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/document_open.png</normaloff>:/images/document_open.png</iconset>
|
<normaloff>:/images/document_open.png</normaloff>:/images/document_open.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -252,35 +264,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
<widget class="Editor" name="comment" native="true"/>
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>200</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="title">
|
|
||||||
<string>Comments</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="_8">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QTextEdit" name="comment">
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>180</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
@ -297,6 +281,11 @@
|
|||||||
<extends>QComboBox</extends>
|
<extends>QComboBox</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>CompleteComboBox</class>
|
||||||
|
<extends>QComboBox</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>CompleteLineEdit</class>
|
<class>CompleteLineEdit</class>
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
@ -309,9 +298,10 @@
|
|||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>CompleteComboBox</class>
|
<class>Editor</class>
|
||||||
<extends>QComboBox</extends>
|
<extends>QWidget</extends>
|
||||||
<header>widgets.h</header>
|
<header>calibre/gui2/comments_editor.h</header>
|
||||||
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
@ -322,7 +312,6 @@
|
|||||||
<tabstop>tags</tabstop>
|
<tabstop>tags</tabstop>
|
||||||
<tabstop>series</tabstop>
|
<tabstop>series</tabstop>
|
||||||
<tabstop>series_index</tabstop>
|
<tabstop>series_index</tabstop>
|
||||||
<tabstop>comment</tabstop>
|
|
||||||
<tabstop>cover_path</tabstop>
|
<tabstop>cover_path</tabstop>
|
||||||
<tabstop>cover_button</tabstop>
|
<tabstop>cover_button</tabstop>
|
||||||
<tabstop>opt_prefer_metadata_cover</tabstop>
|
<tabstop>opt_prefer_metadata_cover</tabstop>
|
||||||
|
@ -1018,7 +1018,8 @@ class DeviceMixin(object): # {{{
|
|||||||
ids = [self.library_view.model().id(r) \
|
ids = [self.library_view.model().id(r) \
|
||||||
for r in self.library_view.selectionModel().selectedRows()] \
|
for r in self.library_view.selectionModel().selectedRows()] \
|
||||||
if send_ids is None else send_ids
|
if send_ids is None else send_ids
|
||||||
if not self.device_manager or not ids or len(ids) == 0:
|
if not self.device_manager or not ids or len(ids) == 0 or \
|
||||||
|
not self.device_manager.is_device_connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
settings = self.device_manager.device.settings()
|
settings = self.device_manager.device.settings()
|
||||||
|
@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog, QApplication
|
|||||||
from calibre.gui2.dialogs.add_from_isbn_ui import Ui_Dialog
|
from calibre.gui2.dialogs.add_from_isbn_ui import Ui_Dialog
|
||||||
from calibre.ebooks.metadata import check_isbn
|
from calibre.ebooks.metadata import check_isbn
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
|
from calibre.gui2 import gprefs
|
||||||
|
|
||||||
class AddFromISBN(QDialog, Ui_Dialog):
|
class AddFromISBN(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
@ -25,7 +26,9 @@ class AddFromISBN(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
self.isbns = []
|
self.isbns = []
|
||||||
self.books = []
|
self.books = []
|
||||||
|
self.set_tags = []
|
||||||
self.paste_button.clicked.connect(self.paste)
|
self.paste_button.clicked.connect(self.paste)
|
||||||
|
self.add_tags.setText(', '.join(gprefs.get('add from ISBN tags', [])))
|
||||||
|
|
||||||
def paste(self, *args):
|
def paste(self, *args):
|
||||||
app = QApplication.instance()
|
app = QApplication.instance()
|
||||||
@ -37,6 +40,10 @@ class AddFromISBN(QDialog, Ui_Dialog):
|
|||||||
self.isbn_box.setPlainText(new)
|
self.isbn_box.setPlainText(new)
|
||||||
|
|
||||||
def accept(self, *args):
|
def accept(self, *args):
|
||||||
|
tags = unicode(self.add_tags.text()).strip().split(',')
|
||||||
|
tags = list(filter(None, [x.strip() for x in tags]))
|
||||||
|
gprefs['add from ISBN tags'] = tags
|
||||||
|
self.set_tags = tags
|
||||||
for line in unicode(self.isbn_box.toPlainText()).strip().splitlines():
|
for line in unicode(self.isbn_box.toPlainText()).strip().splitlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line:
|
if not line:
|
||||||
|
@ -18,9 +18,20 @@
|
|||||||
<normaloff>:/images/add_book.png</normaloff>:/images/add_book.png</iconset>
|
<normaloff>:/images/add_book.png</normaloff>:/images/add_book.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0">
|
<item row="0" column="0" rowspan="2">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
<widget class="QPlainTextEdit" name="isbn_box"/>
|
<widget class="QPlainTextEdit" name="isbn_box"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="paste_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Paste from clipboard</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -34,6 +45,36 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Tags to set on created book entries:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>add_tags</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="add_tags"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item row="2" column="0" colspan="2">
|
<item row="2" column="0" colspan="2">
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -44,13 +85,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QPushButton" name="paste_button">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Paste from clipboard</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
|
@ -49,7 +49,6 @@ def get_cover_data(path): # {{{
|
|||||||
return cdata, area
|
return cdata, area
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
class MyBlockingBusy(QDialog): # {{{
|
class MyBlockingBusy(QDialog): # {{{
|
||||||
|
|
||||||
do_one_signal = pyqtSignal()
|
do_one_signal = pyqtSignal()
|
||||||
@ -134,7 +133,7 @@ class MyBlockingBusy(QDialog): # {{{
|
|||||||
do_autonumber, do_remove_format, remove_format, do_swap_ta, \
|
do_autonumber, do_remove_format, remove_format, do_swap_ta, \
|
||||||
do_remove_conv, do_auto_author, series, do_series_restart, \
|
do_remove_conv, do_auto_author, series, do_series_restart, \
|
||||||
series_start_value, do_title_case, cover_action, clear_series, \
|
series_start_value, do_title_case, cover_action, clear_series, \
|
||||||
pubdate = self.args
|
pubdate, adddate = self.args
|
||||||
|
|
||||||
|
|
||||||
# first loop: do author and title. These will commit at the end of each
|
# first loop: do author and title. These will commit at the end of each
|
||||||
@ -214,6 +213,9 @@ class MyBlockingBusy(QDialog): # {{{
|
|||||||
if pubdate is not None:
|
if pubdate is not None:
|
||||||
self.db.set_pubdate(id, pubdate, notify=False, commit=False)
|
self.db.set_pubdate(id, pubdate, notify=False, commit=False)
|
||||||
|
|
||||||
|
if adddate is not None:
|
||||||
|
self.db.set_timestamp(id, adddate, notify=False, commit=False)
|
||||||
|
|
||||||
if do_series:
|
if do_series:
|
||||||
if do_series_restart:
|
if do_series_restart:
|
||||||
if self.series_start_value is None:
|
if self.series_start_value is None:
|
||||||
@ -300,6 +302,10 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
self.pubdate.setSpecialValueText(_('Undefined'))
|
self.pubdate.setSpecialValueText(_('Undefined'))
|
||||||
self.clear_pubdate_button.clicked.connect(self.clear_pubdate)
|
self.clear_pubdate_button.clicked.connect(self.clear_pubdate)
|
||||||
self.pubdate.dateChanged.connect(self.do_apply_pubdate)
|
self.pubdate.dateChanged.connect(self.do_apply_pubdate)
|
||||||
|
self.adddate.setMinimumDate(UNDEFINED_QDATE)
|
||||||
|
self.adddate.setSpecialValueText(_('Undefined'))
|
||||||
|
self.clear_adddate_button.clicked.connect(self.clear_adddate)
|
||||||
|
self.adddate.dateChanged.connect(self.do_apply_adddate)
|
||||||
|
|
||||||
if len(self.db.custom_field_keys(include_composites=False)) == 0:
|
if len(self.db.custom_field_keys(include_composites=False)) == 0:
|
||||||
self.central_widget.removeTab(1)
|
self.central_widget.removeTab(1)
|
||||||
@ -322,6 +328,12 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
def clear_pubdate(self, *args):
|
def clear_pubdate(self, *args):
|
||||||
self.pubdate.setDate(UNDEFINED_QDATE)
|
self.pubdate.setDate(UNDEFINED_QDATE)
|
||||||
|
|
||||||
|
def do_apply_adddate(self, *args):
|
||||||
|
self.apply_adddate.setChecked(True)
|
||||||
|
|
||||||
|
def clear_adddate(self, *args):
|
||||||
|
self.adddate.setDate(UNDEFINED_QDATE)
|
||||||
|
|
||||||
def button_clicked(self, which):
|
def button_clicked(self, which):
|
||||||
if which == self.button_box.button(QDialogButtonBox.Apply):
|
if which == self.button_box.button(QDialogButtonBox.Apply):
|
||||||
self.do_again = True
|
self.do_again = True
|
||||||
@ -805,9 +817,11 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
do_remove_conv = self.remove_conversion_settings.isChecked()
|
do_remove_conv = self.remove_conversion_settings.isChecked()
|
||||||
do_auto_author = self.auto_author_sort.isChecked()
|
do_auto_author = self.auto_author_sort.isChecked()
|
||||||
do_title_case = self.change_title_to_title_case.isChecked()
|
do_title_case = self.change_title_to_title_case.isChecked()
|
||||||
pubdate = None
|
pubdate = adddate = None
|
||||||
if self.apply_pubdate.isChecked():
|
if self.apply_pubdate.isChecked():
|
||||||
pubdate = qt_to_dt(self.pubdate.date())
|
pubdate = qt_to_dt(self.pubdate.date())
|
||||||
|
if self.apply_adddate.isChecked():
|
||||||
|
adddate = qt_to_dt(self.adddate.date())
|
||||||
|
|
||||||
cover_action = None
|
cover_action = None
|
||||||
if self.cover_remove.isChecked():
|
if self.cover_remove.isChecked():
|
||||||
@ -821,7 +835,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
|||||||
do_autonumber, do_remove_format, remove_format, do_swap_ta,
|
do_autonumber, do_remove_format, remove_format, do_swap_ta,
|
||||||
do_remove_conv, do_auto_author, series, do_series_restart,
|
do_remove_conv, do_auto_author, series, do_series_restart,
|
||||||
series_start_value, do_title_case, cover_action, clear_series,
|
series_start_value, do_title_case, cover_action, clear_series,
|
||||||
pubdate)
|
pubdate, adddate)
|
||||||
|
|
||||||
bb = MyBlockingBusy(_('Applying changes to %d books.\nPhase {0} {1}%%.')
|
bb = MyBlockingBusy(_('Applying changes to %d books.\nPhase {0} {1}%%.')
|
||||||
%len(self.ids), args, self.db, self.ids,
|
%len(self.ids), args, self.db, self.ids,
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<string>Edit Meta information</string>
|
<string>Edit Meta information</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
@ -45,7 +45,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>842</width>
|
<width>842</width>
|
||||||
<height>553</height>
|
<height>589</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
@ -210,7 +210,7 @@
|
|||||||
<string>Open Tag Editor</string>
|
<string>Open Tag Editor</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/chapters.png</normaloff>:/images/chapters.png</iconset>
|
<normaloff>:/images/chapters.png</normaloff>:/images/chapters.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -347,6 +347,51 @@ from the value in the box</string>
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="9" column="0">
|
||||||
|
<widget class="QLabel" name="label_10">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Date:</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>adddate</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QDateEdit" name="adddate">
|
||||||
|
<property name="displayFormat">
|
||||||
|
<string>d MMM yyyy</string>
|
||||||
|
</property>
|
||||||
|
<property name="calendarPopup">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="clear_adddate_button">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="2">
|
||||||
|
<widget class="QCheckBox" name="apply_adddate">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Apply date</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="10" column="0">
|
<item row="10" column="0">
|
||||||
<widget class="QLabel" name="label_9">
|
<widget class="QLabel" name="label_9">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -381,7 +426,7 @@ from the value in the box</string>
|
|||||||
<string>...</string>
|
<string>...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -395,6 +440,42 @@ from the value in the box</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="11" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Remove &format:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>remove_format</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="11" column="1">
|
||||||
|
<widget class="QComboBox" name="remove_format">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>120</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="12" column="0">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>15</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
<item row="13" column="0" colspan="3">
|
<item row="13" column="0" colspan="3">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
<item>
|
<item>
|
||||||
@ -478,42 +559,6 @@ Future conversion of these books will use the default settings.</string>
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="12" column="0">
|
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeType">
|
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>15</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item row="11" column="0">
|
|
||||||
<widget class="QLabel" name="label_5">
|
|
||||||
<property name="text">
|
|
||||||
<string>Remove &format:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>remove_format</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="11" column="1">
|
|
||||||
<widget class="QComboBox" name="remove_format">
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>120</width>
|
|
||||||
<height>16777215</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="tab">
|
<widget class="QWidget" name="tab">
|
||||||
@ -874,8 +919,8 @@ not multiple and the destination field is multiple</string>
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>231</width>
|
<width>197</width>
|
||||||
<height>82</height>
|
<height>60</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="testgrid">
|
<layout class="QGridLayout" name="testgrid">
|
||||||
@ -954,6 +999,11 @@ not multiple and the destination field is multiple</string>
|
|||||||
<extends>QComboBox</extends>
|
<extends>QComboBox</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>CompleteComboBox</class>
|
||||||
|
<extends>QComboBox</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>CompleteLineEdit</class>
|
<class>CompleteLineEdit</class>
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
@ -964,11 +1014,6 @@ not multiple and the destination field is multiple</string>
|
|||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
|
||||||
<class>CompleteComboBox</class>
|
|
||||||
<extends>QComboBox</extends>
|
|
||||||
<header>widgets.h</header>
|
|
||||||
</customwidget>
|
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>authors</tabstop>
|
<tabstop>authors</tabstop>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<string>Edit Meta Information</string>
|
<string>Edit Meta Information</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeGripEnabled">
|
<property name="sizeGripEnabled">
|
||||||
@ -43,8 +43,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>955</width>
|
<width>986</width>
|
||||||
<height>665</height>
|
<height>677</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
@ -125,7 +125,7 @@ Using this button to create title sort will change title sort from red to green.
|
|||||||
<string>...</string>
|
<string>...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/auto_author_sort.png</normaloff>:/images/auto_author_sort.png</iconset>
|
<normaloff>:/images/auto_author_sort.png</normaloff>:/images/auto_author_sort.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -152,7 +152,7 @@ Using this button to create title sort will change title sort from red to green.
|
|||||||
<string>...</string>
|
<string>...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/swap.png</normaloff>:/images/swap.png</iconset>
|
<normaloff>:/images/swap.png</normaloff>:/images/swap.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="iconSize">
|
<property name="iconSize">
|
||||||
@ -186,7 +186,7 @@ Using this button to create author sort will change author sort from red to gree
|
|||||||
<string>...</string>
|
<string>...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/auto_author_sort.png</normaloff>:/images/auto_author_sort.png</iconset>
|
<normaloff>:/images/auto_author_sort.png</normaloff>:/images/auto_author_sort.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -352,7 +352,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<string>Open Tag Editor</string>
|
<string>Open Tag Editor</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/chapters.png</normaloff>:/images/chapters.png</iconset>
|
<normaloff>:/images/chapters.png</normaloff>:/images/chapters.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -405,7 +405,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<string>...</string>
|
<string>...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -491,7 +491,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<string>Clear published date</string>
|
<string>Clear published date</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -550,9 +550,15 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="_4">
|
<layout class="QVBoxLayout" name="_4">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
<property name="sizeConstraint">
|
<property name="sizeConstraint">
|
||||||
<enum>QLayout::SetMaximumSize</enum>
|
<enum>QLayout::SetMaximumSize</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -565,13 +571,19 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="_5">
|
<layout class="QHBoxLayout" name="_5">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="margin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="cover_button">
|
<widget class="QPushButton" name="cover_button">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Browse</string>
|
<string>&Browse</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/document_open.png</normaloff>:/images/document_open.png</iconset>
|
<normaloff>:/images/document_open.png</normaloff>:/images/document_open.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -585,7 +597,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<string>T&rim</string>
|
<string>T&rim</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/trim.png</normaloff>:/images/trim.png</iconset>
|
<normaloff>:/images/trim.png</normaloff>:/images/trim.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -599,7 +611,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<string>&Remove</string>
|
<string>&Remove</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
@ -690,7 +702,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<string>...</string>
|
<string>...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/add_book.png</normaloff>:/images/add_book.png</iconset>
|
<normaloff>:/images/add_book.png</normaloff>:/images/add_book.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="iconSize">
|
<property name="iconSize">
|
||||||
@ -710,7 +722,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<string>...</string>
|
<string>...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
<normaloff>:/images/trash.png</normaloff>:/images/trash.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="iconSize">
|
<property name="iconSize">
|
||||||
@ -730,7 +742,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<string>...</string>
|
<string>...</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/book.png</normaloff>:/images/book.png</iconset>
|
<normaloff>:/images/book.png</normaloff>:/images/book.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="iconSize">
|
<property name="iconSize">
|
||||||
@ -750,7 +762,7 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<string/>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="iconSize">
|
<property name="iconSize">
|
||||||
@ -834,6 +846,11 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>CompleteComboBox</class>
|
||||||
|
<extends>QComboBox</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>FormatList</class>
|
<class>FormatList</class>
|
||||||
<extends>QListWidget</extends>
|
<extends>QListWidget</extends>
|
||||||
@ -851,11 +868,6 @@ If the box is colored green, then text matches the individual author's sort stri
|
|||||||
<header location="global">calibre/gui2/comments_editor.h</header>
|
<header location="global">calibre/gui2/comments_editor.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
|
||||||
<class>CompleteComboBox</class>
|
|
||||||
<extends>QComboBox</extends>
|
|
||||||
<header>widgets.h</header>
|
|
||||||
</customwidget>
|
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>title</tabstop>
|
<tabstop>title</tabstop>
|
||||||
|
@ -33,7 +33,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
|||||||
self.authors_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
|
self.authors_box.setAutoCompletionCaseSensitivity(Qt.CaseInsensitive)
|
||||||
self.authors_box.set_separator('&')
|
self.authors_box.set_separator('&')
|
||||||
self.authors_box.set_space_before_sep(True)
|
self.authors_box.set_space_before_sep(True)
|
||||||
self.authors_box.update_items_cache(self.db.all_author_names())
|
self.authors_box.update_items_cache(db.all_author_names())
|
||||||
|
|
||||||
all_series = db.all_series()
|
all_series = db.all_series()
|
||||||
all_series.sort(key=lambda x : sort_key(x[1]))
|
all_series.sort(key=lambda x : sort_key(x[1]))
|
||||||
|
@ -6,15 +6,15 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>752</width>
|
<width>731</width>
|
||||||
<height>472</height>
|
<height>411</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Advanced Search</string>
|
<string>Advanced Search</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset>
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
<normaloff>:/images/search.png</normaloff>:/images/search.png</iconset>
|
<normaloff>:/images/search.png</normaloff>:/images/search.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
@ -148,7 +148,6 @@ class StatusBar(QStatusBar): # {{{
|
|||||||
self.get_version() + ' ' + _('created by Kovid Goyal')
|
self.get_version() + ' ' + _('created by Kovid Goyal')
|
||||||
self.device_string = ''
|
self.device_string = ''
|
||||||
self.update_label = QLabel('')
|
self.update_label = QLabel('')
|
||||||
self.update_label.setOpenExternalLinks(True)
|
|
||||||
self.addPermanentWidget(self.update_label)
|
self.addPermanentWidget(self.update_label)
|
||||||
self.update_label.setVisible(False)
|
self.update_label.setVisible(False)
|
||||||
self._font = QFont()
|
self._font = QFont()
|
||||||
@ -174,8 +173,9 @@ class StatusBar(QStatusBar): # {{{
|
|||||||
self.clearMessage()
|
self.clearMessage()
|
||||||
|
|
||||||
def new_version_available(self, ver, url):
|
def new_version_available(self, ver, url):
|
||||||
msg = (u'<span style="color:red; font-weight: bold">%s: <a href="%s">%s<a></span>') % (
|
msg = (u'<span style="color:red; font-weight: bold">%s: <a'
|
||||||
_('Update found'), url, ver)
|
' href="update:%s">%s<a></span>') % (
|
||||||
|
_('Update found'), ver, ver)
|
||||||
self.update_label.setText(msg)
|
self.update_label.setText(msg)
|
||||||
self.update_label.setCursor(Qt.PointingHandCursor)
|
self.update_label.setCursor(Qt.PointingHandCursor)
|
||||||
self.update_label.setVisible(True)
|
self.update_label.setVisible(True)
|
||||||
@ -240,6 +240,13 @@ class LayoutMixin(object): # {{{
|
|||||||
self.status_bar.addPermanentWidget(button)
|
self.status_bar.addPermanentWidget(button)
|
||||||
self.status_bar.addPermanentWidget(self.jobs_button)
|
self.status_bar.addPermanentWidget(self.jobs_button)
|
||||||
self.setStatusBar(self.status_bar)
|
self.setStatusBar(self.status_bar)
|
||||||
|
self.status_bar.update_label.linkActivated.connect(self.update_link_clicked)
|
||||||
|
|
||||||
|
def update_link_clicked(self, url):
|
||||||
|
url = unicode(url)
|
||||||
|
if url.startswith('update:'):
|
||||||
|
version = url.partition(':')[-1]
|
||||||
|
self.update_found(version, force=True)
|
||||||
|
|
||||||
def finalize_layout(self):
|
def finalize_layout(self):
|
||||||
self.status_bar.initialize(self.system_tray_icon)
|
self.status_bar.initialize(self.system_tray_icon)
|
||||||
|
@ -184,29 +184,6 @@ class TagsDelegate(QStyledItemDelegate): # {{{
|
|||||||
return editor
|
return editor
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class AuthorsDelegate(QStyledItemDelegate): # {{{
|
|
||||||
def __init__(self, parent):
|
|
||||||
QStyledItemDelegate.__init__(self, parent)
|
|
||||||
self.db = None
|
|
||||||
|
|
||||||
def set_database(self, db):
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
|
||||||
if self.db:
|
|
||||||
col = index.model().column_map[index.column()]
|
|
||||||
if not index.model().is_custom_column(col):
|
|
||||||
editor = CompleteLineEdit(parent, self.db.all_author_names(), '&', True)
|
|
||||||
else:
|
|
||||||
editor = CompleteLineEdit(parent,
|
|
||||||
sorted(list(self.db.all_custom(label=self.db.field_metadata.key_to_label(col))),
|
|
||||||
key=sort_key), '&', True)
|
|
||||||
return editor
|
|
||||||
else:
|
|
||||||
editor = EnLineEdit(parent)
|
|
||||||
return editor
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class CompleteDelegate(QStyledItemDelegate): # {{{
|
class CompleteDelegate(QStyledItemDelegate): # {{{
|
||||||
def __init__(self, parent, sep, items_func_name, space_before_sep=False):
|
def __init__(self, parent, sep, items_func_name, space_before_sep=False):
|
||||||
QStyledItemDelegate.__init__(self, parent)
|
QStyledItemDelegate.__init__(self, parent)
|
||||||
|
@ -485,7 +485,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
if 'calibre.ebooks.DRMError' in job.details:
|
if 'calibre.ebooks.DRMError' in job.details:
|
||||||
if not minz:
|
if not minz:
|
||||||
from calibre.gui2.dialogs.drm_error import DRMErrorMessage
|
from calibre.gui2.dialogs.drm_error import DRMErrorMessage
|
||||||
d = DRMErrorMessage(self, job.description.split(':')[-1])
|
d = DRMErrorMessage(self, _('Cannot convert') + ' ' +
|
||||||
|
job.description.split(':')[-1].partition('(')[-1][:-1])
|
||||||
d.setModal(False)
|
d.setModal(False)
|
||||||
d.show()
|
d.show()
|
||||||
self._modeless_dialogs.append(d)
|
self._modeless_dialogs.append(d)
|
||||||
|
@ -52,8 +52,7 @@ class UpdateNotification(QDialog):
|
|||||||
self.label = QLabel('<p>'+
|
self.label = QLabel('<p>'+
|
||||||
_('%s has been updated to version <b>%s</b>. '
|
_('%s has been updated to version <b>%s</b>. '
|
||||||
'See the <a href="http://calibre-ebook.com/whats-new'
|
'See the <a href="http://calibre-ebook.com/whats-new'
|
||||||
'">new features</a>. Visit the download pa'
|
'">new features</a>.')%(__appname__, version))
|
||||||
'ge?')%(__appname__, version))
|
|
||||||
self.label.setOpenExternalLinks(True)
|
self.label.setOpenExternalLinks(True)
|
||||||
self.label.setWordWrap(True)
|
self.label.setWordWrap(True)
|
||||||
self.setWindowTitle(_('Update available!'))
|
self.setWindowTitle(_('Update available!'))
|
||||||
@ -94,13 +93,13 @@ class UpdateMixin(object):
|
|||||||
type=Qt.QueuedConnection)
|
type=Qt.QueuedConnection)
|
||||||
self.update_checker.start()
|
self.update_checker.start()
|
||||||
|
|
||||||
def update_found(self, version):
|
def update_found(self, version, force=False):
|
||||||
os = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
os = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||||
url = 'http://calibre-ebook.com/download_%s'%os
|
url = 'http://calibre-ebook.com/download_%s'%os
|
||||||
self.status_bar.new_version_available(version, url)
|
self.status_bar.new_version_available(version, url)
|
||||||
|
|
||||||
if config.get('new_version_notification') and \
|
if force or (config.get('new_version_notification') and \
|
||||||
dynamic.get('update to version %s'%version, True):
|
dynamic.get('update to version %s'%version, True)):
|
||||||
self._update_notification__ = UpdateNotification(version,
|
self._update_notification__ = UpdateNotification(version,
|
||||||
parent=self)
|
parent=self)
|
||||||
self._update_notification__.show()
|
self._update_notification__.show()
|
||||||
|
@ -546,9 +546,9 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
name = 'Catalog_EPUB_MOBI'
|
name = 'Catalog_EPUB_MOBI'
|
||||||
description = 'EPUB/MOBI catalog generator'
|
description = 'EPUB/MOBI catalog generator'
|
||||||
supported_platforms = ['windows', 'osx', 'linux']
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
minimum_calibre_version = (0, 6, 34)
|
minimum_calibre_version = (0, 7, 40)
|
||||||
author = 'Greg Riker'
|
author = 'Greg Riker'
|
||||||
version = (0, 0, 1)
|
version = (1, 0, 0)
|
||||||
file_types = set(['epub','mobi'])
|
file_types = set(['epub','mobi'])
|
||||||
|
|
||||||
THUMB_SMALLEST = "1.0"
|
THUMB_SMALLEST = "1.0"
|
||||||
@ -900,15 +900,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
'''
|
'''
|
||||||
Generates catalog source files from calibre database
|
Generates catalog source files from calibre database
|
||||||
|
|
||||||
Implementation notes
|
Flow of control:
|
||||||
- 'Marker tags' in a book's metadata are used to flag special conditions:
|
|
||||||
(Defaults)
|
|
||||||
'~' : Do not catalog this book
|
|
||||||
'+' : Mark this book as read (check mark) in lists
|
|
||||||
'*' : Display trailing text as 'Note: <text>' in top frame next to cover
|
|
||||||
'[<source>] : Source of content (e.g., Amazon, Project Gutenberg). Do not create genre
|
|
||||||
|
|
||||||
- Program flow
|
|
||||||
gui2.actions.catalog:generate_catalog()
|
gui2.actions.catalog:generate_catalog()
|
||||||
gui2.tools:generate_catalog() or library.cli:command_catalog()
|
gui2.tools:generate_catalog() or library.cli:command_catalog()
|
||||||
called from gui2.convert.gui_conversion:gui_catalog()
|
called from gui2.convert.gui_conversion:gui_catalog()
|
||||||
@ -953,7 +945,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.__creator = opts.creator
|
self.__creator = opts.creator
|
||||||
self.__db = db
|
self.__db = db
|
||||||
self.__descriptionClip = opts.descriptionClip
|
self.__descriptionClip = opts.descriptionClip
|
||||||
self.__error = None
|
self.__error = []
|
||||||
self.__generateForKindle = True if (self.opts.fmt == 'mobi' and \
|
self.__generateForKindle = True if (self.opts.fmt == 'mobi' and \
|
||||||
self.opts.output_profile and \
|
self.opts.output_profile and \
|
||||||
self.opts.output_profile.startswith("kindle")) else False
|
self.opts.output_profile.startswith("kindle")) else False
|
||||||
@ -1033,6 +1025,22 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
# +1 thumbs
|
# +1 thumbs
|
||||||
self.__totalSteps += 3
|
self.__totalSteps += 3
|
||||||
|
|
||||||
|
# Load section list templates
|
||||||
|
templates = ['by_authors_normal_title_template',
|
||||||
|
'by_authors_series_title_template',
|
||||||
|
'by_titles_normal_title_template',
|
||||||
|
'by_titles_series_title_template',
|
||||||
|
'by_series_title_template',
|
||||||
|
'by_genres_normal_title_template',
|
||||||
|
'by_genres_series_title_template',
|
||||||
|
'by_recently_added_normal_title_template',
|
||||||
|
'by_recently_added_series_title_template',
|
||||||
|
'by_month_added_normal_title_template',
|
||||||
|
'by_month_added_series_title_template']
|
||||||
|
execfile(P('catalog/section_list_templates.py'), locals())
|
||||||
|
for t in templates:
|
||||||
|
setattr(self,t,eval(t))
|
||||||
|
|
||||||
# Accessors
|
# Accessors
|
||||||
if True:
|
if True:
|
||||||
'''
|
'''
|
||||||
@ -1352,6 +1360,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
return False
|
return False
|
||||||
self.fetchBookmarks()
|
self.fetchBookmarks()
|
||||||
if self.opts.generate_descriptions:
|
if self.opts.generate_descriptions:
|
||||||
|
self.generateThumbnails()
|
||||||
self.generateHTMLDescriptions()
|
self.generateHTMLDescriptions()
|
||||||
self.generateHTMLByAuthor()
|
self.generateHTMLByAuthor()
|
||||||
if self.opts.generate_titles:
|
if self.opts.generate_titles:
|
||||||
@ -1364,8 +1373,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.generateHTMLByDateAdded()
|
self.generateHTMLByDateAdded()
|
||||||
if self.generateRecentlyRead:
|
if self.generateRecentlyRead:
|
||||||
self.generateHTMLByDateRead()
|
self.generateHTMLByDateRead()
|
||||||
if self.opts.generate_descriptions:
|
|
||||||
self.generateThumbnails()
|
|
||||||
self.generateOPF()
|
self.generateOPF()
|
||||||
self.generateNCXHeader()
|
self.generateNCXHeader()
|
||||||
self.generateNCXByAuthor("Authors")
|
self.generateNCXByAuthor("Authors")
|
||||||
@ -1420,26 +1428,35 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
self.updateProgressFullStep("Sorting database")
|
self.updateProgressFullStep("Sorting database")
|
||||||
|
|
||||||
'''
|
|
||||||
# Sort titles case-insensitive, by author
|
|
||||||
self.booksByAuthor = sorted(self.booksByTitle,
|
|
||||||
key=lambda x:(x['author_sort'].upper(), x['author_sort'].upper()))
|
|
||||||
'''
|
|
||||||
|
|
||||||
self.booksByAuthor = list(self.booksByTitle)
|
self.booksByAuthor = list(self.booksByTitle)
|
||||||
self.booksByAuthor.sort(self.author_compare)
|
|
||||||
|
|
||||||
if False and self.verbose:
|
# Test for author_sort mismatches
|
||||||
self.opts.log.info("fetchBooksByAuthor(): %d books" % len(self.booksByAuthor))
|
self.booksByAuthor = sorted(self.booksByAuthor, key=self.booksByAuthorSorter_author)
|
||||||
self.opts.log.info(" %-30s %-20s %s" % ('title', 'series', 'series_index'))
|
# Build the unique_authors set from existing data
|
||||||
for title in self.booksByAuthor:
|
authors = [(record['author'], record['author_sort']) for record in self.booksByAuthor]
|
||||||
self.opts.log.info((u" %-30s %-20s%5s " % \
|
current_author = authors[0]
|
||||||
(title['title'][:30],
|
for (i,author) in enumerate(authors):
|
||||||
title['series'][:20] if title['series'] else '',
|
if author != current_author and i:
|
||||||
title['series_index'],
|
# Exit if author matches previous, but author_sort doesn't match
|
||||||
)).encode('utf-8'))
|
if author[0] == current_author[0]:
|
||||||
raise SystemExit
|
error_msg = _('''
|
||||||
|
Inconsistent Author Sort values for Author '{0}' ('{1}' <> '{2}'), unable to build catalog.\n
|
||||||
|
Select all books by '{0}', apply correct Author Sort value in Edit Metadata dialog,
|
||||||
|
then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
||||||
|
self.opts.log.warn('\n*** Metadata error ***')
|
||||||
|
self.opts.log.warn(error_msg)
|
||||||
|
|
||||||
|
self.error.append('Metadata error')
|
||||||
|
self.error.append(error_msg)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
self.booksByAuthor = sorted(self.booksByAuthor, key=self.booksByAuthorSorter_author_sort)
|
||||||
|
|
||||||
|
# for book in self.booksByAuthor:
|
||||||
|
# print '{0:<10} {1:<5} {2:<20} {3:<20} {4:<20} {5:<20}'.format(book['series'], book['series_index'], book['title'],
|
||||||
|
# book['author'], book['authors'],book['author_sort'])
|
||||||
|
# print
|
||||||
|
|
||||||
# Build the unique_authors set from existing data
|
# Build the unique_authors set from existing data
|
||||||
authors = [(record['author'], record['author_sort'].capitalize()) for record in self.booksByAuthor]
|
authors = [(record['author'], record['author_sort'].capitalize()) for record in self.booksByAuthor]
|
||||||
@ -1456,19 +1473,6 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
# Note that current_author and author are tuples: (friendly, sort)
|
# Note that current_author and author are tuples: (friendly, sort)
|
||||||
multiple_authors = True
|
multiple_authors = True
|
||||||
|
|
||||||
if author != current_author and i:
|
|
||||||
# Warn, exit if friendly matches previous, but sort doesn't
|
|
||||||
if author[0] == current_author[0]:
|
|
||||||
error_msg = _('''
|
|
||||||
\n*** Metadata error ***
|
|
||||||
Inconsistent Author Sort values for Author '{0}', unable to continue building catalog.
|
|
||||||
Select all books by '{0}', apply correct Author Sort value in Edit Metadata dialog,
|
|
||||||
then rebuild the catalog.\n''').format(author[0])
|
|
||||||
|
|
||||||
self.opts.log.warn(error_msg)
|
|
||||||
self.error = error_msg
|
|
||||||
return False
|
|
||||||
|
|
||||||
# New author, save the previous author/sort/count
|
# New author, save the previous author/sort/count
|
||||||
unique_authors.append((current_author[0], icu_title(current_author[1]),
|
unique_authors.append((current_author[0], icu_title(current_author[1]),
|
||||||
books_by_current_author))
|
books_by_current_author))
|
||||||
@ -1496,16 +1500,8 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def fetchBooksByTitle(self):
|
def fetchBooksByTitle(self):
|
||||||
|
|
||||||
self.updateProgressFullStep("Fetching database")
|
self.updateProgressFullStep("Fetching database")
|
||||||
|
|
||||||
# Get the database as a dictionary
|
|
||||||
# Sort by title
|
|
||||||
# Search is a string like this:
|
|
||||||
# not tag:<exclude_tag> author:"Riker"
|
|
||||||
# So we need to merge opts.exclude_tag with opts.search_text
|
|
||||||
# not tag:"~" author:"Riker"
|
|
||||||
|
|
||||||
self.opts.sort_by = 'title'
|
self.opts.sort_by = 'title'
|
||||||
|
|
||||||
# Merge opts.exclude_tags with opts.search_text
|
# Merge opts.exclude_tags with opts.search_text
|
||||||
@ -1528,7 +1524,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
else:
|
else:
|
||||||
self.opts.search_text = search_phrase
|
self.opts.search_text = search_phrase
|
||||||
|
|
||||||
#print "fetchBooksByTitle(): opts.search_text: %s" % self.opts.search_text
|
|
||||||
# Fetch the database as a dictionary
|
# Fetch the database as a dictionary
|
||||||
data = self.plugin.search_sort_db(self.db, self.opts)
|
data = self.plugin.search_sort_db(self.db, self.opts)
|
||||||
data = self.processExclusions(data)
|
data = self.processExclusions(data)
|
||||||
@ -1536,8 +1531,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
# Populate this_title{} from data[{},{}]
|
# Populate this_title{} from data[{},{}]
|
||||||
titles = []
|
titles = []
|
||||||
for record in data:
|
for record in data:
|
||||||
if False:
|
|
||||||
print "available record metadata:\n%s" % sorted(record.keys())
|
|
||||||
this_title = {}
|
this_title = {}
|
||||||
|
|
||||||
this_title['id'] = record['id']
|
this_title['id'] = record['id']
|
||||||
@ -1547,7 +1540,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
if record['series']:
|
if record['series']:
|
||||||
this_title['series'] = record['series']
|
this_title['series'] = record['series']
|
||||||
this_title['series_index'] = record['series_index']
|
this_title['series_index'] = record['series_index']
|
||||||
this_title['title'] = self.generateSeriesTitle(this_title)
|
|
||||||
else:
|
else:
|
||||||
this_title['series'] = None
|
this_title['series'] = None
|
||||||
this_title['series_index'] = 0.0
|
this_title['series_index'] = 0.0
|
||||||
@ -1572,7 +1564,12 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
this_title['publisher'] = re.sub('&', '&', record['publisher'])
|
this_title['publisher'] = re.sub('&', '&', record['publisher'])
|
||||||
|
|
||||||
this_title['rating'] = record['rating'] if record['rating'] else 0
|
this_title['rating'] = record['rating'] if record['rating'] else 0
|
||||||
|
|
||||||
|
if re.match('0100-01-01',str(record['pubdate'].date())):
|
||||||
|
this_title['date'] = None
|
||||||
|
else:
|
||||||
this_title['date'] = strftime(u'%B %Y', record['pubdate'].timetuple())
|
this_title['date'] = strftime(u'%B %Y', record['pubdate'].timetuple())
|
||||||
|
|
||||||
this_title['timestamp'] = record['timestamp']
|
this_title['timestamp'] = record['timestamp']
|
||||||
|
|
||||||
if record['comments']:
|
if record['comments']:
|
||||||
@ -1646,7 +1643,10 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
title['title_sort'][0:40])).decode('mac-roman'))
|
title['title_sort'][0:40])).decode('mac-roman'))
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.error = _("No books found to catalog.\nCheck 'Excluded books' criteria in E-book options.")
|
error_msg = _("No books found to catalog.\nCheck 'Excluded books' criteria in E-book options.\n")
|
||||||
|
self.opts.log.error('*** ' + error_msg + ' ***')
|
||||||
|
self.error.append(_('No books available to include in catalog'))
|
||||||
|
self.error.append(error_msg)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def fetchBookmarks(self):
|
def fetchBookmarks(self):
|
||||||
@ -1748,13 +1748,12 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
self.bookmarked_books = {}
|
self.bookmarked_books = {}
|
||||||
|
|
||||||
def generateHTMLDescriptions(self):
|
def generateHTMLDescriptions(self):
|
||||||
# Write each title to a separate HTML file in contentdir
|
'''
|
||||||
|
Write each title to a separate HTML file in contentdir
|
||||||
|
'''
|
||||||
self.updateProgressFullStep("'Descriptions'")
|
self.updateProgressFullStep("'Descriptions'")
|
||||||
|
|
||||||
for (title_num, title) in enumerate(self.booksByTitle):
|
for (title_num, title) in enumerate(self.booksByTitle):
|
||||||
if False:
|
|
||||||
self.opts.log.info("%3s: %s - %s" % (title['id'], title['title'], title['author']))
|
|
||||||
|
|
||||||
self.updateProgressMicroStep("Description %d of %d" % \
|
self.updateProgressMicroStep("Description %d of %d" % \
|
||||||
(title_num, len(self.booksByTitle)),
|
(title_num, len(self.booksByTitle)),
|
||||||
float(title_num*100/len(self.booksByTitle))/100)
|
float(title_num*100/len(self.booksByTitle))/100)
|
||||||
@ -1768,8 +1767,9 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
outfile.close()
|
outfile.close()
|
||||||
|
|
||||||
def generateHTMLByTitle(self):
|
def generateHTMLByTitle(self):
|
||||||
# Write books by title A-Z to HTML file
|
'''
|
||||||
|
Write books by title A-Z to HTML file
|
||||||
|
'''
|
||||||
self.updateProgressFullStep("'Titles'")
|
self.updateProgressFullStep("'Titles'")
|
||||||
|
|
||||||
soup = self.generateHTMLEmptyHeader("Books By Alpha Title")
|
soup = self.generateHTMLEmptyHeader("Books By Alpha Title")
|
||||||
@ -1807,22 +1807,11 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
current_letter = ""
|
current_letter = ""
|
||||||
|
|
||||||
# Re-sort title list without leading series/series_index
|
# Re-sort title list without leading series/series_index
|
||||||
|
# Incoming title <series> <series_index>: <title>
|
||||||
if not self.useSeriesPrefixInTitlesSection:
|
if not self.useSeriesPrefixInTitlesSection:
|
||||||
nspt = deepcopy(self.booksByTitle)
|
nspt = deepcopy(self.booksByTitle)
|
||||||
for book in nspt:
|
nspt = sorted(nspt, key=lambda x:(x['title_sort'].upper(), x['title_sort'].upper()))
|
||||||
if book['series']:
|
|
||||||
tokens = book['title'].partition(':')
|
|
||||||
book['title'] = '%s (%s)' % (tokens[2].strip(), tokens[0])
|
|
||||||
book['title_sort'] = self.generateSortTitle(book['title'])
|
|
||||||
nspt = sorted(nspt,
|
|
||||||
key=lambda x:(x['title_sort'].upper(), x['title_sort'].upper()))
|
|
||||||
self.booksByTitle_noSeriesPrefix = nspt
|
self.booksByTitle_noSeriesPrefix = nspt
|
||||||
if False and self.verbose:
|
|
||||||
self.opts.log.info("no_series_prefix_titles: %d books" % len(nspt))
|
|
||||||
self.opts.log.info(" %-40s %-40s" % ('title', 'title_sort'))
|
|
||||||
for title in nspt:
|
|
||||||
self.opts.log.info((u" %-40s %-40s" % (title['title'][0:40],
|
|
||||||
title['title_sort'][0:40])).encode('utf-8'))
|
|
||||||
|
|
||||||
# Loop through the books by title
|
# Loop through the books by title
|
||||||
title_list = self.booksByTitle
|
title_list = self.booksByTitle
|
||||||
@ -1878,7 +1867,14 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
if self.opts.generate_descriptions:
|
if self.opts.generate_descriptions:
|
||||||
aTag['href'] = "book_%d.html" % (int(float(book['id'])))
|
aTag['href'] = "book_%d.html" % (int(float(book['id'])))
|
||||||
aTag.insert(0,escape(book['title']))
|
|
||||||
|
# Generate the title from the template
|
||||||
|
args = self.generateFormatArgs(book)
|
||||||
|
if book['series']:
|
||||||
|
formatted_title = self.by_titles_series_title_template.format(**args).rstrip()
|
||||||
|
else:
|
||||||
|
formatted_title = self.by_titles_normal_title_template.format(**args).rstrip()
|
||||||
|
aTag.insert(0,NavigableString(escape(formatted_title)))
|
||||||
pBookTag.insert(ptc, aTag)
|
pBookTag.insert(ptc, aTag)
|
||||||
ptc += 1
|
ptc += 1
|
||||||
|
|
||||||
@ -1916,7 +1912,9 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
self.htmlFileList_1.append("content/ByAlphaTitle.html")
|
self.htmlFileList_1.append("content/ByAlphaTitle.html")
|
||||||
|
|
||||||
def generateHTMLByAuthor(self):
|
def generateHTMLByAuthor(self):
|
||||||
# Write books by author A-Z
|
'''
|
||||||
|
Write books by author A-Z
|
||||||
|
'''
|
||||||
self.updateProgressFullStep("'Authors'")
|
self.updateProgressFullStep("'Authors'")
|
||||||
|
|
||||||
friendly_name = "Authors"
|
friendly_name = "Authors"
|
||||||
@ -1953,7 +1951,9 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
current_author = ''
|
current_author = ''
|
||||||
current_letter = ''
|
current_letter = ''
|
||||||
current_series = None
|
current_series = None
|
||||||
|
#for book in sorted(self.booksByAuthor, key = self.booksByAuthorSorter_author_sort):
|
||||||
for book in self.booksByAuthor:
|
for book in self.booksByAuthor:
|
||||||
|
|
||||||
book_count += 1
|
book_count += 1
|
||||||
if self.letter_or_symbol(book['author_sort'][0].upper()) != current_letter :
|
if self.letter_or_symbol(book['author_sort'][0].upper()) != current_letter :
|
||||||
# Start a new letter with Index letter
|
# Start a new letter with Index letter
|
||||||
@ -2067,14 +2067,18 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
if self.opts.generate_descriptions:
|
if self.opts.generate_descriptions:
|
||||||
aTag['href'] = "book_%d.html" % (int(float(book['id'])))
|
aTag['href'] = "book_%d.html" % (int(float(book['id'])))
|
||||||
# Use series, series index if avail else title, + year of publication
|
|
||||||
|
# Generate the title from the template
|
||||||
|
args = self.generateFormatArgs(book)
|
||||||
if current_series:
|
if current_series:
|
||||||
aTag.insert(0,'%s (%s)' % (escape(book['title'][len(book['series'])+1:]),
|
#aTag.insert(0,'%s%s' % (escape(book['title'][len(book['series'])+1:]),pubyear))
|
||||||
book['date'].split()[1]))
|
formatted_title = self.by_authors_series_title_template.format(**args).rstrip()
|
||||||
else:
|
else:
|
||||||
aTag.insert(0,'%s (%s)' % (escape(book['title']),
|
#aTag.insert(0,'%s%s' % (escape(book['title']), pubyear))
|
||||||
book['date'].split()[1]))
|
formatted_title = self.by_authors_normal_title_template.format(**args).rstrip()
|
||||||
non_series_books += 1
|
non_series_books += 1
|
||||||
|
aTag.insert(0,NavigableString(escape(formatted_title)))
|
||||||
|
|
||||||
pBookTag.insert(ptc, aTag)
|
pBookTag.insert(ptc, aTag)
|
||||||
ptc += 1
|
ptc += 1
|
||||||
|
|
||||||
@ -2111,7 +2115,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
# Add the divTag to the body
|
# Add the divTag to the body
|
||||||
body.insert(btc, divTag)
|
body.insert(btc, divTag)
|
||||||
|
|
||||||
|
|
||||||
# Write the generated file to contentdir
|
# Write the generated file to contentdir
|
||||||
outfile_spec = "%s/ByAlphaAuthor.html" % (self.contentDir)
|
outfile_spec = "%s/ByAlphaAuthor.html" % (self.contentDir)
|
||||||
outfile = open(outfile_spec, 'w')
|
outfile = open(outfile_spec, 'w')
|
||||||
@ -2120,13 +2123,15 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
self.htmlFileList_1.append("content/ByAlphaAuthor.html")
|
self.htmlFileList_1.append("content/ByAlphaAuthor.html")
|
||||||
|
|
||||||
def generateHTMLByDateAdded(self):
|
def generateHTMLByDateAdded(self):
|
||||||
# Write books by reverse chronological order
|
'''
|
||||||
|
Write books by reverse chronological order
|
||||||
|
'''
|
||||||
self.updateProgressFullStep("'Recently Added'")
|
self.updateProgressFullStep("'Recently Added'")
|
||||||
|
|
||||||
def add_books_to_HTML_by_month(this_months_list, dtc):
|
def add_books_to_HTML_by_month(this_months_list, dtc):
|
||||||
if len(this_months_list):
|
if len(this_months_list):
|
||||||
|
|
||||||
this_months_list.sort(self.author_compare)
|
#this_months_list = sorted(this_months_list, key=self.booksByAuthorSorter_author_sort)
|
||||||
|
|
||||||
# Create a new month anchor
|
# Create a new month anchor
|
||||||
date_string = strftime(u'%B %Y', current_date.timetuple())
|
date_string = strftime(u'%B %Y', current_date.timetuple())
|
||||||
@ -2156,16 +2161,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
divTag.insert(dtc,pAuthorTag)
|
divTag.insert(dtc,pAuthorTag)
|
||||||
dtc += 1
|
dtc += 1
|
||||||
|
|
||||||
'''
|
|
||||||
# Insert an <hr /> between non-series and series
|
|
||||||
if not current_series and non_series_books and new_entry['series']:
|
|
||||||
# Insert an <hr />
|
|
||||||
hrTag = Tag(soup,'hr')
|
|
||||||
hrTag['class'] = "series_divider"
|
|
||||||
divTag.insert(dtc,hrTag)
|
|
||||||
dtc += 1
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Check for series
|
# Check for series
|
||||||
if new_entry['series'] and new_entry['series'] != current_series:
|
if new_entry['series'] and new_entry['series'] != current_series:
|
||||||
# Start a new series
|
# Start a new series
|
||||||
@ -2213,11 +2208,15 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
if self.opts.generate_descriptions:
|
if self.opts.generate_descriptions:
|
||||||
aTag['href'] = "book_%d.html" % (int(float(new_entry['id'])))
|
aTag['href'] = "book_%d.html" % (int(float(new_entry['id'])))
|
||||||
|
|
||||||
|
# Generate the title from the template
|
||||||
|
args = self.generateFormatArgs(new_entry)
|
||||||
if current_series:
|
if current_series:
|
||||||
aTag.insert(0,escape(new_entry['title'][len(new_entry['series'])+1:]))
|
formatted_title = self.by_month_added_series_title_template.format(**args).rstrip()
|
||||||
else:
|
else:
|
||||||
aTag.insert(0,escape(new_entry['title']))
|
formatted_title = self.by_month_added_normal_title_template.format(**args).rstrip()
|
||||||
non_series_books += 1
|
non_series_books += 1
|
||||||
|
aTag.insert(0,NavigableString(escape(formatted_title)))
|
||||||
pBookTag.insert(ptc, aTag)
|
pBookTag.insert(ptc, aTag)
|
||||||
ptc += 1
|
ptc += 1
|
||||||
|
|
||||||
@ -2265,7 +2264,14 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
if self.opts.generate_descriptions:
|
if self.opts.generate_descriptions:
|
||||||
aTag['href'] = "book_%d.html" % (int(float(new_entry['id'])))
|
aTag['href'] = "book_%d.html" % (int(float(new_entry['id'])))
|
||||||
aTag.insert(0,escape(new_entry['title']))
|
|
||||||
|
# Generate the title from the template
|
||||||
|
args = self.generateFormatArgs(new_entry)
|
||||||
|
if new_entry['series']:
|
||||||
|
formatted_title = self.by_recently_added_series_title_template.format(**args).rstrip()
|
||||||
|
else:
|
||||||
|
formatted_title = self.by_recently_added_normal_title_template.format(**args).rstrip()
|
||||||
|
aTag.insert(0,NavigableString(escape(formatted_title)))
|
||||||
pBookTag.insert(ptc, aTag)
|
pBookTag.insert(ptc, aTag)
|
||||||
ptc += 1
|
ptc += 1
|
||||||
|
|
||||||
@ -2323,17 +2329,12 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
divTag = Tag(soup, "div")
|
divTag = Tag(soup, "div")
|
||||||
dtc = 0
|
dtc = 0
|
||||||
|
|
||||||
# Add books by date range
|
# >>> Books by date range <<<
|
||||||
if self.useSeriesPrefixInTitlesSection:
|
if self.useSeriesPrefixInTitlesSection:
|
||||||
self.booksByDateRange = sorted(self.booksByTitle,
|
self.booksByDateRange = sorted(self.booksByTitle,
|
||||||
key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
|
key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
|
||||||
else:
|
else:
|
||||||
nspt = deepcopy(self.booksByTitle)
|
nspt = deepcopy(self.booksByTitle)
|
||||||
for book in nspt:
|
|
||||||
if book['series']:
|
|
||||||
tokens = book['title'].partition(':')
|
|
||||||
book['title'] = '%s (%s)' % (tokens[2].strip(), tokens[0])
|
|
||||||
book['title_sort'] = self.generateSortTitle(book['title'])
|
|
||||||
self.booksByDateRange = sorted(nspt, key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
|
self.booksByDateRange = sorted(nspt, key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
|
||||||
|
|
||||||
date_range_list = []
|
date_range_list = []
|
||||||
@ -2356,15 +2357,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
dtc = add_books_to_HTML_by_date_range(date_range_list, date_range, dtc)
|
dtc = add_books_to_HTML_by_date_range(date_range_list, date_range, dtc)
|
||||||
date_range_list = [book]
|
date_range_list = [book]
|
||||||
|
|
||||||
'''
|
|
||||||
if books_added_in_date_range:
|
|
||||||
# Add an <hr> separating date ranges from months
|
|
||||||
hrTag = Tag(soup,'hr')
|
|
||||||
hrTag['class'] = "description_divider"
|
|
||||||
divTag.insert(dtc,hrTag)
|
|
||||||
dtc += 1
|
|
||||||
'''
|
|
||||||
|
|
||||||
# >>>> Books by month <<<<
|
# >>>> Books by month <<<<
|
||||||
# Sort titles case-insensitive for by month using series prefix
|
# Sort titles case-insensitive for by month using series prefix
|
||||||
self.booksByMonth = sorted(self.booksByTitle,
|
self.booksByMonth = sorted(self.booksByTitle,
|
||||||
@ -2395,7 +2387,9 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
self.htmlFileList_2.append("content/ByDateAdded.html")
|
self.htmlFileList_2.append("content/ByDateAdded.html")
|
||||||
|
|
||||||
def generateHTMLByDateRead(self):
|
def generateHTMLByDateRead(self):
|
||||||
# Write books by active bookmarks
|
'''
|
||||||
|
Write books by active bookmarks
|
||||||
|
'''
|
||||||
friendly_name = 'Recently Read'
|
friendly_name = 'Recently Read'
|
||||||
self.updateProgressFullStep("'%s'" % friendly_name)
|
self.updateProgressFullStep("'%s'" % friendly_name)
|
||||||
if not self.bookmarked_books:
|
if not self.bookmarked_books:
|
||||||
@ -2533,32 +2527,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
self.booksByDateRead = sorted(bookmarked_books,
|
self.booksByDateRead = sorted(bookmarked_books,
|
||||||
key=lambda x:(x['bookmark_timestamp'], x['bookmark_timestamp']),reverse=True)
|
key=lambda x:(x['bookmark_timestamp'], x['bookmark_timestamp']),reverse=True)
|
||||||
|
|
||||||
'''
|
|
||||||
# >>>> Recently by date range <<<<
|
|
||||||
date_range_list = []
|
|
||||||
today_time = datetime.datetime.utcnow()
|
|
||||||
today_time.replace(hour=23, minute=59, second=59)
|
|
||||||
books_added_in_date_range = False
|
|
||||||
for (i, date) in enumerate(self.DATE_RANGE):
|
|
||||||
date_range_limit = self.DATE_RANGE[i]
|
|
||||||
if i:
|
|
||||||
date_range = '%d to %d days ago' % (self.DATE_RANGE[i-1], self.DATE_RANGE[i])
|
|
||||||
else:
|
|
||||||
date_range = 'Last %d days' % (self.DATE_RANGE[i])
|
|
||||||
|
|
||||||
for book in self.booksByDateRead:
|
|
||||||
bookmark_time = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp'])
|
|
||||||
delta = today_time-bookmark_time
|
|
||||||
if delta.days <= date_range_limit:
|
|
||||||
date_range_list.append(book)
|
|
||||||
books_added_in_date_range = True
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
dtc = add_books_to_HTML_by_date_range(date_range_list, date_range, dtc)
|
|
||||||
date_range_list = [book]
|
|
||||||
'''
|
|
||||||
|
|
||||||
# >>>> Recently read by day <<<<
|
# >>>> Recently read by day <<<<
|
||||||
current_date = datetime.date.fromordinal(1)
|
current_date = datetime.date.fromordinal(1)
|
||||||
todays_list = []
|
todays_list = []
|
||||||
@ -2713,10 +2681,15 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
# Use series, series index if avail else just title
|
# Use series, series index if avail else just title
|
||||||
#aTag.insert(0,'%d. %s · %s' % (book['series_index'],escape(book['title']), ' & '.join(book['authors'])))
|
#aTag.insert(0,'%d. %s · %s' % (book['series_index'],escape(book['title']), ' & '.join(book['authors'])))
|
||||||
|
|
||||||
# Link to book
|
# Reassert 'date' since this is the result of a new search
|
||||||
aTag.insert(0,'%d. %s (%s)' % (book['series_index'],
|
if re.match('0100-01-01',str(book['pubdate'].date())):
|
||||||
escape(book['title']),
|
book['date'] = None
|
||||||
strftime(u'%Y', book['pubdate'].timetuple())))
|
else:
|
||||||
|
book['date'] = strftime(u'%B %Y', book['pubdate'].timetuple())
|
||||||
|
|
||||||
|
args = self.generateFormatArgs(book)
|
||||||
|
formatted_title = self.by_series_title_template.format(**args).rstrip()
|
||||||
|
aTag.insert(0,NavigableString(escape(formatted_title)))
|
||||||
pBookTag.insert(ptc, aTag)
|
pBookTag.insert(ptc, aTag)
|
||||||
ptc += 1
|
ptc += 1
|
||||||
|
|
||||||
@ -2760,10 +2733,11 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
self.htmlFileList_1.append("content/BySeries.html")
|
self.htmlFileList_1.append("content/BySeries.html")
|
||||||
|
|
||||||
def generateHTMLByTags(self):
|
def generateHTMLByTags(self):
|
||||||
# Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
|
'''
|
||||||
# Note that special tags - ~+*[] - have already been filtered from books[]
|
Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
|
||||||
# There may be synonomous tags
|
Note that special tags - have already been filtered from books[]
|
||||||
|
There may be synonomous tags
|
||||||
|
'''
|
||||||
self.updateProgressFullStep("'Genres'")
|
self.updateProgressFullStep("'Genres'")
|
||||||
|
|
||||||
self.genre_tags_dict = self.filterDbTags(self.db.all_tags())
|
self.genre_tags_dict = self.filterDbTags(self.db.all_tags())
|
||||||
@ -2787,6 +2761,8 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
this_book['tags'] = book['tags']
|
this_book['tags'] = book['tags']
|
||||||
this_book['id'] = book['id']
|
this_book['id'] = book['id']
|
||||||
this_book['series'] = book['series']
|
this_book['series'] = book['series']
|
||||||
|
this_book['series_index'] = book['series_index']
|
||||||
|
this_book['date'] = book['date']
|
||||||
normalized_tag = self.genre_tags_dict[friendly_tag]
|
normalized_tag = self.genre_tags_dict[friendly_tag]
|
||||||
genre_tag_list = [key for genre in genre_list for key in genre]
|
genre_tag_list = [key for genre in genre_list for key in genre]
|
||||||
if normalized_tag in genre_tag_list:
|
if normalized_tag in genre_tag_list:
|
||||||
@ -2843,13 +2819,7 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
unique_authors.append((current_author[0], current_author[1], books_by_current_author))
|
unique_authors.append((current_author[0], current_author[1], books_by_current_author))
|
||||||
else:
|
else:
|
||||||
books_by_current_author += 1
|
books_by_current_author += 1
|
||||||
'''
|
|
||||||
# Extract the unique entries
|
|
||||||
unique_authors = []
|
|
||||||
for author in authors:
|
|
||||||
if not author in unique_authors:
|
|
||||||
unique_authors.append(author)
|
|
||||||
'''
|
|
||||||
# Write the genre book list as an article
|
# Write the genre book list as an article
|
||||||
titles_spanned = self.generateHTMLByGenre(genre, True if index==0 else False,
|
titles_spanned = self.generateHTMLByGenre(genre, True if index==0 else False,
|
||||||
genre_tag_set[genre],
|
genre_tag_set[genre],
|
||||||
@ -2863,18 +2833,14 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
'books':genre_tag_set[genre],
|
'books':genre_tag_set[genre],
|
||||||
'titles_spanned':titles_spanned})
|
'titles_spanned':titles_spanned})
|
||||||
|
|
||||||
if False and self.opts.verbose:
|
|
||||||
for genre in master_genre_list:
|
|
||||||
print "genre['tag']: %s" % genre['tag']
|
|
||||||
for book in genre['books']:
|
|
||||||
print book['title']
|
|
||||||
self.genres = master_genre_list
|
self.genres = master_genre_list
|
||||||
|
|
||||||
def generateThumbnails(self):
|
def generateThumbnails(self):
|
||||||
# Generate a thumbnail per cover. If a current thumbnail exists, skip
|
'''
|
||||||
# If a cover doesn't exist, use default
|
Generate a thumbnail per cover. If a current thumbnail exists, skip
|
||||||
# Return list of active thumbs
|
If a cover doesn't exist, use default
|
||||||
|
Return list of active thumbs
|
||||||
|
'''
|
||||||
self.updateProgressFullStep("'Thumbnails'")
|
self.updateProgressFullStep("'Thumbnails'")
|
||||||
thumbs = ['thumbnail_default.jpg']
|
thumbs = ['thumbnail_default.jpg']
|
||||||
image_dir = "%s/images" % self.catalogPath
|
image_dir = "%s/images" % self.catalogPath
|
||||||
@ -2886,45 +2852,52 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
|
|
||||||
thumb_file = 'thumbnail_%d.jpg' % int(title['id'])
|
thumb_file = 'thumbnail_%d.jpg' % int(title['id'])
|
||||||
thumb_generated = True
|
thumb_generated = True
|
||||||
|
valid_cover = True
|
||||||
try:
|
try:
|
||||||
self.generateThumbnail(title, image_dir, thumb_file)
|
self.generateThumbnail(title, image_dir, thumb_file)
|
||||||
thumbs.append("thumbnail_%d.jpg" % int(title['id']))
|
thumbs.append("thumbnail_%d.jpg" % int(title['id']))
|
||||||
except:
|
except:
|
||||||
|
if 'cover' in title and os.path.exists(title['cover']):
|
||||||
|
valid_cover = False
|
||||||
|
self.opts.log.warn(" *** Invalid cover file for '%s'***" %
|
||||||
|
(title['title']))
|
||||||
|
if not self.error:
|
||||||
|
self.error.append('Invalid cover files')
|
||||||
|
self.error.append("Warning: invalid cover file for '%s', default cover substituted.\n" % (title['title']))
|
||||||
|
|
||||||
thumb_generated = False
|
thumb_generated = False
|
||||||
|
|
||||||
|
|
||||||
if not thumb_generated:
|
if not thumb_generated:
|
||||||
# Use default cover
|
self.opts.log.warn(" using default cover for '%s' (%d)" % (title['title'], title['id']))
|
||||||
if False and self.verbose:
|
# Confirm thumb exists, default is current
|
||||||
self.opts.log.warn(" using default cover for '%s'" % \
|
default_thumb_fp = os.path.join(image_dir,"thumbnail_default.jpg")
|
||||||
(title['title']))
|
cover = os.path.join(self.catalogPath, "DefaultCover.png")
|
||||||
# Check to make sure default is current
|
title['cover'] = cover
|
||||||
# Check to see if thumbnail exists
|
|
||||||
thumb_fp = "%s/thumbnail_default.jpg" % (image_dir)
|
|
||||||
cover = "%s/DefaultCover.png" % (self.catalogPath)
|
|
||||||
if not os.path.exists(cover):
|
if not os.path.exists(cover):
|
||||||
shutil.copyfile(I('book.png'), cover)
|
shutil.copyfile(I('book.png'), cover)
|
||||||
|
|
||||||
if os.path.isfile(thumb_fp):
|
if os.path.isfile(default_thumb_fp):
|
||||||
# Check to see if default cover is newer than thumbnail
|
# Check to see if default cover is newer than thumbnail
|
||||||
# os.path.getmtime() = modified time
|
# os.path.getmtime() = modified time
|
||||||
# os.path.ctime() = creation time
|
# os.path.ctime() = creation time
|
||||||
cover_timestamp = os.path.getmtime(cover)
|
cover_timestamp = os.path.getmtime(cover)
|
||||||
thumb_timestamp = os.path.getmtime(thumb_fp)
|
thumb_timestamp = os.path.getmtime(default_thumb_fp)
|
||||||
if thumb_timestamp < cover_timestamp:
|
if thumb_timestamp < cover_timestamp:
|
||||||
if False and self.verbose:
|
if False and self.verbose:
|
||||||
self.opts.log.warn("updating thumbnail_default for %s" % title['title'])
|
self.opts.log.warn("updating thumbnail_default for %s" % title['title'])
|
||||||
#title['cover'] = "%s/DefaultCover.jpg" % self.catalogPath
|
self.generateThumbnail(title, image_dir,
|
||||||
title['cover'] = cover
|
"thumbnail_default.jpg" if valid_cover else thumb_file)
|
||||||
self.generateThumbnail(title, image_dir, "thumbnail_default.jpg")
|
|
||||||
else:
|
else:
|
||||||
if False and self.verbose:
|
if False and self.verbose:
|
||||||
self.opts.log.warn(" generating new thumbnail_default.jpg")
|
self.opts.log.warn(" generating new thumbnail_default.jpg")
|
||||||
#title['cover'] = "%s/DefaultCover.jpg" % self.catalogPath
|
self.generateThumbnail(title, image_dir,
|
||||||
title['cover'] = cover
|
"thumbnail_default.jpg" if valid_cover else thumb_file)
|
||||||
self.generateThumbnail(title, image_dir, "thumbnail_default.jpg")
|
# Clear the book's cover property
|
||||||
|
title['cover'] = None
|
||||||
|
|
||||||
# Write the thumb_width to the file validating cache contents
|
|
||||||
|
# Write thumb_width to the file, validating cache contents
|
||||||
# Allows detection of aborted catalog builds
|
# Allows detection of aborted catalog builds
|
||||||
with ZipFile(self.__archive_path, mode='a') as zfw:
|
with ZipFile(self.__archive_path, mode='a') as zfw:
|
||||||
zfw.writestr('thumb_width', self.opts.thumb_width)
|
zfw.writestr('thumb_width', self.opts.thumb_width)
|
||||||
@ -3162,15 +3135,17 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
navLabelTag = Tag(ncx_soup, "navLabel")
|
navLabelTag = Tag(ncx_soup, "navLabel")
|
||||||
textTag = Tag(ncx_soup, "text")
|
textTag = Tag(ncx_soup, "text")
|
||||||
if book['series']:
|
if book['series']:
|
||||||
tokens = list(book['title'].partition(':'))
|
series_index = str(book['series_index'])
|
||||||
|
if series_index.endswith('.0'):
|
||||||
|
series_index = series_index[:-2]
|
||||||
if self.generateForKindle:
|
if self.generateForKindle:
|
||||||
# Don't include Author for Kindle
|
# Don't include Author for Kindle
|
||||||
textTag.insert(0, NavigableString(self.formatNCXText('%s (%s)' % \
|
textTag.insert(0, NavigableString(self.formatNCXText('%s (%s [%s])' %
|
||||||
(tokens[2].strip(), tokens[0]), dest='title')))
|
(book['title'], book['series'], series_index), dest='title')))
|
||||||
else:
|
else:
|
||||||
# Include Author for non-Kindle
|
# Include Author for non-Kindle
|
||||||
textTag.insert(0, NavigableString(self.formatNCXText('%s · %s (%s)' % \
|
textTag.insert(0, NavigableString(self.formatNCXText('%s (%s [%s]) · %s ' %
|
||||||
(tokens[2].strip(), book['author'], tokens[0]), dest='title')))
|
(book['title'], book['series'], series_index, book['author']), dest='title')))
|
||||||
else:
|
else:
|
||||||
if self.generateForKindle:
|
if self.generateForKindle:
|
||||||
# Don't include Author for Kindle
|
# Don't include Author for Kindle
|
||||||
@ -3199,8 +3174,13 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
# Add the author tag
|
# Add the author tag
|
||||||
cmTag = Tag(ncx_soup, '%s' % 'calibre:meta')
|
cmTag = Tag(ncx_soup, '%s' % 'calibre:meta')
|
||||||
cmTag['name'] = "author"
|
cmTag['name'] = "author"
|
||||||
|
|
||||||
|
if book['date']:
|
||||||
navStr = '%s | %s' % (self.formatNCXText(book['author'], dest='author'),
|
navStr = '%s | %s' % (self.formatNCXText(book['author'], dest='author'),
|
||||||
book['date'].split()[1])
|
book['date'].split()[1])
|
||||||
|
else:
|
||||||
|
navStr = '%s' % (self.formatNCXText(book['author'], dest='author'))
|
||||||
|
|
||||||
if 'tags' in book and len(book['tags']):
|
if 'tags' in book and len(book['tags']):
|
||||||
navStr = self.formatNCXText(navStr + ' | ' + ' · '.join(sorted(book['tags'])), dest='author')
|
navStr = self.formatNCXText(navStr + ' | ' + ' · '.join(sorted(book['tags'])), dest='author')
|
||||||
cmTag.insert(0, NavigableString(navStr))
|
cmTag.insert(0, NavigableString(navStr))
|
||||||
@ -3725,43 +3705,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
add_to_master_date_range_list(current_titles_list)
|
add_to_master_date_range_list(current_titles_list)
|
||||||
current_titles_list = [book['title']]
|
current_titles_list = [book['title']]
|
||||||
|
|
||||||
'''
|
|
||||||
# Add *article* entries for each populated date range
|
|
||||||
# master_date_range_list{}: [0]:titles list [1]:datestr
|
|
||||||
for books_by_date_range in master_date_range_list:
|
|
||||||
navPointByDateRangeTag = Tag(soup, 'navPoint')
|
|
||||||
navPointByDateRangeTag['class'] = "article"
|
|
||||||
navPointByDateRangeTag['id'] = "%s-ID" % books_by_date_range[1].replace(' ','')
|
|
||||||
navPointTag['playOrder'] = self.playOrder
|
|
||||||
self.playOrder += 1
|
|
||||||
navLabelTag = Tag(soup, 'navLabel')
|
|
||||||
textTag = Tag(soup, 'text')
|
|
||||||
textTag.insert(0, NavigableString(books_by_date_range[1]))
|
|
||||||
navLabelTag.insert(0, textTag)
|
|
||||||
navPointByDateRangeTag.insert(0,navLabelTag)
|
|
||||||
contentTag = Tag(soup, 'content')
|
|
||||||
contentTag['src'] = "%s#bdr_%s" % (HTML_file,
|
|
||||||
books_by_date_range[1].replace(' ',''))
|
|
||||||
|
|
||||||
navPointByDateRangeTag.insert(1,contentTag)
|
|
||||||
|
|
||||||
if self.generateForKindle:
|
|
||||||
cmTag = Tag(soup, '%s' % 'calibre:meta')
|
|
||||||
cmTag['name'] = "description"
|
|
||||||
cmTag.insert(0, NavigableString(books_by_date_range[0]))
|
|
||||||
navPointByDateRangeTag.insert(2, cmTag)
|
|
||||||
|
|
||||||
cmTag = Tag(soup, '%s' % 'calibre:meta')
|
|
||||||
cmTag['name'] = "author"
|
|
||||||
navStr = '%d titles' % books_by_date_range[2] if books_by_date_range[2] > 1 else \
|
|
||||||
'%d title' % books_by_date_range[2]
|
|
||||||
cmTag.insert(0, NavigableString(navStr))
|
|
||||||
navPointByDateRangeTag.insert(3, cmTag)
|
|
||||||
|
|
||||||
navPointTag.insert(nptc, navPointByDateRangeTag)
|
|
||||||
nptc += 1
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Create an NCX article entry for each populated day
|
# Create an NCX article entry for each populated day
|
||||||
# Loop over the booksByDate list, find start of each month,
|
# Loop over the booksByDate list, find start of each month,
|
||||||
# add description_preview_count titles
|
# add description_preview_count titles
|
||||||
@ -3944,7 +3887,8 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
outfile = open("%s/%s.ncx" % (self.catalogPath, self.basename), 'w')
|
outfile = open("%s/%s.ncx" % (self.catalogPath, self.basename), 'w')
|
||||||
outfile.write(self.ncxSoup.prettify())
|
outfile.write(self.ncxSoup.prettify())
|
||||||
|
|
||||||
# Helpers
|
|
||||||
|
# ======================== Helpers ========================
|
||||||
def author_to_author_sort(self, author):
|
def author_to_author_sort(self, author):
|
||||||
tokens = author.split()
|
tokens = author.split()
|
||||||
tokens = tokens[-1:] + tokens[:-1]
|
tokens = tokens[-1:] + tokens[:-1]
|
||||||
@ -3952,45 +3896,39 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
tokens[0] += ','
|
tokens[0] += ','
|
||||||
return ' '.join(tokens).capitalize()
|
return ' '.join(tokens).capitalize()
|
||||||
|
|
||||||
def author_compare(self,x,y):
|
def booksByAuthorSorter_author_sort(self, book):
|
||||||
# Return -1 if x<y
|
'''
|
||||||
# Return 0 if x==y
|
Sort non-series books before series books
|
||||||
# Return 1 if x>y
|
'''
|
||||||
|
if not book['series']:
|
||||||
|
key = '%s %s' % (book['author_sort'].capitalize(),
|
||||||
|
book['title_sort'].capitalize())
|
||||||
|
else:
|
||||||
|
index = book['series_index']
|
||||||
|
integer = int(index)
|
||||||
|
fraction = index-integer
|
||||||
|
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
|
||||||
|
key = '%s ~%s %s' % (book['author_sort'].capitalize(),
|
||||||
|
self.generateSortTitle(book['series']),
|
||||||
|
series_index)
|
||||||
|
return key
|
||||||
|
|
||||||
# Different authors - sort by author_sort
|
def booksByAuthorSorter_author(self, book):
|
||||||
if x['author_sort'].capitalize() > y['author_sort'].capitalize():
|
'''
|
||||||
return 1
|
Sort non-series books before series books
|
||||||
elif x['author_sort'].capitalize() < y['author_sort'].capitalize():
|
'''
|
||||||
return -1
|
if not book['series']:
|
||||||
|
key = '%s %s' % (self.author_to_author_sort(book['author']),
|
||||||
|
book['title_sort'].capitalize())
|
||||||
else:
|
else:
|
||||||
# Same author
|
index = book['series_index']
|
||||||
if x['series'] != y['series']:
|
integer = int(index)
|
||||||
# One title is a series, the other is not
|
fraction = index-integer
|
||||||
if not x['series']:
|
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
|
||||||
# Sort regular titles < series titles
|
key = '%s ~%s %s' % (self.author_to_author_sort(book['author']),
|
||||||
return -1
|
self.generateSortTitle(book['series']),
|
||||||
elif not y['series']:
|
series_index)
|
||||||
return 1
|
return key
|
||||||
|
|
||||||
# Different series
|
|
||||||
if x['title_sort'].lstrip() > y['title_sort'].lstrip():
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
# Same series
|
|
||||||
if x['series'] == y['series']:
|
|
||||||
if float(x['series_index']) > float(y['series_index']):
|
|
||||||
return 1
|
|
||||||
elif float(x['series_index']) < float(y['series_index']):
|
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
if x['series'] > y['series']:
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return -1
|
|
||||||
|
|
||||||
def calculateThumbnailSize(self):
|
def calculateThumbnailSize(self):
|
||||||
''' Calculate thumbnail dimensions based on device DPI. Scale Kindle by 50% '''
|
''' Calculate thumbnail dimensions based on device DPI. Scale Kindle by 50% '''
|
||||||
@ -4155,6 +4093,20 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
# Strip white space to ''
|
# Strip white space to ''
|
||||||
return re.sub("\W","", author)
|
return re.sub("\W","", author)
|
||||||
|
|
||||||
|
def generateFormatArgs(self, book):
|
||||||
|
series_index = str(book['series_index'])
|
||||||
|
if series_index.endswith('.0'):
|
||||||
|
series_index = series_index[:-2]
|
||||||
|
args = dict(
|
||||||
|
title = book['title'],
|
||||||
|
series = book['series'],
|
||||||
|
series_index = series_index,
|
||||||
|
rating = self.generateRatingString(book),
|
||||||
|
rating_parens = '(%s)' % self.generateRatingString(book) if 'rating' in book else '',
|
||||||
|
pubyear = book['date'].split()[1] if book['date'] else '',
|
||||||
|
pubyear_parens = "(%s)" % book['date'].split()[1] if book['date'] else '')
|
||||||
|
return args
|
||||||
|
|
||||||
def generateHTMLByGenre(self, genre, section_head, books, outfile):
|
def generateHTMLByGenre(self, genre, section_head, books, outfile):
|
||||||
# Write an HTML file of this genre's book list
|
# Write an HTML file of this genre's book list
|
||||||
# Return a list with [(first_author, first_book), (last_author, last_book)]
|
# Return a list with [(first_author, first_book), (last_author, last_book)]
|
||||||
@ -4201,16 +4153,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
divTag.insert(dtc,pAuthorTag)
|
divTag.insert(dtc,pAuthorTag)
|
||||||
dtc += 1
|
dtc += 1
|
||||||
|
|
||||||
'''
|
|
||||||
# Insert an <hr /> between non-series and series
|
|
||||||
if not current_series and non_series_books and book['series']:
|
|
||||||
# Insert an <hr />
|
|
||||||
hrTag = Tag(soup,'hr')
|
|
||||||
hrTag['class'] = "series_divider"
|
|
||||||
divTag.insert(dtc,hrTag)
|
|
||||||
dtc += 1
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Check for series
|
# Check for series
|
||||||
if book['series'] and book['series'] != current_series:
|
if book['series'] and book['series'] != current_series:
|
||||||
# Start a new series
|
# Start a new series
|
||||||
@ -4235,17 +4177,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
pBookTag = Tag(soup, "p")
|
pBookTag = Tag(soup, "p")
|
||||||
ptc = 0
|
ptc = 0
|
||||||
|
|
||||||
'''
|
|
||||||
# This if clause does not display MISSING_SYMBOL for wishlist items
|
|
||||||
# If this is the wishlist_tag genre, don't show missing symbols
|
|
||||||
# normalized_wishlist_tag = self.genre_tags_dict[self.opts.wishlist_tag]
|
|
||||||
if self.opts.wishlist_tag in book['tags'] and \
|
|
||||||
self.genre_tags_dict[self.opts.wishlist_tag] != genre:
|
|
||||||
pBookTag['class'] = "wishlist_item"
|
|
||||||
pBookTag.insert(ptc,NavigableString(self.MISSING_SYMBOL))
|
|
||||||
ptc += 1
|
|
||||||
'''
|
|
||||||
|
|
||||||
# book with read|reading|unread symbol or wishlist item
|
# book with read|reading|unread symbol or wishlist item
|
||||||
if self.opts.wishlist_tag in book.get('tags', []):
|
if self.opts.wishlist_tag in book.get('tags', []):
|
||||||
pBookTag['class'] = "wishlist_item"
|
pBookTag['class'] = "wishlist_item"
|
||||||
@ -4271,12 +4202,18 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
if self.opts.generate_descriptions:
|
if self.opts.generate_descriptions:
|
||||||
aTag['href'] = "book_%d.html" % (int(float(book['id'])))
|
aTag['href'] = "book_%d.html" % (int(float(book['id'])))
|
||||||
# Use series, series index if avail else just title
|
|
||||||
|
# Generate the title from the template
|
||||||
|
args = self.generateFormatArgs(book)
|
||||||
if current_series:
|
if current_series:
|
||||||
aTag.insert(0,escape(book['title'][len(book['series'])+1:]))
|
#aTag.insert(0,escape(book['title'][len(book['series'])+1:]))
|
||||||
|
formatted_title = self.by_genres_series_title_template.format(**args).rstrip()
|
||||||
else:
|
else:
|
||||||
aTag.insert(0,escape(book['title']))
|
#aTag.insert(0,escape(book['title']))
|
||||||
|
formatted_title = self.by_genres_normal_title_template.format(**args).rstrip()
|
||||||
non_series_books += 1
|
non_series_books += 1
|
||||||
|
aTag.insert(0,NavigableString(escape(formatted_title)))
|
||||||
|
|
||||||
pBookTag.insert(ptc, aTag)
|
pBookTag.insert(ptc, aTag)
|
||||||
ptc += 1
|
ptc += 1
|
||||||
|
|
||||||
@ -4327,31 +4264,16 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
generated_html = substitute_entites(generated_html)
|
generated_html = substitute_entites(generated_html)
|
||||||
return BeautifulSoup(generated_html)
|
return BeautifulSoup(generated_html)
|
||||||
|
|
||||||
if False:
|
|
||||||
print "title metadata:\n%s" % ', '.join(sorted(book.keys()))
|
|
||||||
if False:
|
|
||||||
for item in sorted(book.keys()):
|
|
||||||
try:
|
|
||||||
print "%s: %s%s" % (item, book[item][:50], '...' if len(book[item])>50 else '')
|
|
||||||
except:
|
|
||||||
print "%s: %s" % (item, book[item])
|
|
||||||
|
|
||||||
# Generate the template arguments
|
# Generate the template arguments
|
||||||
css = P('catalog/stylesheet.css', data=True).decode('utf-8')
|
css = P('catalog/stylesheet.css', data=True).decode('utf-8')
|
||||||
title_str = escape(book['title'])
|
title_str = title = escape(book['title'])
|
||||||
|
series = ''
|
||||||
# Title/series
|
series_index = ''
|
||||||
if book['series']:
|
if book['series']:
|
||||||
series_id, _, title = book['title'].partition(':')
|
|
||||||
title = escape(title.strip())
|
|
||||||
series = escape(book['series'])
|
series = escape(book['series'])
|
||||||
series_index = str(book['series_index'])
|
series_index = str(book['series_index'])
|
||||||
if series_index.endswith('.0'):
|
if series_index.endswith('.0'):
|
||||||
series_index = series_index[:-2]
|
series_index = series_index[:-2]
|
||||||
else:
|
|
||||||
title = escape(book['title'])
|
|
||||||
series = ''
|
|
||||||
series_index = ''
|
|
||||||
|
|
||||||
# Author, author_prefix (read|reading|none symbol or missing symbol)
|
# Author, author_prefix (read|reading|none symbol or missing symbol)
|
||||||
author = book['author']
|
author = book['author']
|
||||||
@ -4392,12 +4314,14 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
|
|
||||||
# Date of publication
|
# Date of publication
|
||||||
pubdate = book['date']
|
pubdate = book['date']
|
||||||
pubmonth, pubyear = pubdate.split(' ')
|
pubmonth, pubyear = pubdate.split()
|
||||||
|
if pubyear == '101':
|
||||||
|
pubdate = pubmonth = pubyear = ''
|
||||||
|
|
||||||
# Thumb
|
# Thumb
|
||||||
_soup = BeautifulSoup('<html>',selfClosingTags=['img'])
|
_soup = BeautifulSoup('<html>',selfClosingTags=['img'])
|
||||||
thumb = Tag(_soup,"img")
|
thumb = Tag(_soup,"img")
|
||||||
if 'cover' in book:
|
if 'cover' in book and book['cover']:
|
||||||
thumb['src'] = "../images/thumbnail_%d.jpg" % int(book['id'])
|
thumb['src'] = "../images/thumbnail_%d.jpg" % int(book['id'])
|
||||||
else:
|
else:
|
||||||
thumb['src'] = "../images/thumbnail_default.jpg"
|
thumb['src'] = "../images/thumbnail_default.jpg"
|
||||||
@ -4562,16 +4486,19 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
draw.text((left, top), text, fill=(0,0,0), font=font)
|
draw.text((left, top), text, fill=(0,0,0), font=font)
|
||||||
img.save(open(out_path, 'wb'), 'GIF')
|
img.save(open(out_path, 'wb'), 'GIF')
|
||||||
|
|
||||||
def generateSeriesTitle(self, title):
|
def generateRatingString(self, book):
|
||||||
if float(title['series_index']) - int(title['series_index']):
|
rating = ''
|
||||||
series_title = '%s %4.2f: %s' % (title['series'],
|
try:
|
||||||
title['series_index'],
|
if 'rating' in book:
|
||||||
title['title'])
|
stars = int(book['rating']) / 2
|
||||||
else:
|
if stars:
|
||||||
series_title = '%s %d: %s' % (title['series'],
|
star_string = self.FULL_RATING_SYMBOL * stars
|
||||||
title['series_index'],
|
empty_stars = self.EMPTY_RATING_SYMBOL * (5 - stars)
|
||||||
title['title'])
|
rating = '%s%s' % (star_string,empty_stars)
|
||||||
return series_title
|
except:
|
||||||
|
# Rating could be None
|
||||||
|
pass
|
||||||
|
return rating
|
||||||
|
|
||||||
def generateShortDescription(self, description, dest=None):
|
def generateShortDescription(self, description, dest=None):
|
||||||
# Truncate the description, on word boundaries if necessary
|
# Truncate the description, on word boundaries if necessary
|
||||||
@ -4610,9 +4537,11 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
raise RuntimeError
|
raise RuntimeError
|
||||||
|
|
||||||
def generateSortTitle(self, title):
|
def generateSortTitle(self, title):
|
||||||
# Generate a string suitable for sorting from the title
|
'''
|
||||||
# Ignore leading stop words
|
Generate a string suitable for sorting from the title
|
||||||
# Optionally convert leading numbers to strings
|
Ignore leading stop words
|
||||||
|
Optionally convert leading numbers to strings
|
||||||
|
'''
|
||||||
from calibre.ebooks.metadata import title_sort
|
from calibre.ebooks.metadata import title_sort
|
||||||
|
|
||||||
# Strip stop words
|
# Strip stop words
|
||||||
@ -4910,13 +4839,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
self.progressInt = coarse_progress + fine_progress
|
self.progressInt = coarse_progress + fine_progress
|
||||||
self.reporter(self.progressInt, self.progressString)
|
self.reporter(self.progressInt, self.progressString)
|
||||||
|
|
||||||
class NotImplementedError:
|
|
||||||
def __init__(self, error):
|
|
||||||
self.error = error
|
|
||||||
|
|
||||||
def logerror(self):
|
|
||||||
self.opts.log.info('%s not implemented' % self.error)
|
|
||||||
|
|
||||||
def run(self, path_to_output, opts, db, notification=DummyReporter()):
|
def run(self, path_to_output, opts, db, notification=DummyReporter()):
|
||||||
opts.log = log
|
opts.log = log
|
||||||
opts.fmt = self.fmt = path_to_output.rpartition('.')[2]
|
opts.fmt = self.fmt = path_to_output.rpartition('.')[2]
|
||||||
@ -4982,11 +4904,12 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
if opts_dict['ids']:
|
if opts_dict['ids']:
|
||||||
build_log.append(" book count: %d" % len(opts_dict['ids']))
|
build_log.append(" book count: %d" % len(opts_dict['ids']))
|
||||||
|
|
||||||
sections_list = ['Authors']
|
|
||||||
'''
|
'''
|
||||||
|
sections_list = []
|
||||||
if opts.generate_authors:
|
if opts.generate_authors:
|
||||||
sections_list.append('Authors')
|
sections_list.append('Authors')
|
||||||
'''
|
'''
|
||||||
|
sections_list = ['Authors']
|
||||||
if opts.generate_titles:
|
if opts.generate_titles:
|
||||||
sections_list.append('Titles')
|
sections_list.append('Titles')
|
||||||
if opts.generate_genres:
|
if opts.generate_genres:
|
||||||
@ -5042,7 +4965,7 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
if catalog_source_built:
|
if catalog_source_built:
|
||||||
log.info(" Completed catalog source generation\n")
|
log.info(" Completed catalog source generation\n")
|
||||||
else:
|
else:
|
||||||
log.warn(" No database hits with supplied criteria")
|
log.warn(" *** Errors during catalog generation, check log for details ***")
|
||||||
|
|
||||||
if catalog_source_built:
|
if catalog_source_built:
|
||||||
recommendations = []
|
recommendations = []
|
||||||
@ -5072,8 +4995,6 @@ then rebuild the catalog.\n''').format(author[0])
|
|||||||
abort_after_input_dump=False)
|
abort_after_input_dump=False)
|
||||||
plumber.merge_ui_recommendations(recommendations)
|
plumber.merge_ui_recommendations(recommendations)
|
||||||
plumber.run()
|
plumber.run()
|
||||||
# returns to gui2.actions.catalog:catalog_generated()
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
# returns to gui2.actions.catalog:catalog_generated()
|
# returns to gui2.actions.catalog:catalog_generated()
|
||||||
return catalog.error
|
return catalog.error
|
||||||
|
@ -693,14 +693,7 @@ def command_catalog(args, dbpath):
|
|||||||
}
|
}
|
||||||
|
|
||||||
with plugin:
|
with plugin:
|
||||||
ret = plugin.run(args[1], opts, get_db(dbpath, opts))
|
return int(bool(plugin.run(args[1], opts, get_db(dbpath, opts))))
|
||||||
if ret is None:
|
|
||||||
ret = 0
|
|
||||||
else:
|
|
||||||
ret = 1
|
|
||||||
return ret
|
|
||||||
|
|
||||||
# end of GR additions
|
|
||||||
|
|
||||||
def parse_series_string(db, label, value):
|
def parse_series_string(db, label, value):
|
||||||
val = unicode(value).strip()
|
val = unicode(value).strip()
|
||||||
|
@ -1061,7 +1061,8 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
self.conn.get('SELECT id, name FROM authors')]
|
self.conn.get('SELECT id, name FROM authors')]
|
||||||
|
|
||||||
def all_author_names(self):
|
def all_author_names(self):
|
||||||
return [i[0].strip() for i in self.conn.get('SELECT name FROM authors') if i[0].strip()]
|
return filter(None, [i[0].strip().replace('|', ',') for i in self.conn.get(
|
||||||
|
'SELECT name FROM authors')])
|
||||||
|
|
||||||
def all_publishers(self):
|
def all_publishers(self):
|
||||||
return [ (i[0], i[1]) for i in \
|
return [ (i[0], i[1]) for i in \
|
||||||
|
@ -547,6 +547,7 @@ Some limitations of PDF input are:
|
|||||||
* Extraction of vector images and tables from within the document is also not supported.
|
* Extraction of vector images and tables from within the document is also not supported.
|
||||||
* Some PDFs use special glyphs to represent ll or ff or fi, etc. Conversion of these may or may not work depending on just how they are represented internally in the PDF.
|
* Some PDFs use special glyphs to represent ll or ff or fi, etc. Conversion of these may or may not work depending on just how they are represented internally in the PDF.
|
||||||
* Some PDFs store their images upside down with a rotation instruction, |app| currently doesn't support that instruction, so the images will be rotated in the output as well.
|
* Some PDFs store their images upside down with a rotation instruction, |app| currently doesn't support that instruction, so the images will be rotated in the output as well.
|
||||||
|
* Links and Tables of Contents are not supported
|
||||||
|
|
||||||
To re-iterate **PDF is a really, really bad** format to use as input. If you absolutely must use PDF, then be prepared for an
|
To re-iterate **PDF is a really, really bad** format to use as input. If you absolutely must use PDF, then be prepared for an
|
||||||
output ranging anywhere from decent to unusable, depending on the input PDF.
|
output ranging anywhere from decent to unusable, depending on the input PDF.
|
||||||
|
@ -101,6 +101,17 @@ We just need some information from you:
|
|||||||
Once you send us the output for a particular operating system, support for the device in that operating system
|
Once you send us the output for a particular operating system, support for the device in that operating system
|
||||||
will appear in the next release of |app|.
|
will appear in the next release of |app|.
|
||||||
|
|
||||||
|
My device is not being detected by |app|?
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Follow these steps to find the problem:
|
||||||
|
|
||||||
|
* Make sure that you are connecting only a single device to your computer at a time. Do not have another |app| supported device like an iPhone/iPad etc. at the same time.
|
||||||
|
* Make sure you are running the latest version of |app|. The latest version can always be downloaded from `http://calibre-ebook.com/download`_.
|
||||||
|
* Ensure your operating system is seeing the device. That is, the device should be mounted as a disk that you can access using Windows explorer or whatever the file management program on your computer is
|
||||||
|
* In calibre, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled.
|
||||||
|
* If all the above steps fail, go to Preferences->Miscellaneous and click debug device detection with your device attached and post the output as a ticket on `http://bugs.calibre-ebook.com`_.
|
||||||
|
|
||||||
How does |app| manage collections on my SONY reader?
|
How does |app| manage collections on my SONY reader?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -274,9 +274,9 @@ class TemplateFormatter(string.Formatter):
|
|||||||
colon += 1
|
colon += 1
|
||||||
|
|
||||||
funcs = formatter_functions.get_functions()
|
funcs = formatter_functions.get_functions()
|
||||||
if fmt[colon:p] in funcs:
|
fname = fmt[colon:p]
|
||||||
field = fmt[colon:p]
|
if fname in funcs:
|
||||||
func = funcs[field]
|
func = funcs[fname]
|
||||||
if func.arg_count == 2:
|
if func.arg_count == 2:
|
||||||
# only one arg expected. Don't bother to scan. Avoids need
|
# only one arg expected. Don't bother to scan. Avoids need
|
||||||
# for escaping characters
|
# for escaping characters
|
||||||
@ -292,6 +292,8 @@ class TemplateFormatter(string.Formatter):
|
|||||||
else:
|
else:
|
||||||
val = func.eval_(self, self.kwargs, self.book, self.locals,
|
val = func.eval_(self, self.kwargs, self.book, self.locals,
|
||||||
val, *args).strip()
|
val, *args).strip()
|
||||||
|
else:
|
||||||
|
return _('%s: unknown function')%fname
|
||||||
if val:
|
if val:
|
||||||
val = self._do_format(val, dispfmt)
|
val = self._do_format(val, dispfmt)
|
||||||
if not val:
|
if not val:
|
||||||
|
@ -839,7 +839,13 @@ class BasicNewsRecipe(Recipe):
|
|||||||
fetcher.image_url_processor = self.image_url_processor
|
fetcher.image_url_processor = self.image_url_processor
|
||||||
res, path, failures = fetcher.start_fetch(url), fetcher.downloaded_paths, fetcher.failed_links
|
res, path, failures = fetcher.start_fetch(url), fetcher.downloaded_paths, fetcher.failed_links
|
||||||
if not res or not os.path.exists(res):
|
if not res or not os.path.exists(res):
|
||||||
raise Exception(_('Could not fetch article. Run with -vv to see the reason'))
|
msg = _('Could not fetch article.') + ' '
|
||||||
|
if self.debug:
|
||||||
|
msg += _('The debug traceback is available earlier in this log')
|
||||||
|
else:
|
||||||
|
msg += _('Run with -vv to see the reason')
|
||||||
|
raise Exception(msg)
|
||||||
|
|
||||||
return res, path, failures
|
return res, path, failures
|
||||||
|
|
||||||
def fetch_article(self, url, dir, f, a, num_of_feeds):
|
def fetch_article(self, url, dir, f, a, num_of_feeds):
|
||||||
@ -902,9 +908,6 @@ class BasicNewsRecipe(Recipe):
|
|||||||
feeds = feeds[:2]
|
feeds = feeds[:2]
|
||||||
self.has_single_feed = len(feeds) == 1
|
self.has_single_feed = len(feeds) == 1
|
||||||
|
|
||||||
if self.use_embedded_content is None:
|
|
||||||
self.use_embedded_content = feeds[0].has_embedded_content()
|
|
||||||
|
|
||||||
index = os.path.join(self.output_dir, 'index.html')
|
index = os.path.join(self.output_dir, 'index.html')
|
||||||
|
|
||||||
html = self.feeds2index(feeds)
|
html = self.feeds2index(feeds)
|
||||||
@ -939,7 +942,9 @@ class BasicNewsRecipe(Recipe):
|
|||||||
url = None
|
url = None
|
||||||
if not url:
|
if not url:
|
||||||
continue
|
continue
|
||||||
func, arg = (self.fetch_embedded_article, article) if self.use_embedded_content else \
|
func, arg = (self.fetch_embedded_article, article) \
|
||||||
|
if self.use_embedded_content or (self.use_embedded_content == None and feed.has_embedded_content()) \
|
||||||
|
else \
|
||||||
((self.fetch_obfuscated_article if self.articles_are_obfuscated \
|
((self.fetch_obfuscated_article if self.articles_are_obfuscated \
|
||||||
else self.fetch_article), url)
|
else self.fetch_article), url)
|
||||||
req = WorkRequest(func, (arg, art_dir, f, a, len(feed)),
|
req = WorkRequest(func, (arg, art_dir, f, a, len(feed)),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user