KG updates

This commit is contained in:
GRiker 2011-01-28 04:19:02 -07:00
commit 6713cbfc54
97 changed files with 30375 additions and 17882 deletions

View File

@ -2,7 +2,7 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__author__ = 'Luis Hernandez' __author__ = 'Luis Hernandez'
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>' __copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
description = 'Periódico gratuito en español - v0.5 - 25 Jan 2011' description = 'Periódico gratuito en español - v0.8 - 27 Jan 2011'
''' '''
www.20minutos.es www.20minutos.es
@ -15,8 +15,8 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
title = u'20 Minutos' title = u'20 Minutos'
publisher = u'Grupo 20 Minutos' publisher = u'Grupo 20 Minutos'
__author__ = u'Luis Hernández' __author__ = 'Luis Hernández'
description = u'Periódico gratuito en español' description = 'Periódico gratuito en español'
cover_url = 'http://estaticos.20minutos.es/mmedia/especiales/corporativo/css/img/logotipos_grupo20minutos.gif' cover_url = 'http://estaticos.20minutos.es/mmedia/especiales/corporativo/css/img/logotipos_grupo20minutos.gif'
oldest_article = 5 oldest_article = 5
@ -30,8 +30,9 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
language = 'es' language = 'es'
timefmt = '[%a, %d %b, %Y]' timefmt = '[%a, %d %b, %Y]'
keep_only_tags = [dict(name='div', attrs={'id':['content']}) keep_only_tags = [
,dict(name='div', attrs={'class':['boxed','description','lead','article-content']}) dict(name='div', attrs={'id':['content','vinetas',]})
,dict(name='div', attrs={'class':['boxed','description','lead','article-content','cuerpo estirar']})
,dict(name='span', attrs={'class':['photo-bar']}) ,dict(name='span', attrs={'class':['photo-bar']})
,dict(name='ul', attrs={'class':['article-author']}) ,dict(name='ul', attrs={'class':['article-author']})
] ]
@ -42,9 +43,11 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
remove_tags = [ remove_tags = [
dict(name='ol', attrs={'class':['navigation',]}) dict(name='ol', attrs={'class':['navigation',]})
,dict(name='span', attrs={'class':['action']}) ,dict(name='span', attrs={'class':['action']})
,dict(name='div', attrs={'class':['twitter comments-list hidden','related-news','col']}) ,dict(name='div', attrs={'class':['twitter comments-list hidden','related-news','col','photo-gallery','calendario','article-comment','postto estirar','otras_vinetas estirar','kment','user-actions']})
,dict(name='div', attrs={'id':['twitter-destacados']}) ,dict(name='div', attrs={'id':['twitter-destacados','eco-tabs','inner','vineta_calendario','vinetistas clearfix','otras_vinetas estirar','MIN1','main','SUP1','INT']})
,dict(name='ul', attrs={'class':['article-user-actions','stripped-list']}) ,dict(name='ul', attrs={'class':['article-user-actions','stripped-list']})
,dict(name='ul', attrs={'id':['site-links']})
,dict(name='li', attrs={'class':['puntuacion','enviar','compartir']})
] ]
feeds = [ feeds = [
@ -62,6 +65,6 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
,(u'Empleo' , u'http://www.20minutos.es/rss/empleo/') ,(u'Empleo' , u'http://www.20minutos.es/rss/empleo/')
,(u'Cine' , u'http://www.20minutos.es/rss/cine/') ,(u'Cine' , u'http://www.20minutos.es/rss/cine/')
,(u'Musica' , u'http://www.20minutos.es/rss/musica/') ,(u'Musica' , u'http://www.20minutos.es/rss/musica/')
,(u'Vinetas' , u'http://www.20minutos.es/rss/vinetas/')
,(u'Comunidad20' , u'http://www.20minutos.es/rss/zona20/') ,(u'Comunidad20' , u'http://www.20minutos.es/rss/zona20/')
] ]

View File

@ -44,6 +44,7 @@ class CanWestPaper(BasicNewsRecipe):
language = 'en_CA' language = 'en_CA'
__author__ = 'Nick Redding' __author__ = 'Nick Redding'
encoding = 'latin1'
no_stylesheets = True no_stylesheets = True
timefmt = ' [%b %d]' timefmt = ' [%b %d]'
extra_css = ''' extra_css = '''
@ -97,6 +98,8 @@ class CanWestPaper(BasicNewsRecipe):
atag = h1tag.find('a',href=True) atag = h1tag.find('a',href=True)
if not atag: if not atag:
continue continue
url = atag['href']
if not url.startswith('http:'):
url = self.url_prefix+'/news/todays-paper/'+atag['href'] url = self.url_prefix+'/news/todays-paper/'+atag['href']
#self.log("Section %s" % key) #self.log("Section %s" % key)
#self.log("url %s" % url) #self.log("url %s" % url)

View File

@ -0,0 +1,11 @@
from calibre.web.feeds.news import BasicNewsRecipe
class CapesnBabesRecipe(BasicNewsRecipe):
title = u'Capes n Babes'
language = 'en'
description = 'The Capes n Babes comic Blog'
__author__ = 'skyhawker'
oldest_article = 31
max_articles_per_feed = 100
use_embedded_content = True
feeds = [(u'Capes & Babes', u'feed://www.capesnbabes.com/feed/')]

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8
__license__ = 'GPL v3'
__author__ = 'Luis Hernandez'
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
'''
http://www.filmica.com/david_bravo/
'''
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
title = u'Blog de David Bravo'
publisher = u'Filmica'
__author__ = 'Luis Hernández'
description = 'blog sobre leyes, p2p y copyright'
cover_url = 'http://www.elpais.es/edigitales/image.php?foto=par/portada/1551.jpg'
oldest_article = 365
max_articles_per_feed = 100
remove_javascript = True
no_stylesheets = True
use_embedded_content = False
encoding = 'ISO-8859-1'
language = 'es'
timefmt = '[%a, %d %b, %Y]'
keep_only_tags = [
dict(name='div', attrs={'class':['blog','date','blogbody','comments-head','comments-body']})
,dict(name='span', attrs={'class':['comments-post']})
]
remove_tags_before = dict(name='div' , attrs={'id':['bitacoras']})
remove_tags_after = dict(name='div' , attrs={'id':['comments-body']})
extra_css = ' p{text-align: justify; font-size: 100%} body{ text-align: left; font-family: serif; font-size: 100% } h2{ font-family: sans-serif; font-size:75%; font-weight: 800; text-align: justify } h3{ font-family: sans-serif; font-size:150%; font-weight: 600; text-align: left } img{margin-bottom: 0.4em} '
feeds = [(u'Blog', u'http://www.filmica.com/david_bravo/index.rdf')]

View File

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3'
__author__ = 'Luis Hernandez'
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
description = 'Diario independiente de Asturias - v1.0 - 27 Jan 2011'
'''
www.lne.es
'''
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
title = u'La Nueva España'
publisher = u'Editorial Prensa Iberica'
__author__ = 'Luis Hernandez'
description = 'Diario independiente de Asturias'
cover_url = 'http://estaticos00.lne.es//elementosWeb/mediaweb/images/iconos/logo2.jpg'
oldest_article = 3
max_articles_per_feed = 100
remove_javascript = True
no_stylesheets = True
use_embedded_content = False
encoding = 'ISO-8859-1'
language = 'es'
timefmt = '[%a, %d %b, %Y]'
keep_only_tags = [
dict(name='div', attrs={'class':['noticia_titular','subtitulo','noticiadd2','noticia_texto']})
,dict(name='div', attrs={'id':['noticia_texto']})
]
extra_css = ' p{text-align: justify; font-size: 100%} body{ text-align: left; font-family: serif; font-size: 100% } h1{ font-family: sans-serif; font-size:150%; font-weight: 600; text-align: justify; } h2{ font-family: sans-serif; font-size:120%; font-weight: 500; text-align: justify } '
remove_tags_before = dict(name='div' , attrs={'class':['contenedor']})
remove_tags_after = dict(name='div' , attrs={'class':['fin_noticia']})
remove_tags = [
dict(name='div', attrs={'class':['epigrafe','antetitulo','bloqueclear','bloqueclear_video','cuadro_multimedia','cintillo2','editor_documentos','noticiadd','noticiadd3','noticiainterior','fin_noticia']})
,dict(name='div', attrs={'id':['evotos']})
]
feeds = [
(u'Al minuto' , u'http://www.lne.es/elementosInt/rss/AlMinuto')
,(u'General' , u'http://www.lne.es/elementosInt/rss/55')
,(u'Nacional' , u'http://www.lne.es/elementosInt/rss/43')
,(u'Internacional' , u'http://www.lne.es/elementosInt/rss/44')
,(u'Economia' , u'http://www.lne.es/elementosInt/rss/45')
,(u'Deportes' , u'http://www.lne.es/elementosInt/rss/47')
,(u'Campeones' , u'http://www.lne.es/elementosInt/rss/65')
,(u'Sociedad' , u'http://www.lne.es/elementosInt/rss/46')
,(u'Sucesos' , u'http://www.lne.es/elementosInt/rss/48')
,(u'Galeria' , u'http://www.lne.es/elementosInt/rss/51')
,(u'Cultura' , u'http://www.lne.es/elementosInt/rss/66')
,(u'Motor' , u'http://www.lne.es/elementosInt/rss/62')
,(u'Opinion' , u'http://www.lne.es/elementosInt/rss/52')
,(u'Asturias' , u'http://www.lne.es/elementosInt/rss/42')
,(u'Oviedo' , u'http://www.lne.es/elementosInt/rss/31')
,(u'Gijon' , u'http://www.lne.es/elementosInt/rss/35')
,(u'Aviles' , u'http://www.lne.es/elementosInt/rss/36')
,(u'Nalon' , u'http://www.lne.es/elementosInt/rss/37')
,(u'Cuencas' , u'http://www.lne.es/elementosInt/rss/38')
,(u'Caudal' , u'http://www.lne.es/elementosInt/rss/39')
,(u'Oriente' , u'http://www.lne.es/elementosInt/rss/40')
,(u'Occidente' , u'http://www.lne.es/elementosInt/rss/41')
,(u'Mar y Campo' , u'http://www.lne.es/elementosInt/rss/63')
,(u'Ultima' , u'http://www.lne.es/elementosInt/rss/50')
]

View File

@ -1,9 +1,22 @@
# -*- coding: utf-8 -*-
__license__ = 'GPL v3'
__author__ = 'Luis Hernandez'
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
description = 'Diario local de Talavera de la Reina - v1.2 - 27 Jan 2011'
'''
http://www.latribunadetalavera.es/
'''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1294946868(BasicNewsRecipe): class AdvancedUserRecipe1294946868(BasicNewsRecipe):
title = u'La Tribuna de Talavera' title = u'La Tribuna de Talavera'
publisher = u'Grupo PROMECAL'
__author__ = 'Luis Hernández' __author__ = 'Luis Hernández'
description = 'Diario de Talavera de la Reina' description = 'Diario local de Talavera de la Reina'
cover_url = 'http://www.latribunadetalavera.es/entorno/mancheta.gif' cover_url = 'http://www.latribunadetalavera.es/entorno/mancheta.gif'
oldest_article = 5 oldest_article = 5
@ -17,7 +30,8 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
language = 'es' language = 'es'
timefmt = '[%a, %d %b, %Y]' timefmt = '[%a, %d %b, %Y]'
keep_only_tags = [dict(name='div', attrs={'id':['articulo']}) keep_only_tags = [
dict(name='div', attrs={'id':['articulo']})
,dict(name='div', attrs={'class':['foto']}) ,dict(name='div', attrs={'class':['foto']})
,dict(name='p', attrs={'id':['texto']}) ,dict(name='p', attrs={'id':['texto']})
] ]
@ -25,5 +39,13 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
remove_tags_before = dict(name='div' , attrs={'class':['comparte']}) remove_tags_before = dict(name='div' , attrs={'class':['comparte']})
remove_tags_after = dict(name='div' , attrs={'id':['relacionadas']}) remove_tags_after = dict(name='div' , attrs={'id':['relacionadas']})
extra_css = ' p{text-align: justify; font-size: 100%} body{ text-align: left; font-family: serif; font-size: 100% } h1{ font-family: sans-serif; font-size:150%; font-weight: 700; text-align: justify; } h2{ font-family: sans-serif; font-size:120%; font-weight: 600; text-align: justify } h3{ font-family: sans-serif; font-size:60%; font-weight: 600; text-align: left } h4{ font-family: sans-serif; font-size:80%; font-weight: 600; text-align: left } h5{ font-family: sans-serif; font-size:70%; font-weight: 600; text-align: left }img{margin-bottom: 0.4em} '
def preprocess_html(self, soup):
for alink in soup.findAll('a'):
if alink.string is not None:
tstr = alink.string
alink.replaceWith(tstr)
return soup
feeds = [(u'Portada', u'http://www.latribunadetalavera.es/rss.html')] feeds = [(u'Portada', u'http://www.latribunadetalavera.es/rss.html')]

View File

@ -0,0 +1,40 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1292550626(BasicNewsRecipe):
title = 'Leduc - Wetaskiwin Pipestone Flyer'
__author__ = 'Brian Hahn'
description = 'News from Alberta, Canada'
oldest_article = 56
max_articles_per_feed = 100
no_stylesheets = True
#delay = 1
use_embedded_content = False
publisher = 'Pipestone Publishing'
category = 'News, Alberta, Canada'
language = 'en_CA'
encoding = 'iso-8859-1'
cover_url = 'http://www.pipestoneflyer.ca/images/calibre-cover.jpg'
remove_tags_before = dict(id='ContentPanel')
remove_tags_after = dict(id='ContentPanel')
remove_tags = [dict(name='div', attrs={'id':'StoryNav'}),dict(name='div', attrs={'id':'BottomAds'}),dict(name='div', attrs={'id':'MoreStoryLinks'})]
extra_css = 'img { margin:5px }'
feeds = [
('Feature', 'http://www.pipestoneflyer.ca/Feature.rss'),
('Editors Desk', 'http://www.pipestoneflyer.ca/Editor%27s%20Desk.rss'),
('Letters', 'http://www.pipestoneflyer.ca/Letters.rss'),
('A Loco Viewpoint', 'http://www.pipestoneflyer.ca/A%20Loco%20Viewpoint.rss'),
('Lifes Doorway', 'http://www.pipestoneflyer.ca/Life%27s%20Doorway.rss'),
('From the Otherside', 'http://www.pipestoneflyer.ca/From%20the%20Otherside.rss'),
('Opinion', 'http://www.pipestoneflyer.ca/Opinion.rss'),
('Community', 'http://www.pipestoneflyer.ca/Community.rss'),
('Sports', 'http://www.pipestoneflyer.ca/Sports.rss'),
('Chambers', 'http://www.pipestoneflyer.ca/Chambers.rss'),
('Government', 'http://www.pipestoneflyer.ca/Government.rss'),
('Environment', 'http://www.pipestoneflyer.ca/Environment.rss'),
('Health', 'http://www.pipestoneflyer.ca/Health.rss'),
('Funnies', 'http://www.pipestoneflyer.ca/Funnies.rss'),
('Faith', 'http://www.pipestoneflyer.ca/Faith.rss'),
('News and Views', 'http://www.pipestoneflyer.ca/News%20and%20Views.rss'),
('Obituaries', 'http://www.pipestoneflyer.ca/Obituaries.rss'),
('Police Blotter', 'http://www.pipestoneflyer.ca/Police%20Blotter.rss'),
]

View File

@ -0,0 +1,15 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1296179411(BasicNewsRecipe):
title = u'SPIN Magzine'
__author__ = 'Quistopher'
language = 'en'
oldest_article = 7
max_articles_per_feed = 100
feeds = [
(u'Daily Noise Blog | SPIN.com', u'http://www.spin.com/blog/feed'),
(u'It Happened Last Night | SPIN.com', u'http://www.spin.com/it-happened-last-night/feed'),
(u'Album Reviews | SPIN.com', u'http://www.spin.com/album-reviews/feed')
]

View File

@ -495,6 +495,22 @@ class SonyReader900Output(SonyReaderOutput):
screen_size = (600, 999) screen_size = (600, 999)
comic_screen_size = screen_size comic_screen_size = screen_size
class GenericEink(SonyReaderOutput):
name = 'Generic e-ink'
short_name = 'generic_eink'
description = _('Suitable for use with any e-ink device')
epub_periodical_format = None
class GenericEinkLarge(GenericEink):
name = 'Generic e-ink large'
short_name = 'generic_eink_large'
description = _('Suitable for use with any large screen e-ink device')
screen_size = (600, 999)
comic_screen_size = screen_size
class JetBook5Output(OutputProfile): class JetBook5Output(OutputProfile):
name = 'JetBook 5-inch' name = 'JetBook 5-inch'
@ -719,6 +735,6 @@ output_profiles = [OutputProfile, SonyReaderOutput, SonyReader300Output,
iPadOutput, KoboReaderOutput, TabletOutput, SamsungGalaxy, iPadOutput, KoboReaderOutput, TabletOutput, SamsungGalaxy,
SonyReaderLandscapeOutput, KindleDXOutput, IlliadOutput, SonyReaderLandscapeOutput, KindleDXOutput, IlliadOutput,
IRexDR1000Output, IRexDR800Output, JetBook5Output, NookOutput, IRexDR1000Output, IRexDR800Output, JetBook5Output, NookOutput,
BambookOutput, NookColorOutput] BambookOutput, NookColorOutput, GenericEink, GenericEinkLarge]
output_profiles.sort(cmp=lambda x,y:cmp(x.name.lower(), y.name.lower())) output_profiles.sort(cmp=lambda x,y:cmp(x.name.lower(), y.name.lower()))

View File

@ -36,6 +36,7 @@ def author_to_author_sort(author):
return author return author
author = _bracket_pat.sub('', author).strip() author = _bracket_pat.sub('', author).strip()
tokens = author.split() tokens = author.split()
if tokens and tokens[-1] not in ('Inc.', 'Inc'):
tokens = tokens[-1:] + tokens[:-1] tokens = tokens[-1:] + tokens[:-1]
if len(tokens) > 1 and method != 'nocomma': if len(tokens) > 1 and method != 'nocomma':
tokens[0] += ',' tokens[0] += ','

View File

@ -143,7 +143,9 @@ class PML_HTMLizer(object):
def __init__(self): def __init__(self):
self.state = {} self.state = {}
self.toc = TOC() # toc consists of a tuple
# (level, (href, id, text))
self.toc = []
self.file_name = '' self.file_name = ''
def prepare_pml(self, pml): def prepare_pml(self, pml):
@ -494,7 +496,7 @@ class PML_HTMLizer(object):
output = [] output = []
self.state = {} self.state = {}
self.toc = TOC() self.toc = []
self.file_name = file_name self.file_name = file_name
indent_state = {'t': False, 'T': False} indent_state = {'t': False, 'T': False}
@ -542,6 +544,7 @@ class PML_HTMLizer(object):
# inside of ="" so we don't have do special processing # inside of ="" so we don't have do special processing
# for C. # for C.
t = '' t = ''
level = 0
if c in 'XC': if c in 'XC':
level = line.read(1) level = line.read(1)
id = 'pml_toc-%s' % len(self.toc) id = 'pml_toc-%s' % len(self.toc)
@ -553,7 +556,7 @@ class PML_HTMLizer(object):
if not value or value == '': if not value or value == '':
text = t text = t
else: else:
self.toc.add_item(os.path.basename(self.file_name), id, value) self.toc.append((level, (os.path.basename(self.file_name), id, value)))
text = '%s<span id="%s"></span>' % (t, id) text = '%s<span id="%s"></span>' % (t, id)
elif c == 'm': elif c == 'm':
empty = False empty = False
@ -624,7 +627,72 @@ class PML_HTMLizer(object):
return output return output
def get_toc(self): def get_toc(self):
return self.toc '''
Toc can have up to 5 levels, 0 - 4 inclusive.
This function will add items to their appropriate
depth in the TOC tree. If the specified depth is
invalid (item would not have a valid parent) add
it to the next valid level above the specified
level.
'''
# Base toc object all items will be added to.
n_toc = TOC()
# Used to track nodes in the toc so we can add
# sub items to the appropriate place in tree.
t_l0 = None
t_l1 = None
t_l2 = None
t_l3 = None
for level, (href, id, text) in self.toc:
if level == u'0':
t_l0 = n_toc.add_item(href, id, text)
t_l1 = None
t_l2 = None
t_l3 = None
elif level == u'1':
if t_l0 == None:
t_l0 = n_toc
t_l1 = t_l0.add_item(href, id, text)
t_l2 = None
t_l3 = None
elif level == u'2':
if t_l1 == None:
if t_l0 == None:
t_l1 = n_toc
else:
t_l1 = t_l0
t_l2 = t_l1.add_item(href, id, text)
t_l3 = None
elif level == u'3':
if t_l2 == None:
if t_l1 == None:
if t_l0 == None:
t_l2 = n_toc
else:
t_l2 = t_l0
else:
t_l2 = t_l1
t_l3 = t_l2.add_item(href, id, text)
# Level 4.
# Anything above 4 is invalid but we will count
# it as level 4.
else:
if t_l3 == None:
if t_l2 == None:
if t_l1 == None:
if t_l0 == None:
t_l3 = n_toc
else:
t_l3 = t_l0
else:
t_l3 = t_l1
else:
t_l3 = t_l2
t_l3.add_item(href, id, text)
return n_toc
def pml_to_html(pml): def pml_to_html(pml):

View File

@ -83,7 +83,6 @@ class TXTInput(InputFormatPlugin):
setattr(options, 'markup_chapter_headings', True) setattr(options, 'markup_chapter_headings', True)
setattr(options, 'italicize_common_cases', True) setattr(options, 'italicize_common_cases', True)
setattr(options, 'fix_indents', True) setattr(options, 'fix_indents', True)
setattr(options, 'preserve_spaces', True)
setattr(options, 'delete_blank_paragraphs', True) setattr(options, 'delete_blank_paragraphs', True)
setattr(options, 'format_scene_breaks', True) setattr(options, 'format_scene_breaks', True)
setattr(options, 'dehyphenate', True) setattr(options, 'dehyphenate', True)

View File

@ -20,9 +20,13 @@ HTML_TEMPLATE = u'<html><head><meta http-equiv="Content-Type" content="text/html
def clean_txt(txt): def clean_txt(txt):
if isbytestring(txt): if isbytestring(txt):
txt = txt.decode('utf-8', 'replace') txt = txt.decode('utf-8', 'replace')
# Strip whitespace from the beginning and end of the line. Also replace # Strip whitespace from the end of the line. Also replace
# all line breaks with \n. # all line breaks with \n.
txt = '\n'.join([line.strip() for line in txt.splitlines()]) txt = '\n'.join([line.rstrip() for line in txt.splitlines()])
# Replace whitespace at the beginning of the list with &nbsp;
txt = re.sub('(?m)(?P<space>[ ]+)', lambda mo: '&nbsp;' * mo.groups('space').count(' '), txt)
txt = re.sub('(?m)(?P<space>[\t]+)', lambda mo: '&nbsp;' * 4 * mo.groups('space').count('\t'), txt)
# Condense redundant spaces # Condense redundant spaces
txt = re.sub('[ ]{2,}', ' ', txt) txt = re.sub('[ ]{2,}', ' ', txt)
@ -31,7 +35,7 @@ def clean_txt(txt):
txt = re.sub('^\s+(?=.)', '', txt) txt = re.sub('^\s+(?=.)', '', txt)
txt = re.sub('(?<=.)\s+$', '', txt) txt = re.sub('(?<=.)\s+$', '', txt)
# Remove excessive line breaks. # Remove excessive line breaks.
txt = re.sub('\n{3,}', '\n\n', txt) txt = re.sub('\n{5,}', '\n\n\n\n', txt)
#remove ASCII invalid chars : 0 to 8 and 11-14 to 24 #remove ASCII invalid chars : 0 to 8 and 11-14 to 24
txt = clean_ascii_chars(txt) txt = clean_ascii_chars(txt)
@ -59,10 +63,16 @@ def convert_basic(txt, title='', epub_split_size_kb=0):
txt = split_txt(txt, epub_split_size_kb) txt = split_txt(txt, epub_split_size_kb)
lines = [] lines = []
blank_count = 0
# Split into paragraphs based on having a blank line between text. # Split into paragraphs based on having a blank line between text.
for line in txt.split('\n\n'): for line in txt.split('\n'):
if line.strip(): if line.strip():
blank_count = 0
lines.append(u'<p>%s</p>' % prepare_string_for_xml(line.replace('\n', ' '))) lines.append(u'<p>%s</p>' % prepare_string_for_xml(line.replace('\n', ' ')))
else:
blank_count += 1
if blank_count == 2:
lines.append(u'<p>&nbsp;</p>')
return HTML_TEMPLATE % (title, u'\n'.join(lines)) return HTML_TEMPLATE % (title, u'\n'.join(lines))
@ -85,7 +95,7 @@ def normalize_line_endings(txt):
return txt return txt
def separate_paragraphs_single_line(txt): def separate_paragraphs_single_line(txt):
txt = re.sub(u'(?<=.)\n(?=.)', '\n\n', txt) txt = txt.replace('\n', '\n\n')
return txt return txt
def separate_paragraphs_print_formatted(txt): def separate_paragraphs_print_formatted(txt):
@ -93,7 +103,7 @@ def separate_paragraphs_print_formatted(txt):
return txt return txt
def preserve_spaces(txt): def preserve_spaces(txt):
txt = txt.replace(' ', '&nbsp;') txt = re.sub('(?P<space>[ ]{2,})', lambda mo: ' ' + ('&nbsp;' * (len(mo.group('space')) - 1)), txt)
txt = txt.replace('\t', '&nbsp;&nbsp;&nbsp;&nbsp;') txt = txt.replace('\t', '&nbsp;&nbsp;&nbsp;&nbsp;')
return txt return txt

View File

@ -100,6 +100,9 @@ class AddAction(InterfaceAction):
mi = MetaInformation(_('Unknown'), dlg.selected_authors) mi = MetaInformation(_('Unknown'), dlg.selected_authors)
self.gui.library_view.model().db.import_book(mi, []) self.gui.library_view.model().db.import_book(mi, [])
self.gui.library_view.model().books_added(num) self.gui.library_view.model().books_added(num)
if hasattr(self.gui, 'db_images'):
self.gui.db_images.reset()
self.gui.tags_view.recount()
def add_isbns(self, books, add_tags=[]): def add_isbns(self, books, add_tags=[]):
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation

View File

@ -17,7 +17,7 @@ from calibre.gui2.actions import InterfaceAction
class GenerateCatalogAction(InterfaceAction): class GenerateCatalogAction(InterfaceAction):
name = 'Generate Catalog' name = 'Generate Catalog'
action_spec = (_('Create catalog of books in your calibre library'), None, None, None) action_spec = (_('Create a catalog of the books in your calibre library'), None, None, None)
dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
def generate_catalog(self): def generate_catalog(self):

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os, shutil import os, shutil
from functools import partial from functools import partial
from PyQt4.Qt import QMenu, Qt, QInputDialog, QThread, pyqtSignal, QProgressDialog from PyQt4.Qt import QMenu, Qt, QInputDialog
from calibre import isbytestring from calibre import isbytestring
from calibre.constants import filesystem_encoding from calibre.constants import filesystem_encoding
@ -16,7 +16,7 @@ from calibre.utils.config import prefs
from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \ from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
question_dialog, info_dialog question_dialog, info_dialog
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2.dialogs.check_library import CheckLibraryDialog from calibre.gui2.dialogs.check_library import CheckLibraryDialog, DBCheck
class LibraryUsageStats(object): # {{{ class LibraryUsageStats(object): # {{{
@ -76,76 +76,6 @@ class LibraryUsageStats(object): # {{{
self.write_stats() self.write_stats()
# }}} # }}}
# Check Integrity {{{
class VacThread(QThread):
check_done = pyqtSignal(object, object)
callback = pyqtSignal(object, object)
def __init__(self, parent, db):
QThread.__init__(self, parent)
self.db = db
self._parent = parent
def run(self):
err = bad = None
try:
bad = self.db.check_integrity(self.callbackf)
except:
import traceback
err = traceback.format_exc()
self.check_done.emit(bad, err)
def callbackf(self, progress, msg):
self.callback.emit(progress, msg)
class CheckIntegrity(QProgressDialog):
def __init__(self, db, parent=None):
QProgressDialog.__init__(self, parent)
self.db = db
self.setCancelButton(None)
self.setMinimum(0)
self.setMaximum(100)
self.setWindowTitle(_('Checking database integrity'))
self.setAutoReset(False)
self.setValue(0)
self.vthread = VacThread(self, db)
self.vthread.check_done.connect(self.check_done,
type=Qt.QueuedConnection)
self.vthread.callback.connect(self.callback, type=Qt.QueuedConnection)
self.vthread.start()
def callback(self, progress, msg):
self.setLabelText(msg)
self.setValue(int(100*progress))
def check_done(self, bad, err):
if err:
error_dialog(self, _('Error'),
_('Failed to check database integrity'),
det_msg=err, show=True)
elif bad:
titles = [self.db.title(x, index_is_id=True) for x in bad]
det_msg = '\n'.join(titles)
warning_dialog(self, _('Some inconsistencies found'),
_('The following books had formats or covers listed in the '
'database that are not actually available. '
'The entries for the formats/covers have been removed. '
'You should check them manually. This can '
'happen if you manipulate the files in the '
'library folder directly.'), det_msg=det_msg, show=True)
else:
info_dialog(self, _('No errors found'),
_('The integrity check completed with no uncorrectable errors found.'),
show=True)
self.reset()
# }}}
class ChooseLibraryAction(InterfaceAction): class ChooseLibraryAction(InterfaceAction):
name = 'Choose Library' name = 'Choose Library'
@ -209,14 +139,6 @@ class ChooseLibraryAction(InterfaceAction):
None, None), attr='action_check_library') None, None), attr='action_check_library')
ac.triggered.connect(self.check_library, type=Qt.QueuedConnection) ac.triggered.connect(self.check_library, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac) self.maintenance_menu.addAction(ac)
ac = self.create_action(spec=(_('Check database integrity'), 'lt.png',
None, None), attr='action_check_database')
ac.triggered.connect(self.check_database, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac)
ac = self.create_action(spec=(_('Recover database'), 'lt.png',
None, None), attr='action_restore_database')
ac.triggered.connect(self.restore_database, type=Qt.QueuedConnection)
self.maintenance_menu.addAction(ac)
self.choose_menu.addMenu(self.maintenance_menu) self.choose_menu.addMenu(self.maintenance_menu)
def pick_random(self, *args): def pick_random(self, *args):
@ -346,28 +268,35 @@ class ChooseLibraryAction(InterfaceAction):
'rate of approximately 1 book every three seconds.'), show=True) 'rate of approximately 1 book every three seconds.'), show=True)
def check_library(self): def check_library(self):
db = self.gui.library_view.model().db self.gui.library_view.save_state()
d = CheckLibraryDialog(self.gui.parent(), db)
d.exec_()
def check_database(self, *args):
m = self.gui.library_view.model() m = self.gui.library_view.model()
m.stop_metadata_backup() m.stop_metadata_backup()
try: db = m.db
d = CheckIntegrity(m.db, self.gui) db.prefs.disable_setting = True
d.exec_()
finally:
m.start_metadata_backup()
def restore_database(self): d = DBCheck(self.gui, db)
info_dialog(self.gui, _('Recover database'), '<p>'+ d.start()
_( try:
'This command rebuilds your calibre database from the information ' d.conn.close()
'stored by calibre in the OPF files.<p>' except:
'This function is not currently available in the GUI. You can ' pass
'recover your database using the \'calibredb restore_database\' ' d.break_cycles()
'command line function.' self.gui.library_moved(db.library_path, call_close=not
), show=True) d.closed_orig_conn)
if d.rejected:
return
if d.error is None:
if not question_dialog(self.gui, _('Success'),
_('Found no errors in your calibre library database.'
' Do you want calibre to check if the files in your '
' library match the information in the database?')):
return
else:
return error_dialog(self.gui, _('Failed'),
_('Database integrity check failed, click Show details'
' for details.'), show=True, det_msg=d.error[1])
d = CheckLibraryDialog(self.gui, m.db)
d.exec_()
def switch_requested(self, location): def switch_requested(self, location):
if not self.change_library_allowed(): if not self.change_library_allowed():

View File

@ -31,7 +31,7 @@ class ConvertAction(InterfaceAction):
partial(self.convert_ebook, False, bulk=True)) partial(self.convert_ebook, False, bulk=True))
cm.addSeparator() cm.addSeparator()
ac = cm.addAction( ac = cm.addAction(
_('Create catalog of books in your calibre library')) _('Create a catalog of the books in your calibre library'))
ac.triggered.connect(self.gui.iactions['Generate Catalog'].generate_catalog) ac.triggered.connect(self.gui.iactions['Generate Catalog'].generate_catalog)
self.qaction.setMenu(cm) self.qaction.setMenu(cm)
self.qaction.triggered.connect(self.convert_ebook) self.qaction.triggered.connect(self.convert_ebook)

View File

@ -19,7 +19,9 @@ class PluginWidget(QWidget, Ui_Form):
('bib_entry', 0), #mixed ('bib_entry', 0), #mixed
('bibfile_enc', 0), #utf-8 ('bibfile_enc', 0), #utf-8
('bibfile_enctag', 0), #strict ('bibfile_enctag', 0), #strict
('impcit', True) ] ('impcit', True),
('addfiles', False),
]
sync_enabled = False sync_enabled = False
formats = set(['bib']) formats = set(['bib'])
@ -49,7 +51,7 @@ class PluginWidget(QWidget, Ui_Form):
opt_value = gprefs.get(self.name + '_' + opt[0], opt[1]) opt_value = gprefs.get(self.name + '_' + opt[0], opt[1])
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']: if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
getattr(self, opt[0]).setCurrentIndex(opt_value) getattr(self, opt[0]).setCurrentIndex(opt_value)
elif opt[0] == 'impcit' : elif opt[0] in ['impcit', 'addfiles'] :
getattr(self, opt[0]).setChecked(opt_value) getattr(self, opt[0]).setChecked(opt_value)
else: else:
getattr(self, opt[0]).setText(opt_value) getattr(self, opt[0]).setText(opt_value)
@ -76,7 +78,7 @@ class PluginWidget(QWidget, Ui_Form):
for opt in self.OPTION_FIELDS: for opt in self.OPTION_FIELDS:
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']: if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
opt_value = getattr(self,opt[0]).currentIndex() opt_value = getattr(self,opt[0]).currentIndex()
elif opt[0] == 'impcit' : elif opt[0] in ['impcit', 'addfiles'] :
opt_value = getattr(self, opt[0]).isChecked() opt_value = getattr(self, opt[0]).isChecked()
else : else :
opt_value = unicode(getattr(self, opt[0]).text()) opt_value = unicode(getattr(self, opt[0]).text())

View File

@ -47,7 +47,7 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="1" column="1" rowspan="12"> <item row="1" column="1" rowspan="11">
<widget class="QListWidget" name="db_fields"> <widget class="QListWidget" name="db_fields">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding"> <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
@ -141,6 +141,13 @@
</widget> </widget>
</item> </item>
<item row="8" column="0"> <item row="8" column="0">
<widget class="QCheckBox" name="addfiles">
<property name="text">
<string>Add files path with formats?</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>Expression to form the BibTeX citation tag:</string> <string>Expression to form the BibTeX citation tag:</string>

View File

@ -151,12 +151,27 @@ class DateEdit(QDateEdit):
def set_to_today(self): def set_to_today(self):
self.setDate(now()) self.setDate(now())
def set_to_clear(self):
self.setDate(UNDEFINED_QDATE)
class DateTime(Base): class DateTime(Base):
def setup_ui(self, parent): def setup_ui(self, parent):
cm = self.col_metadata cm = self.col_metadata
self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(parent), self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(parent)]
QLabel(''), QPushButton(_('Set \'%s\' to today')%cm['name'], parent)] self.widgets.append(QLabel(''))
w = QWidget(parent)
self.widgets.append(w)
l = QHBoxLayout()
l.setContentsMargins(0, 0, 0, 0)
w.setLayout(l)
l.addStretch(1)
self.today_button = QPushButton(_('Set \'%s\' to today')%cm['name'], parent)
l.addWidget(self.today_button)
self.clear_button = QPushButton(_('Clear \'%s\'')%cm['name'], parent)
l.addWidget(self.clear_button)
l.addStretch(2)
w = self.widgets[1] w = self.widgets[1]
format = cm['display'].get('date_format','') format = cm['display'].get('date_format','')
if not format: if not format:
@ -165,7 +180,8 @@ class DateTime(Base):
w.setCalendarPopup(True) w.setCalendarPopup(True)
w.setMinimumDate(UNDEFINED_QDATE) w.setMinimumDate(UNDEFINED_QDATE)
w.setSpecialValueText(_('Undefined')) w.setSpecialValueText(_('Undefined'))
self.widgets[3].clicked.connect(w.set_to_today) self.today_button.clicked.connect(w.set_to_today)
self.clear_button.clicked.connect(w.set_to_clear)
def setter(self, val): def setter(self, val):
if val is None: if val is None:
@ -470,11 +486,48 @@ class BulkBase(Base):
self.setter(val) self.setter(val)
def commit(self, book_ids, notify=False): def commit(self, book_ids, notify=False):
if not self.a_c_checkbox.isChecked():
return
val = self.gui_val val = self.gui_val
val = self.normalize_ui_val(val) val = self.normalize_ui_val(val)
if val != self.initial_val: if val != self.initial_val:
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify) self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
def make_widgets(self, parent, main_widget_class, extra_label_text=''):
w = QWidget(parent)
self.widgets = [QLabel('&'+self.col_metadata['name']+':', w), w]
l = QHBoxLayout()
l.setContentsMargins(0, 0, 0, 0)
w.setLayout(l)
self.main_widget = main_widget_class(w)
l.addWidget(self.main_widget)
l.setStretchFactor(self.main_widget, 10)
self.a_c_checkbox = QCheckBox( _('Apply changes'), w)
l.addWidget(self.a_c_checkbox)
self.ignore_change_signals = True
# connect to the various changed signals so we can auto-update the
# apply changes checkbox
if hasattr(self.main_widget, 'editTextChanged'):
# editable combobox widgets
self.main_widget.editTextChanged.connect(self.a_c_checkbox_changed)
if hasattr(self.main_widget, 'textChanged'):
# lineEdit widgets
self.main_widget.textChanged.connect(self.a_c_checkbox_changed)
if hasattr(self.main_widget, 'currentIndexChanged'):
# combobox widgets
self.main_widget.currentIndexChanged[int].connect(self.a_c_checkbox_changed)
if hasattr(self.main_widget, 'valueChanged'):
# spinbox widgets
self.main_widget.valueChanged.connect(self.a_c_checkbox_changed)
if hasattr(self.main_widget, 'dateChanged'):
# dateEdit widgets
self.main_widget.dateChanged.connect(self.a_c_checkbox_changed)
def a_c_checkbox_changed(self):
if not self.ignore_change_signals:
self.a_c_checkbox.setChecked(True)
class BulkBool(BulkBase, Bool): class BulkBool(BulkBase, Bool):
def get_initial_value(self, book_ids): def get_initial_value(self, book_ids):
@ -484,58 +537,144 @@ class BulkBool(BulkBase, Bool):
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None: if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
val = False val = False
if value is not None and value != val: if value is not None and value != val:
return 'nochange' return None
value = val value = val
return value return value
def setup_ui(self, parent): def setup_ui(self, parent):
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), self.make_widgets(parent, QComboBox)
QComboBox(parent)] items = [_('Yes'), _('No'), _('Undefined')]
w = self.widgets[1] icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
items = [_('Yes'), _('No'), _('Undefined'), _('Do not change')] self.main_widget.blockSignals(True)
icons = [I('ok.png'), I('list_remove.png'), I('blank.png'), I('blank.png')]
for icon, text in zip(icons, items): for icon, text in zip(icons, items):
w.addItem(QIcon(icon), text) self.main_widget.addItem(QIcon(icon), text)
self.main_widget.blockSignals(False)
def getter(self): def getter(self):
val = self.widgets[1].currentIndex() val = self.main_widget.currentIndex()
return {3: 'nochange', 2: None, 1: False, 0: True}[val] return {2: None, 1: False, 0: True}[val]
def setter(self, val): def setter(self, val):
val = {'nochange': 3, None: 2, False: 1, True: 0}[val] val = {None: 2, False: 1, True: 0}[val]
self.widgets[1].setCurrentIndex(val) self.main_widget.setCurrentIndex(val)
self.ignore_change_signals = False
def commit(self, book_ids, notify=False): def commit(self, book_ids, notify=False):
if not self.a_c_checkbox.isChecked():
return
val = self.gui_val val = self.gui_val
val = self.normalize_ui_val(val) val = self.normalize_ui_val(val)
if val != self.initial_val and val != 'nochange':
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None: if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None:
val = False val = False
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify) self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
class BulkInt(BulkBase, Int): class BulkInt(BulkBase):
pass
class BulkFloat(BulkBase, Float): def setup_ui(self, parent):
pass self.make_widgets(parent, QSpinBox)
self.main_widget.setRange(-100, sys.maxint)
self.main_widget.setSpecialValueText(_('Undefined'))
self.main_widget.setSingleStep(1)
class BulkRating(BulkBase, Rating): def setter(self, val):
pass if val is None:
val = self.main_widget.minimum()
else:
val = int(val)
self.main_widget.setValue(val)
self.ignore_change_signals = False
class BulkDateTime(BulkBase, DateTime): def getter(self):
pass val = self.main_widget.value()
if val == self.main_widget.minimum():
val = None
return val
class BulkFloat(BulkInt):
def setup_ui(self, parent):
self.make_widgets(parent, QDoubleSpinBox)
self.main_widget.setRange(-100., float(sys.maxint))
self.main_widget.setDecimals(2)
self.main_widget.setSpecialValueText(_('Undefined'))
self.main_widget.setSingleStep(1)
class BulkRating(BulkBase):
def setup_ui(self, parent):
self.make_widgets(parent, QSpinBox)
self.main_widget.setRange(0, 5)
self.main_widget.setSuffix(' '+_('star(s)'))
self.main_widget.setSpecialValueText(_('Unrated'))
self.main_widget.setSingleStep(1)
def setter(self, val):
if val is None:
val = 0
self.main_widget.setValue(int(round(val/2.)))
self.ignore_change_signals = False
def getter(self):
val = self.main_widget.value()
if val == 0:
val = None
else:
val *= 2
return val
class BulkDateTime(BulkBase):
def setup_ui(self, parent):
cm = self.col_metadata
self.make_widgets(parent, DateEdit)
self.widgets.append(QLabel(''))
w = QWidget(parent)
self.widgets.append(w)
l = QHBoxLayout()
l.setContentsMargins(0, 0, 0, 0)
w.setLayout(l)
l.addStretch(1)
self.today_button = QPushButton(_('Set \'%s\' to today')%cm['name'], parent)
l.addWidget(self.today_button)
self.clear_button = QPushButton(_('Clear \'%s\'')%cm['name'], parent)
l.addWidget(self.clear_button)
l.addStretch(2)
w = self.main_widget
format = cm['display'].get('date_format','')
if not format:
format = 'dd MMM yyyy'
w.setDisplayFormat(format)
w.setCalendarPopup(True)
w.setMinimumDate(UNDEFINED_QDATE)
w.setSpecialValueText(_('Undefined'))
self.today_button.clicked.connect(w.set_to_today)
self.clear_button.clicked.connect(w.set_to_clear)
def setter(self, val):
if val is None:
val = self.main_widget.minimumDate()
else:
val = QDate(val.year, val.month, val.day)
self.main_widget.setDate(val)
self.ignore_change_signals = False
def getter(self):
val = self.main_widget.date()
if val == UNDEFINED_QDATE:
val = None
else:
val = qt_to_dt(val)
return val
class BulkSeries(BulkBase): class BulkSeries(BulkBase):
def setup_ui(self, parent): def setup_ui(self, parent):
self.make_widgets(parent, EnComboBox)
values = self.all_values = list(self.db.all_custom(num=self.col_id)) values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key) values.sort(key=sort_key)
w = EnComboBox(parent) self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) self.main_widget.setMinimumContentsLength(25)
w.setMinimumContentsLength(25)
self.name_widget = w
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
self.widgets.append(QLabel('', parent)) self.widgets.append(QLabel('', parent))
w = QWidget(parent) w = QWidget(parent)
layout = QHBoxLayout(w) layout = QHBoxLayout(w)
@ -555,15 +694,24 @@ class BulkSeries(BulkBase):
layout.addWidget(self.series_start_number) layout.addWidget(self.series_start_number)
layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)) layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
self.widgets.append(w) self.widgets.append(w)
self.idx_widget.stateChanged.connect(self.check_changed_checkbox)
self.force_number.stateChanged.connect(self.check_changed_checkbox)
self.series_start_number.valueChanged.connect(self.check_changed_checkbox)
self.remove_series.stateChanged.connect(self.check_changed_checkbox)
self.ignore_change_signals = False
def check_changed_checkbox(self):
self.a_c_checkbox.setChecked(True)
def initialize(self, book_id): def initialize(self, book_id):
self.idx_widget.setChecked(False) self.idx_widget.setChecked(False)
for c in self.all_values: for c in self.all_values:
self.name_widget.addItem(c) self.main_widget.addItem(c)
self.name_widget.setEditText('') self.main_widget.setEditText('')
self.a_c_checkbox.setChecked(False)
def getter(self): def getter(self):
n = unicode(self.name_widget.currentText()).strip() n = unicode(self.main_widget.currentText()).strip()
i = self.idx_widget.checkState() i = self.idx_widget.checkState()
f = self.force_number.checkState() f = self.force_number.checkState()
s = self.series_start_number.value() s = self.series_start_number.value()
@ -571,6 +719,8 @@ class BulkSeries(BulkBase):
return n, i, f, s, r return n, i, f, s, r
def commit(self, book_ids, notify=False): def commit(self, book_ids, notify=False):
if not self.a_c_checkbox.isChecked():
return
val, update_indices, force_start, at_value, clear = self.gui_val val, update_indices, force_start, at_value, clear = self.gui_val
val = None if clear else self.normalize_ui_val(val) val = None if clear else self.normalize_ui_val(val)
if clear or val != '': if clear or val != '':
@ -598,9 +748,9 @@ class BulkEnumeration(BulkBase, Enumeration):
def get_initial_value(self, book_ids): def get_initial_value(self, book_ids):
value = None value = None
ret_value = None first = True
dialog_shown = False dialog_shown = False
for i,book_id in enumerate(book_ids): for book_id in book_ids:
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
if val and val not in self.col_metadata['display']['enum_values']: if val and val not in self.col_metadata['display']['enum_values']:
if not dialog_shown: if not dialog_shown:
@ -610,44 +760,32 @@ class BulkEnumeration(BulkBase, Enumeration):
self.col_metadata['name']), self.col_metadata['name']),
show=True, show_copy_button=False) show=True, show_copy_button=False)
dialog_shown = True dialog_shown = True
ret_value = ' nochange ' if first:
elif (value is not None and value != val) or (val and i != 0):
ret_value = ' nochange '
value = val value = val
if ret_value is None: first = False
elif value != val:
value = None
if not value:
self.ignore_change_signals = False
return value return value
return ret_value
def setup_ui(self, parent): def setup_ui(self, parent):
self.parent = parent self.make_widgets(parent, QComboBox)
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
QComboBox(parent)]
w = self.widgets[1]
vals = self.col_metadata['display']['enum_values'] vals = self.col_metadata['display']['enum_values']
w.addItem('Do Not Change') self.main_widget.blockSignals(True)
w.addItem('') self.main_widget.addItem('')
for v in vals: self.main_widget.addItems(vals)
w.addItem(v) self.main_widget.blockSignals(False)
def getter(self): def getter(self):
if self.widgets[1].currentIndex() == 0: return unicode(self.main_widget.currentText())
return ' nochange '
return unicode(self.widgets[1].currentText())
def setter(self, val): def setter(self, val):
if val == ' nochange ':
self.widgets[1].setCurrentIndex(0)
else:
if val is None: if val is None:
self.widgets[1].setCurrentIndex(1) self.main_widget.setCurrentIndex(0)
else: else:
self.widgets[1].setCurrentIndex(self.widgets[1].findText(val)) self.main_widget.setCurrentIndex(self.main_widget.findText(val))
self.ignore_change_signals = False
def commit(self, book_ids, notify=False):
val = self.gui_val
val = self.normalize_ui_val(val)
if val != self.initial_val and val != ' nochange ':
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
class RemoveTags(QWidget): class RemoveTags(QWidget):
@ -658,11 +796,10 @@ class RemoveTags(QWidget):
layout.setContentsMargins(0, 0, 0, 0) layout.setContentsMargins(0, 0, 0, 0)
self.tags_box = CompleteLineEdit(parent, values) self.tags_box = CompleteLineEdit(parent, values)
layout.addWidget(self.tags_box, stretch = 1) layout.addWidget(self.tags_box, stretch=3)
# self.tags_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.checkbox = QCheckBox(_('Remove all tags'), parent) self.checkbox = QCheckBox(_('Remove all tags'), parent)
layout.addWidget(self.checkbox) layout.addWidget(self.checkbox)
layout.addStretch(1)
self.setLayout(layout) self.setLayout(layout)
self.connect(self.checkbox, SIGNAL('stateChanged(int)'), self.box_touched) self.connect(self.checkbox, SIGNAL('stateChanged(int)'), self.box_touched)
@ -679,39 +816,45 @@ class BulkText(BulkBase):
values = self.all_values = list(self.db.all_custom(num=self.col_id)) values = self.all_values = list(self.db.all_custom(num=self.col_id))
values.sort(key=sort_key) values.sort(key=sort_key)
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
w = CompleteLineEdit(parent, values) self.make_widgets(parent, CompleteLineEdit,
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) extra_label_text=_('tags to add'))
self.widgets = [QLabel('&'+self.col_metadata['name']+': ' + self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
_('tags to add'), parent), w] self.adding_widget = self.main_widget
self.adding_widget = w
w = RemoveTags(parent, values) w = RemoveTags(parent, values)
self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' + self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' +
_('tags to remove'), parent)) _('tags to remove'), parent))
self.widgets.append(w) self.widgets.append(w)
self.removing_widget = w self.removing_widget = w
w.tags_box.textChanged.connect(self.a_c_checkbox_changed)
w.checkbox.stateChanged.connect(self.a_c_checkbox_changed)
else: else:
w = EnComboBox(parent) self.make_widgets(parent, EnComboBox)
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon) self.main_widget.setSizeAdjustPolicy(
w.setMinimumContentsLength(25) self.main_widget.AdjustToMinimumContentsLengthWithIcon)
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w] self.main_widget.setMinimumContentsLength(25)
self.ignore_change_signals = False
def initialize(self, book_ids): def initialize(self, book_ids):
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
self.widgets[1].update_items_cache(self.all_values) self.main_widget.update_items_cache(self.all_values)
else: else:
val = self.get_initial_value(book_ids) val = self.get_initial_value(book_ids)
self.initial_val = val = self.normalize_db_val(val) self.initial_val = val = self.normalize_db_val(val)
idx = None idx = None
self.main_widget.blockSignals(True)
for i, c in enumerate(self.all_values): for i, c in enumerate(self.all_values):
if c == val: if c == val:
idx = i idx = i
self.widgets[1].addItem(c) self.main_widget.addItem(c)
self.widgets[1].setEditText('') self.main_widget.setEditText('')
if idx is not None: if idx is not None:
self.widgets[1].setCurrentIndex(idx) self.main_widget.setCurrentIndex(idx)
self.main_widget.blockSignals(False)
def commit(self, book_ids, notify=False): def commit(self, book_ids, notify=False):
if not self.a_c_checkbox.isChecked():
return
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
remove_all, adding, rtext = self.gui_val remove_all, adding, rtext = self.gui_val
remove = set() remove = set()
@ -740,7 +883,7 @@ class BulkText(BulkBase):
unicode(self.adding_widget.text()), \ unicode(self.adding_widget.text()), \
unicode(self.removing_widget.tags_box.text()) unicode(self.removing_widget.tags_box.text())
val = unicode(self.widgets[1].currentText()).strip() val = unicode(self.main_widget.currentText()).strip()
if not val: if not val:
val = None val = None
return val return val

View File

@ -8,15 +8,12 @@ __docformat__ = 'restructuredtext en'
import os, sys import os, sys
from PyQt4 import QtGui
from PyQt4.Qt import QDialog, SIGNAL
from calibre.customize.ui import config from calibre.customize.ui import config
from calibre.gui2.dialogs.catalog_ui import Ui_Dialog from calibre.gui2.dialogs.catalog_ui import Ui_Dialog
from calibre.gui2 import dynamic from calibre.gui2 import dynamic, ResizableDialog
from calibre.customize.ui import catalog_plugins from calibre.customize.ui import catalog_plugins
class Catalog(QDialog, Ui_Dialog): class Catalog(ResizableDialog, Ui_Dialog):
''' Catalog Dialog builder''' ''' Catalog Dialog builder'''
def __init__(self, parent, dbspec, ids, db): def __init__(self, parent, dbspec, ids, db):
@ -24,10 +21,8 @@ class Catalog(QDialog, Ui_Dialog):
from calibre import prints as info from calibre import prints as info
from PyQt4.uic import compileUi from PyQt4.uic import compileUi
QDialog.__init__(self, parent) ResizableDialog.__init__(self, parent)
# Run the dialog setup generated from catalog.ui
self.setupUi(self)
self.dbspec, self.ids = dbspec, ids self.dbspec, self.ids = dbspec, ids
# Display the number of books we've been passed # Display the number of books we've been passed
@ -120,11 +115,13 @@ class Catalog(QDialog, Ui_Dialog):
self.sync.setChecked(dynamic.get('catalog_sync_to_device', True)) self.sync.setChecked(dynamic.get('catalog_sync_to_device', True))
self.format.currentIndexChanged.connect(self.show_plugin_tab) self.format.currentIndexChanged.connect(self.show_plugin_tab)
self.connect(self.buttonBox.button(QtGui.QDialogButtonBox.Apply), self.buttonBox.button(self.buttonBox.Apply).clicked.connect(self.apply)
SIGNAL("clicked()"),
self.apply)
self.show_plugin_tab(None) self.show_plugin_tab(None)
geom = dynamic.get('catalog_window_geom', None)
if geom is not None:
self.restoreGeometry(bytes(geom))
def show_plugin_tab(self, idx): def show_plugin_tab(self, idx):
cf = unicode(self.format.currentText()).lower() cf = unicode(self.format.currentText()).lower()
while self.tabs.count() > 1: while self.tabs.count() > 1:
@ -157,8 +154,9 @@ class Catalog(QDialog, Ui_Dialog):
dynamic.set('catalog_last_used_title', self.catalog_title) dynamic.set('catalog_last_used_title', self.catalog_title)
self.catalog_sync = bool(self.sync.isChecked()) self.catalog_sync = bool(self.sync.isChecked())
dynamic.set('catalog_sync_to_device', self.catalog_sync) dynamic.set('catalog_sync_to_device', self.catalog_sync)
dynamic.set('catalog_window_geom', bytearray(self.saveGeometry()))
def apply(self): def apply(self, *args):
# Store current values without building catalog # Store current values without building catalog
self.save_catalog_settings() self.save_catalog_settings()
if self.tabs.count() > 1: if self.tabs.count() > 1:
@ -166,4 +164,9 @@ class Catalog(QDialog, Ui_Dialog):
def accept(self): def accept(self):
self.save_catalog_settings() self.save_catalog_settings()
return QDialog.accept(self) return ResizableDialog.accept(self)
def reject(self):
dynamic.set('catalog_window_geom', bytearray(self.saveGeometry()))
ResizableDialog.reject(self)

View File

@ -14,7 +14,7 @@
<string>Generate catalog</string> <string>Generate catalog</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset> <iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset> <normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
@ -31,7 +31,38 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>666</width>
<height>599</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabs"> <widget class="QTabWidget" name="tabs">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
@ -82,13 +113,6 @@
<item row="1" column="2"> <item row="1" column="2">
<widget class="QLineEdit" name="title"/> <widget class="QLineEdit" name="title"/>
</item> </item>
<item row="3" column="0">
<widget class="QCheckBox" name="sync">
<property name="text">
<string>&amp;Send catalog to device automatically</string>
</property>
</widget>
</item>
<item row="2" column="1"> <item row="2" column="1">
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
@ -96,30 +120,31 @@
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>20</width> <width>0</width>
<height>299</height> <height>0</height>
</size> </size>
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="3" column="0">
<widget class="QCheckBox" name="sync">
<property name="text">
<string>&amp;Send catalog to device automatically</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
</item> </item>
<item row="2" column="1"> </layout>
<widget class="QDialogButtonBox" name="buttonBox"> </widget>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
<resources> <resources>
<include location="../../../work/calibre/resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@ -3,16 +3,132 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__license__ = 'GPL v3' __license__ = 'GPL v3'
import os import os, shutil
from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \ from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \
QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \ QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \
QLineEdit, Qt QLineEdit, Qt, QProgressBar, QSize, QTimer
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.library.check_library import CheckLibrary, CHECKS from calibre.library.check_library import CheckLibrary, CHECKS
from calibre.library.database2 import delete_file, delete_tree from calibre.library.database2 import delete_file, delete_tree
from calibre import prints from calibre import prints, as_unicode
from calibre.ptempfile import PersistentTemporaryFile
from calibre.library.sqlite import DBThread, OperationalError
class DBCheck(QDialog):
def __init__(self, parent, db):
QDialog.__init__(self, parent)
self.l = QVBoxLayout()
self.setLayout(self.l)
self.l1 = QLabel(_('Checking database integrity')+'...')
self.setWindowTitle(_('Checking database integrity'))
self.l.addWidget(self.l1)
self.pb = QProgressBar(self)
self.l.addWidget(self.pb)
self.pb.setMaximum(0)
self.pb.setMinimum(0)
self.msg = QLabel('')
self.l.addWidget(self.msg)
self.msg.setWordWrap(True)
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
self.l.addWidget(self.bb)
self.bb.rejected.connect(self.reject)
self.resize(self.sizeHint() + QSize(100, 50))
self.error = None
self.db = db
self.closed_orig_conn = False
def start(self):
self.user_version = self.db.user_version
self.rejected = False
self.db.clean()
self.db.conn.close()
self.closed_orig_conn = True
t = DBThread(self.db.dbpath, False)
t.connect()
self.conn = t.conn
self.dump = self.conn.iterdump()
self.statements = []
self.count = 0
self.msg.setText(_('Dumping database to SQL'))
# Give the backup thread time to stop
QTimer.singleShot(2000, self.do_one_dump)
self.exec_()
def do_one_dump(self):
if self.rejected:
return
try:
try:
self.statements.append(self.dump.next())
self.count += 1
except StopIteration:
self.start_load()
return
QTimer.singleShot(0, self.do_one_dump)
except Exception, e:
import traceback
self.error = (as_unicode(e), traceback.format_exc())
self.reject()
def start_load(self):
self.conn.close()
self.pb.setMaximum(self.count)
self.pb.setValue(0)
self.msg.setText(_('Loading database from SQL'))
self.db.conn.close()
self.ndbpath = PersistentTemporaryFile('.db')
self.ndbpath.close()
self.ndbpath = self.ndbpath.name
t = DBThread(self.ndbpath, False)
t.connect()
self.conn = t.conn
self.conn.execute('create temporary table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)')
self.conn.commit()
QTimer.singleShot(0, self.do_one_load)
def do_one_load(self):
if self.rejected:
return
if self.count > 0:
try:
try:
self.conn.execute(self.statements.pop(0))
except OperationalError:
if self.count > 1:
# The last statement in the dump could be an extra
# commit, so ignore it.
raise
self.pb.setValue(self.pb.value() + 1)
self.count -= 1
QTimer.singleShot(0, self.do_one_load)
except Exception, e:
import traceback
self.error = (as_unicode(e), traceback.format_exc())
self.reject()
else:
self.replace_db()
def replace_db(self):
self.conn.commit()
self.conn.execute('pragma user_version=%d'%int(self.user_version))
self.conn.commit()
self.conn.close()
shutil.copyfile(self.ndbpath, self.db.dbpath)
self.db = None
self.accept()
def break_cycles(self):
self.statements = self.unpickler = self.db = self.conn = None
def reject(self):
self.rejected = True
QDialog.reject(self)
class Item(QTreeWidgetItem): class Item(QTreeWidgetItem):
pass pass

View File

@ -39,6 +39,12 @@ class MessageBox(QDialog, Ui_Dialog):
self.det_msg.setPlainText(det_msg) self.det_msg.setPlainText(det_msg)
self.det_msg.setVisible(False) self.det_msg.setVisible(False)
if show_copy_button:
self.ctc_button = self.bb.addButton(_('&Copy to clipboard'),
self.bb.ActionRole)
self.ctc_button.clicked.connect(self.copy_to_clipboard)
if det_msg: if det_msg:
self.show_det_msg = _('Show &details') self.show_det_msg = _('Show &details')
self.hide_det_msg = _('Hide &details') self.hide_det_msg = _('Hide &details')
@ -47,11 +53,6 @@ class MessageBox(QDialog, Ui_Dialog):
self.det_msg_toggle.setToolTip( self.det_msg_toggle.setToolTip(
_('Show detailed information about this error')) _('Show detailed information about this error'))
if show_copy_button:
self.ctc_button = self.bb.addButton(_('&Copy to clipboard'),
self.bb.ActionRole)
self.ctc_button.clicked.connect(self.copy_to_clipboard)
self.copy_action = QAction(self) self.copy_action = QAction(self)
self.addAction(self.copy_action) self.addAction(self.copy_action)

View File

@ -208,6 +208,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
from calibre.gui2 import config from calibre.gui2 import config
title = unicode(self.title.text()).strip() title = unicode(self.title.text()).strip()
author = unicode(self.authors.text()).strip() author = unicode(self.authors.text()).strip()
if author.endswith('&'):
author = author[:-1]
if not title or not author: if not title or not author:
return error_dialog(self, _('Specify title and author'), return error_dialog(self, _('Specify title and author'),
_('You must specify a title and author before generating ' _('You must specify a title and author before generating '

View File

@ -259,14 +259,14 @@ class Scheduler(QObject):
if self.oldest > 0: if self.oldest > 0:
delta = timedelta(days=self.oldest) delta = timedelta(days=self.oldest)
try: try:
ids = self.recipe_model.db.tags_older_than(_('News'), delta) ids = list(self.recipe_model.db.tags_older_than(_('News'),
delta))
except: except:
# Should never happen # Should never happen
ids = [] ids = []
import traceback import traceback
traceback.print_exc() traceback.print_exc()
if ids: if ids:
ids = list(ids)
if ids: if ids:
self.delete_old_news.emit(ids) self.delete_old_news.emit(ids)
QTimer.singleShot(60 * 60 * 1000, self.oldest_check) QTimer.singleShot(60 * 60 * 1000, self.oldest_check)

View File

@ -87,7 +87,7 @@ class MainWindow(QMainWindow):
fe = sio.getvalue() fe = sio.getvalue()
prints(fe, file=sys.stderr) prints(fe, file=sys.stderr)
msg = '<b>%s</b>:'%type.__name__ + unicode(str(value), 'utf8', 'replace') msg = '<b>%s</b>:'%type.__name__ + unicode(str(value), 'utf8', 'replace')
error_dialog(self, _('ERROR: Unhandled exception'), msg, det_msg=fe, error_dialog(self, _('Unhandled exception'), msg, det_msg=fe,
show=True) show=True)
except BaseException: except BaseException:
pass pass

View File

@ -64,6 +64,8 @@ class TagDelegate(QItemDelegate): # {{{
# }}} # }}}
TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_minus': 2}
class TagsView(QTreeView): # {{{ class TagsView(QTreeView): # {{{
refresh_required = pyqtSignal() refresh_required = pyqtSignal()
@ -177,9 +179,16 @@ class TagsView(QTreeView): # {{{
return joiner.join(tokens) return joiner.join(tokens)
def toggle(self, index): def toggle(self, index):
self._toggle(index, None)
def _toggle(self, index, set_to):
'''
set_to: if None, advance the state. Otherwise must be one of the values
in TAG_SEARCH_STATES
'''
modifiers = int(QApplication.keyboardModifiers()) modifiers = int(QApplication.keyboardModifiers())
exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT) exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
if self._model.toggle(index, exclusive): if self._model.toggle(index, exclusive, set_to=set_to):
self.tags_marked.emit(self.search_string) self.tags_marked.emit(self.search_string)
def conditional_clear(self, search_string): def conditional_clear(self, search_string):
@ -187,7 +196,7 @@ class TagsView(QTreeView): # {{{
self.clear() self.clear()
def context_menu_handler(self, action=None, category=None, def context_menu_handler(self, action=None, category=None,
key=None, index=None, negate=None): key=None, index=None, search_state=None):
if not action: if not action:
return return
try: try:
@ -201,11 +210,10 @@ class TagsView(QTreeView): # {{{
self.user_category_edit.emit(category) self.user_category_edit.emit(category)
return return
if action == 'search': if action == 'search':
self.tags_marked.emit(('not ' if negate else '') + self._toggle(index, set_to=search_state)
category + ':"=' + key + '"')
return return
if action == 'search_category': if action == 'search_category':
self.tags_marked.emit(category + ':' + str(not negate)) self.tags_marked.emit(key + ':' + search_state)
return return
if action == 'manage_searches': if action == 'manage_searches':
self.saved_search_edit.emit(category) self.saved_search_edit.emit(category)
@ -270,20 +278,16 @@ class TagsView(QTreeView): # {{{
partial(self.context_menu_handler, partial(self.context_menu_handler,
action='edit_author_sort', index=tag_id)) action='edit_author_sort', index=tag_id))
# Add the search for value items # Add the search for value items
n = tag_name
c = category
if self.db.field_metadata[key]['datatype'] == 'rating':
n = str(len(tag_name))
elif self.db.field_metadata[key]['kind'] in ['user', 'search']:
c = tag_item.tag.category
self.context_menu.addAction(self.search_icon, self.context_menu.addAction(self.search_icon,
_('Search for %s')%tag_name, _('Search for %s')%tag_name,
partial(self.context_menu_handler, action='search', partial(self.context_menu_handler, action='search',
category=c, key=n, negate=False)) search_state=TAG_SEARCH_STATES['mark_plus'],
index=index))
self.context_menu.addAction(self.search_icon, self.context_menu.addAction(self.search_icon,
_('Search for everything but %s')%tag_name, _('Search for everything but %s')%tag_name,
partial(self.context_menu_handler, action='search', partial(self.context_menu_handler, action='search',
category=c, key=n, negate=True)) search_state=TAG_SEARCH_STATES['mark_minus'],
index=index))
self.context_menu.addSeparator() self.context_menu.addSeparator()
# Hide/Show/Restore categories # Hide/Show/Restore categories
self.context_menu.addAction(_('Hide category %s') % category, self.context_menu.addAction(_('Hide category %s') % category,
@ -299,11 +303,11 @@ class TagsView(QTreeView): # {{{
self.context_menu.addAction(self.search_icon, self.context_menu.addAction(self.search_icon,
_('Search for books in category %s')%category, _('Search for books in category %s')%category,
partial(self.context_menu_handler, action='search_category', partial(self.context_menu_handler, action='search_category',
category=key, negate=False)) key=key, search_state='true'))
self.context_menu.addAction(self.search_icon, self.context_menu.addAction(self.search_icon,
_('Search for books not in category %s')%category, _('Search for books not in category %s')%category,
partial(self.context_menu_handler, action='search_category', partial(self.context_menu_handler, action='search_category',
category=key, negate=True)) key=key, search_state='false'))
# Offer specific editors for tags/series/publishers/saved searches # Offer specific editors for tags/series/publishers/saved searches
self.context_menu.addSeparator() self.context_menu.addSeparator()
if key in ['tags', 'publisher', 'series'] or \ if key in ['tags', 'publisher', 'series'] or \
@ -528,9 +532,15 @@ class TagTreeItem(object): # {{{
return QVariant(self.tooltip) return QVariant(self.tooltip)
return NONE return NONE
def toggle(self): def toggle(self, set_to=None):
'''
set_to: None => advance the state, otherwise a value from TAG_SEARCH_STATES
'''
if self.type == self.TAG: if self.type == self.TAG:
if set_to is None:
self.tag.state = (self.tag.state + 1)%3 self.tag.state = (self.tag.state + 1)%3
else:
self.tag.state = set_to
def child_tags(self): def child_tags(self):
res = [] res = []
@ -1014,11 +1024,15 @@ class TagsModel(QAbstractItemModel): # {{{
def clear_state(self): def clear_state(self):
self.reset_all_states() self.reset_all_states()
def toggle(self, index, exclusive): def toggle(self, index, exclusive, set_to=None):
'''
exclusive: clear all states before applying this one
set_to: None => advance the state, otherwise a value from TAG_SEARCH_STATES
'''
if not index.isValid(): return False if not index.isValid(): return False
item = index.internalPointer() item = index.internalPointer()
if item.type == TagTreeItem.TAG: if item.type == TagTreeItem.TAG:
item.toggle() item.toggle(set_to=set_to)
if exclusive: if exclusive:
self.reset_all_states(except_=item.tag) self.reset_all_states(except_=item.tag)
self.dataChanged.emit(index, index) self.dataChanged.emit(index, index)
@ -1040,8 +1054,9 @@ class TagsModel(QAbstractItemModel): # {{{
category_item = self.root_item.children[row_index] category_item = self.root_item.children[row_index]
for tag_item in category_item.child_tags(): for tag_item in category_item.child_tags():
tag = tag_item.tag tag = tag_item.tag
if tag.state > 0: if tag.state != TAG_SEARCH_STATES['clear']:
prefix = ' not ' if tag.state == 2 else '' prefix = ' not ' if tag.state == TAG_SEARCH_STATES['mark_minus'] \
else ''
category = key if key != 'news' else 'tag' category = key if key != 'news' else 'tag'
if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating
ans.append('%s%s:%s'%(prefix, category, len(tag.name))) ans.append('%s%s:%s'%(prefix, category, len(tag.name)))

View File

@ -408,7 +408,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
def booklists(self): def booklists(self):
return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db
def library_moved(self, newloc, copy_structure=False): def library_moved(self, newloc, copy_structure=False, call_close=True):
if newloc is None: return if newloc is None: return
default_prefs = None default_prefs = None
try: try:
@ -441,6 +441,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.apply_named_search_restriction(db.prefs['gui_restriction']) self.apply_named_search_restriction(db.prefs['gui_restriction'])
if olddb is not None: if olddb is not None:
try: try:
if call_close:
olddb.conn.close() olddb.conn.close()
except: except:
import traceback import traceback

View File

@ -503,7 +503,7 @@ class CompleteLineEdit(EnLineEdit):
cursor_pos = self.cursorPosition() cursor_pos = self.cursorPosition()
before_text = unicode(self.text())[:cursor_pos] before_text = unicode(self.text())[:cursor_pos]
after_text = unicode(self.text())[cursor_pos:] after_text = unicode(self.text())[cursor_pos:]
prefix_len = len(before_text.split(self.separator)[-1].strip()) prefix_len = len(before_text.split(self.separator)[-1].lstrip())
if self.space_before_sep: if self.space_before_sep:
complete_text_pat = '%s%s %s %s' complete_text_pat = '%s%s %s %s'
len_extra = 3 len_extra = 3

View File

@ -33,10 +33,10 @@ from calibre.gui2.dialogs.progress import ProgressDialog
class Device(object): class Device(object):
output_profile = 'default' output_profile = 'generic_eink'
output_format = 'EPUB' output_format = 'EPUB'
name = 'Default' name = 'Generic e-ink device'
manufacturer = 'Default' manufacturer = 'Generic'
id = 'default' id = 'default'
supports_color = False supports_color = False
@ -63,6 +63,18 @@ class Device(object):
recs['dont_grayscale'] = True recs['dont_grayscale'] = True
save_defaults('comic_input', recs) save_defaults('comic_input', recs)
class Smartphone(Device):
id = 'smartphone'
name = 'Smartphone'
supports_color = True
class Tablet(Device):
id = 'tablet'
name = 'iPad like tablet'
output_profile = 'tablet'
supports_color = True
class Kindle(Device): class Kindle(Device):
@ -206,12 +218,21 @@ class iPhone(Device):
class Android(Device): class Android(Device):
name = 'Adroid phone + WordPlayer/Aldiko' name = 'Android phone'
output_format = 'EPUB' output_format = 'EPUB'
manufacturer = 'Android' manufacturer = 'Android'
id = 'android' id = 'android'
supports_color = True supports_color = True
class AndroidTablet(Device):
name = 'Android tablet'
output_format = 'EPUB'
manufacturer = 'Android'
id = 'android_tablet'
supports_color = True
output_profile = 'tablet'
class HanlinV3(Device): class HanlinV3(Device):
name = 'Hanlin V3' name = 'Hanlin V3'
@ -268,9 +289,9 @@ def get_manufacturers():
mans = set([]) mans = set([])
for x in get_devices(): for x in get_devices():
mans.add(x.manufacturer) mans.add(x.manufacturer)
if 'Default' in mans: if Device.manufacturer in mans:
mans.remove('Default') mans.remove(Device.manufacturer)
return ['Default'] + sorted(mans) return [Device.manufacturer] + sorted(mans)
def get_devices_of(manufacturer): def get_devices_of(manufacturer):
ans = [d for d in get_devices() if d.manufacturer == manufacturer] ans = [d for d in get_devices() if d.manufacturer == manufacturer]
@ -402,22 +423,6 @@ class StanzaPage(QWizardPage, StanzaUI):
except: except:
continue continue
class WordPlayerPage(StanzaPage):
ID = 6
def __init__(self):
StanzaPage.__init__(self)
self.label.setText('<p>'+_('If you use the WordPlayer e-book app on '
'your Android phone, you can access your calibre book collection '
'directly on the device. To do this you have to turn on the '
'content server.'))
self.instructions.setText('<p>'+_('Remember to leave calibre running '
'as the server only runs as long as calibre is running.')+'<br><br>'
+ _('You have to add the URL http://myhostname:8080 as your '
'calibre library in WordPlayer. Here myhostname should be the fully '
'qualified hostname or the IP address of the computer calibre is running on.'))
class DevicePage(QWizardPage, DeviceUI): class DevicePage(QWizardPage, DeviceUI):
@ -430,6 +435,8 @@ class DevicePage(QWizardPage, DeviceUI):
self.registerField("device", self.device_view) self.registerField("device", self.device_view)
def initializePage(self): def initializePage(self):
self.label.setText(_('Choose you e-book device. If your device is'
' not in the list, choose a "%s" device.')%Device.manufacturer)
self.man_model = ManufacturerModel() self.man_model = ManufacturerModel()
self.manufacturer_view.setModel(self.man_model) self.manufacturer_view.setModel(self.man_model)
previous = dynamic.get('welcome_wizard_device', False) previous = dynamic.get('welcome_wizard_device', False)
@ -477,8 +484,6 @@ class DevicePage(QWizardPage, DeviceUI):
return KindlePage.ID return KindlePage.ID
if dev is iPhone: if dev is iPhone:
return StanzaPage.ID return StanzaPage.ID
if dev is Android:
return WordPlayerPage.ID
return FinishPage.ID return FinishPage.ID
class MoveMonitor(QObject): class MoveMonitor(QObject):
@ -753,13 +758,11 @@ class Wizard(QWizard):
self.set_finish_text() self.set_finish_text()
self.kindle_page = KindlePage() self.kindle_page = KindlePage()
self.stanza_page = StanzaPage() self.stanza_page = StanzaPage()
self.word_player_page = WordPlayerPage()
self.setPage(self.library_page.ID, self.library_page) self.setPage(self.library_page.ID, self.library_page)
self.setPage(self.device_page.ID, self.device_page) self.setPage(self.device_page.ID, self.device_page)
self.setPage(self.finish_page.ID, self.finish_page) self.setPage(self.finish_page.ID, self.finish_page)
self.setPage(self.kindle_page.ID, self.kindle_page) self.setPage(self.kindle_page.ID, self.kindle_page)
self.setPage(self.stanza_page.ID, self.stanza_page) self.setPage(self.stanza_page.ID, self.stanza_page)
self.setPage(self.word_player_page.ID, self.word_player_page)
self.device_extra_page = None self.device_extra_page = None
nh, nw = min_available_height()-75, available_width()-30 nh, nw = min_available_height()-75, available_width()-30

View File

@ -27,7 +27,7 @@
<item row="0" column="0" colspan="2"> <item row="0" column="0" colspan="2">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Choose your book reader. This will set the conversion options to produce books optimized for your device.</string> <string/>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>

View File

@ -49,7 +49,7 @@ class MetadataBackup(Thread): # {{{
def run(self): def run(self):
while self.keep_running: while self.keep_running:
try: try:
time.sleep(2) # Limit to two per second time.sleep(2) # Limit to one book per two seconds
(id_, sequence) = self.db.get_a_dirtied_book() (id_, sequence) = self.db.get_a_dirtied_book()
if id_ is None: if id_ is None:
continue continue

View File

@ -24,10 +24,9 @@ from calibre.utils.logging import default_log as log
from calibre.utils.zipfile import ZipFile, ZipInfo from calibre.utils.zipfile import ZipFile, ZipInfo
from calibre.utils.magick.draw import thumbnail from calibre.utils.magick.draw import thumbnail
FIELDS = ['all', 'author_sort', 'authors', 'comments', FIELDS = ['all', 'title', 'author_sort', 'authors', 'comments',
'cover', 'formats', 'id', 'isbn', 'ondevice', 'pubdate', 'publisher', 'rating', 'cover', 'formats','id', 'isbn', 'ondevice', 'pubdate', 'publisher',
'series_index', 'series', 'size', 'tags', 'timestamp', 'title', 'rating', 'series_index', 'series', 'size', 'tags', 'timestamp', 'uuid']
'uuid']
#Allowed fields for template #Allowed fields for template
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
@ -252,6 +251,15 @@ class BIBTEX(CatalogPlugin): # {{{
"Default: '%default'\n" "Default: '%default'\n"
"Applies to: BIBTEX output format")), "Applies to: BIBTEX output format")),
Option('--add-files-path',
default = 'True',
dest = 'addfiles',
action = None,
help = _('Create a file entry if formats is selected for BibTeX entries.\n'
'Boolean value: True, False\n'
"Default: '%default'\n"
"Applies to: BIBTEX output format")),
Option('--citation-template', Option('--citation-template',
default = '{authors}{id}', default = '{authors}{id}',
dest = 'bib_cit', dest = 'bib_cit',
@ -298,7 +306,7 @@ class BIBTEX(CatalogPlugin): # {{{
from calibre.utils.bibtex import BibTeX from calibre.utils.bibtex import BibTeX
def create_bibtex_entry(entry, fields, mode, template_citation, def create_bibtex_entry(entry, fields, mode, template_citation,
bibtexdict, citation_bibtex = True): bibtexdict, citation_bibtex=True, calibre_files=True):
#Bibtex doesn't like UTF-8 but keep unicode until writing #Bibtex doesn't like UTF-8 but keep unicode until writing
#Define starting chain or if book valid strict and not book return a Fail string #Define starting chain or if book valid strict and not book return a Fail string
@ -360,8 +368,13 @@ class BIBTEX(CatalogPlugin): # {{{
bibtex_entry.append(u'isbn = "%s"' % re.sub(u'[\D]', u'', item)) bibtex_entry.append(u'isbn = "%s"' % re.sub(u'[\D]', u'', item))
elif field == 'formats' : elif field == 'formats' :
item = u', '.join([format.rpartition('.')[2].lower() for format in item]) #Add file path if format is selected
bibtex_entry.append(u'formats = "%s"' % item) formats = [format.rpartition('.')[2].lower() for format in item]
bibtex_entry.append(u'formats = "%s"' % u', '.join(formats))
if calibre_files:
files = [u':%s:%s' % (format, format.rpartition('.')[2].upper())\
for format in item]
bibtex_entry.append(u'files = "%s"' % u', '.join(files))
elif field == 'series_index' : elif field == 'series_index' :
bibtex_entry.append(u'volume = "%s"' % int(item)) bibtex_entry.append(u'volume = "%s"' % int(item))
@ -511,15 +524,26 @@ class BIBTEX(CatalogPlugin): # {{{
else : else :
citation_bibtex= opts.impcit citation_bibtex= opts.impcit
#Check add file entry and go to default in case of bad CLI
if isinstance(opts.addfiles, (StringType, UnicodeType)) :
if opts.addfiles == 'False' :
addfiles_bibtex = False
elif opts.addfiles == 'True' :
addfiles_bibtex = True
else :
log(" WARNING: incorrect --add-files-path, revert to default")
addfiles_bibtex= True
else :
addfiles_bibtex = opts.addfiles
#Preprocess for error and light correction #Preprocess for error and light correction
template_citation = preprocess_template(opts.bib_cit) template_citation = preprocess_template(opts.bib_cit)
#Open output and write entries #Open output and write entries
outfile = codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag) with codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag)\
as outfile:
#File header #File header
nb_entries = len(data) nb_entries = len(data)
#check in book strict if all is ok else throw a warning into log #check in book strict if all is ok else throw a warning into log
if bib_entry == 'book' : if bib_entry == 'book' :
nb_books = len(filter(check_entry_book_valid, data)) nb_books = len(filter(check_entry_book_valid, data))
@ -533,9 +557,7 @@ class BIBTEX(CatalogPlugin): # {{{
for entry in data: for entry in data:
outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation, outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
bibtexc, citation_bibtex)) bibtexc, citation_bibtex, addfiles_bibtex))
outfile.close()
# }}} # }}}
class EPUB_MOBI(CatalogPlugin): class EPUB_MOBI(CatalogPlugin):

View File

@ -21,7 +21,7 @@ from calibre.library.field_metadata import FieldMetadata, TagsIcons
from calibre.library.schema_upgrades import SchemaUpgrade from calibre.library.schema_upgrades import SchemaUpgrade
from calibre.library.caches import ResultCache from calibre.library.caches import ResultCache
from calibre.library.custom_columns import CustomColumns from calibre.library.custom_columns import CustomColumns
from calibre.library.sqlite import connect, IntegrityError, DBThread from calibre.library.sqlite import connect, IntegrityError
from calibre.library.prefs import DBPrefs from calibre.library.prefs import DBPrefs
from calibre.ebooks.metadata import string_to_authors, authors_to_string from calibre.ebooks.metadata import string_to_authors, authors_to_string
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
@ -827,7 +827,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
try: try:
book_ids = self.data.parse(query) book_ids = self.data.parse(query)
except: except:
import traceback
traceback.print_exc() traceback.print_exc()
return identical_book_ids return identical_book_ids
for book_id in book_ids: for book_id in book_ids:
@ -2797,82 +2796,3 @@ books_series_link feeds
yield id, title, script yield id, title, script
def check_integrity(self, callback):
callback(0., _('Checking SQL integrity...'))
self.clean()
user_version = self.user_version
sql = '\n'.join(self.conn.dump())
self.conn.close()
dest = self.dbpath+'.tmp'
if os.path.exists(dest):
os.remove(dest)
conn = None
try:
ndb = DBThread(dest, None)
ndb.connect()
conn = ndb.conn
conn.execute('create table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)')
conn.commit()
conn.executescript(sql)
conn.commit()
conn.execute('pragma user_version=%d'%user_version)
conn.commit()
conn.execute('drop table temp_sequence')
conn.commit()
conn.close()
except:
if conn is not None:
try:
conn.close()
except:
pass
if os.path.exists(dest):
os.remove(dest)
raise
else:
shutil.copyfile(dest, self.dbpath)
self.connect()
self.initialize_dynamic()
self.refresh()
if os.path.exists(dest):
os.remove(dest)
callback(0.1, _('Checking for missing files.'))
bad = {}
us = self.data.universal_set()
total = float(len(us))
for i, id in enumerate(us):
formats = self.data.get(id, self.FIELD_MAP['formats'], row_is_id=True)
if not formats:
formats = []
else:
formats = [x.lower() for x in formats.split(',')]
actual_formats = self.formats(id, index_is_id=True)
if not actual_formats:
actual_formats = []
else:
actual_formats = [x.lower() for x in actual_formats.split(',')]
for fmt in formats:
if fmt in actual_formats:
continue
if id not in bad:
bad[id] = []
bad[id].append(fmt)
has_cover = self.data.get(id, self.FIELD_MAP['cover'],
row_is_id=True)
if has_cover and self.cover(id, index_is_id=True, as_path=True) is None:
if id not in bad:
bad[id] = []
bad[id].append('COVER')
callback(0.1+0.9*(1+i)/total, _('Checked id') + ' %d'%id)
for id in bad:
for fmt in bad[id]:
if fmt != 'COVER':
self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, fmt.upper()))
else:
self.conn.execute('UPDATE books SET has_cover=0 WHERE id=?', (id,))
self.conn.commit()
self.refresh_ids(list(bad.keys()))
return bad

View File

@ -17,6 +17,7 @@ class DBPrefs(dict):
dict.__init__(self) dict.__init__(self)
self.db = db self.db = db
self.defaults = {} self.defaults = {}
self.disable_setting = False
for key, val in self.db.conn.get('SELECT key,val FROM preferences'): for key, val in self.db.conn.get('SELECT key,val FROM preferences'):
try: try:
val = self.raw_to_object(val) val = self.raw_to_object(val)
@ -45,6 +46,8 @@ class DBPrefs(dict):
self.db.conn.commit() self.db.conn.commit()
def __setitem__(self, key, val): def __setitem__(self, key, val):
if self.disable_setting:
return
raw = self.to_raw(val) raw = self.to_raw(val)
self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,)) self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key, self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff