mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -04:00
merge from trunk
This commit is contained in:
commit
cc84c6f11b
@ -2,7 +2,7 @@
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'Luis Hernandez'
|
||||
__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
|
||||
@ -15,8 +15,8 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||
title = u'20 Minutos'
|
||||
publisher = u'Grupo 20 Minutos'
|
||||
|
||||
__author__ = u'Luis Hernández'
|
||||
description = u'Periódico gratuito en español'
|
||||
__author__ = 'Luis Hernández'
|
||||
description = 'Periódico gratuito en español'
|
||||
cover_url = 'http://estaticos.20minutos.es/mmedia/especiales/corporativo/css/img/logotipos_grupo20minutos.gif'
|
||||
|
||||
oldest_article = 5
|
||||
@ -30,8 +30,9 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||
language = 'es'
|
||||
timefmt = '[%a, %d %b, %Y]'
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':['content']})
|
||||
,dict(name='div', attrs={'class':['boxed','description','lead','article-content']})
|
||||
keep_only_tags = [
|
||||
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='ul', attrs={'class':['article-author']})
|
||||
]
|
||||
@ -42,9 +43,11 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||
remove_tags = [
|
||||
dict(name='ol', attrs={'class':['navigation',]})
|
||||
,dict(name='span', attrs={'class':['action']})
|
||||
,dict(name='div', attrs={'class':['twitter comments-list hidden','related-news','col']})
|
||||
,dict(name='div', attrs={'id':['twitter-destacados']})
|
||||
,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','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={'id':['site-links']})
|
||||
,dict(name='li', attrs={'class':['puntuacion','enviar','compartir']})
|
||||
]
|
||||
|
||||
feeds = [
|
||||
@ -62,6 +65,6 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||
,(u'Empleo' , u'http://www.20minutos.es/rss/empleo/')
|
||||
,(u'Cine' , u'http://www.20minutos.es/rss/cine/')
|
||||
,(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/')
|
||||
]
|
||||
|
||||
|
@ -44,6 +44,7 @@ class CanWestPaper(BasicNewsRecipe):
|
||||
|
||||
language = 'en_CA'
|
||||
__author__ = 'Nick Redding'
|
||||
encoding = 'latin1'
|
||||
no_stylesheets = True
|
||||
timefmt = ' [%b %d]'
|
||||
extra_css = '''
|
||||
@ -97,6 +98,8 @@ class CanWestPaper(BasicNewsRecipe):
|
||||
atag = h1tag.find('a',href=True)
|
||||
if not atag:
|
||||
continue
|
||||
url = atag['href']
|
||||
if not url.startswith('http:'):
|
||||
url = self.url_prefix+'/news/todays-paper/'+atag['href']
|
||||
#self.log("Section %s" % key)
|
||||
#self.log("url %s" % url)
|
||||
|
11
resources/recipes/capes_n_babes.recipe
Normal file
11
resources/recipes/capes_n_babes.recipe
Normal 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/')]
|
45
resources/recipes/dbb.recipe
Normal file
45
resources/recipes/dbb.recipe
Normal 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')]
|
74
resources/recipes/la_nueva.recipe
Normal file
74
resources/recipes/la_nueva.recipe
Normal 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')
|
||||
]
|
@ -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
|
||||
|
||||
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||
|
||||
title = u'La Tribuna de Talavera'
|
||||
publisher = u'Grupo PROMECAL'
|
||||
|
||||
__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'
|
||||
|
||||
oldest_article = 5
|
||||
@ -17,7 +30,8 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||
language = 'es'
|
||||
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='p', attrs={'id':['texto']})
|
||||
]
|
||||
@ -25,5 +39,13 @@ class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||
remove_tags_before = dict(name='div' , attrs={'class':['comparte']})
|
||||
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')]
|
||||
|
40
resources/recipes/leduc.recipe
Normal file
40
resources/recipes/leduc.recipe
Normal 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'),
|
||||
]
|
15
resources/recipes/spin_magazine.recipe
Normal file
15
resources/recipes/spin_magazine.recipe
Normal 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')
|
||||
|
||||
]
|
@ -495,6 +495,22 @@ class SonyReader900Output(SonyReaderOutput):
|
||||
screen_size = (600, 999)
|
||||
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):
|
||||
|
||||
name = 'JetBook 5-inch'
|
||||
@ -719,6 +735,6 @@ output_profiles = [OutputProfile, SonyReaderOutput, SonyReader300Output,
|
||||
iPadOutput, KoboReaderOutput, TabletOutput, SamsungGalaxy,
|
||||
SonyReaderLandscapeOutput, KindleDXOutput, IlliadOutput,
|
||||
IRexDR1000Output, IRexDR800Output, JetBook5Output, NookOutput,
|
||||
BambookOutput, NookColorOutput]
|
||||
BambookOutput, NookColorOutput, GenericEink, GenericEinkLarge]
|
||||
|
||||
output_profiles.sort(cmp=lambda x,y:cmp(x.name.lower(), y.name.lower()))
|
||||
|
@ -36,6 +36,7 @@ def author_to_author_sort(author):
|
||||
return author
|
||||
author = _bracket_pat.sub('', author).strip()
|
||||
tokens = author.split()
|
||||
if tokens and tokens[-1] not in ('Inc.', 'Inc'):
|
||||
tokens = tokens[-1:] + tokens[:-1]
|
||||
if len(tokens) > 1 and method != 'nocomma':
|
||||
tokens[0] += ','
|
||||
|
@ -143,7 +143,9 @@ class PML_HTMLizer(object):
|
||||
|
||||
def __init__(self):
|
||||
self.state = {}
|
||||
self.toc = TOC()
|
||||
# toc consists of a tuple
|
||||
# (level, (href, id, text))
|
||||
self.toc = []
|
||||
self.file_name = ''
|
||||
|
||||
def prepare_pml(self, pml):
|
||||
@ -494,7 +496,7 @@ class PML_HTMLizer(object):
|
||||
output = []
|
||||
|
||||
self.state = {}
|
||||
self.toc = TOC()
|
||||
self.toc = []
|
||||
self.file_name = file_name
|
||||
|
||||
indent_state = {'t': False, 'T': False}
|
||||
@ -542,6 +544,7 @@ class PML_HTMLizer(object):
|
||||
# inside of ="" so we don't have do special processing
|
||||
# for C.
|
||||
t = ''
|
||||
level = 0
|
||||
if c in 'XC':
|
||||
level = line.read(1)
|
||||
id = 'pml_toc-%s' % len(self.toc)
|
||||
@ -553,7 +556,7 @@ class PML_HTMLizer(object):
|
||||
if not value or value == '':
|
||||
text = t
|
||||
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)
|
||||
elif c == 'm':
|
||||
empty = False
|
||||
@ -624,7 +627,72 @@ class PML_HTMLizer(object):
|
||||
return output
|
||||
|
||||
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):
|
||||
|
@ -83,7 +83,6 @@ class TXTInput(InputFormatPlugin):
|
||||
setattr(options, 'markup_chapter_headings', True)
|
||||
setattr(options, 'italicize_common_cases', True)
|
||||
setattr(options, 'fix_indents', True)
|
||||
setattr(options, 'preserve_spaces', True)
|
||||
setattr(options, 'delete_blank_paragraphs', True)
|
||||
setattr(options, 'format_scene_breaks', True)
|
||||
setattr(options, 'dehyphenate', True)
|
||||
|
@ -20,9 +20,13 @@ HTML_TEMPLATE = u'<html><head><meta http-equiv="Content-Type" content="text/html
|
||||
def clean_txt(txt):
|
||||
if isbytestring(txt):
|
||||
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.
|
||||
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
|
||||
txt = re.sub('(?m)(?P<space>[ ]+)', lambda mo: ' ' * mo.groups('space').count(' '), txt)
|
||||
txt = re.sub('(?m)(?P<space>[\t]+)', lambda mo: ' ' * 4 * mo.groups('space').count('\t'), txt)
|
||||
|
||||
# Condense redundant spaces
|
||||
txt = re.sub('[ ]{2,}', ' ', txt)
|
||||
@ -31,7 +35,7 @@ def clean_txt(txt):
|
||||
txt = re.sub('^\s+(?=.)', '', txt)
|
||||
txt = re.sub('(?<=.)\s+$', '', txt)
|
||||
# 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
|
||||
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)
|
||||
|
||||
lines = []
|
||||
blank_count = 0
|
||||
# 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():
|
||||
blank_count = 0
|
||||
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> </p>')
|
||||
|
||||
return HTML_TEMPLATE % (title, u'\n'.join(lines))
|
||||
|
||||
@ -85,7 +95,7 @@ def normalize_line_endings(txt):
|
||||
return txt
|
||||
|
||||
def separate_paragraphs_single_line(txt):
|
||||
txt = re.sub(u'(?<=.)\n(?=.)', '\n\n', txt)
|
||||
txt = txt.replace('\n', '\n\n')
|
||||
return txt
|
||||
|
||||
def separate_paragraphs_print_formatted(txt):
|
||||
@ -93,7 +103,7 @@ def separate_paragraphs_print_formatted(txt):
|
||||
return txt
|
||||
|
||||
def preserve_spaces(txt):
|
||||
txt = txt.replace(' ', ' ')
|
||||
txt = re.sub('(?P<space>[ ]{2,})', lambda mo: ' ' + (' ' * (len(mo.group('space')) - 1)), txt)
|
||||
txt = txt.replace('\t', ' ')
|
||||
return txt
|
||||
|
||||
|
@ -100,6 +100,9 @@ class AddAction(InterfaceAction):
|
||||
mi = MetaInformation(_('Unknown'), dlg.selected_authors)
|
||||
self.gui.library_view.model().db.import_book(mi, [])
|
||||
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=[]):
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
|
@ -17,7 +17,7 @@ from calibre.gui2.actions import InterfaceAction
|
||||
class GenerateCatalogAction(InterfaceAction):
|
||||
|
||||
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'])
|
||||
|
||||
def generate_catalog(self):
|
||||
|
@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
|
||||
import os, shutil
|
||||
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.constants import filesystem_encoding
|
||||
@ -16,7 +16,7 @@ from calibre.utils.config import prefs
|
||||
from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \
|
||||
question_dialog, info_dialog
|
||||
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): # {{{
|
||||
|
||||
@ -76,76 +76,6 @@ class LibraryUsageStats(object): # {{{
|
||||
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):
|
||||
|
||||
name = 'Choose Library'
|
||||
@ -209,14 +139,6 @@ class ChooseLibraryAction(InterfaceAction):
|
||||
None, None), attr='action_check_library')
|
||||
ac.triggered.connect(self.check_library, type=Qt.QueuedConnection)
|
||||
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)
|
||||
|
||||
def pick_random(self, *args):
|
||||
@ -346,28 +268,35 @@ class ChooseLibraryAction(InterfaceAction):
|
||||
'rate of approximately 1 book every three seconds.'), show=True)
|
||||
|
||||
def check_library(self):
|
||||
db = self.gui.library_view.model().db
|
||||
d = CheckLibraryDialog(self.gui.parent(), db)
|
||||
d.exec_()
|
||||
|
||||
def check_database(self, *args):
|
||||
self.gui.library_view.save_state()
|
||||
m = self.gui.library_view.model()
|
||||
m.stop_metadata_backup()
|
||||
try:
|
||||
d = CheckIntegrity(m.db, self.gui)
|
||||
d.exec_()
|
||||
finally:
|
||||
m.start_metadata_backup()
|
||||
db = m.db
|
||||
db.prefs.disable_setting = True
|
||||
|
||||
def restore_database(self):
|
||||
info_dialog(self.gui, _('Recover database'), '<p>'+
|
||||
_(
|
||||
'This command rebuilds your calibre database from the information '
|
||||
'stored by calibre in the OPF files.<p>'
|
||||
'This function is not currently available in the GUI. You can '
|
||||
'recover your database using the \'calibredb restore_database\' '
|
||||
'command line function.'
|
||||
), show=True)
|
||||
d = DBCheck(self.gui, db)
|
||||
d.start()
|
||||
try:
|
||||
d.conn.close()
|
||||
except:
|
||||
pass
|
||||
d.break_cycles()
|
||||
self.gui.library_moved(db.library_path, call_close=not
|
||||
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):
|
||||
if not self.change_library_allowed():
|
||||
|
@ -31,7 +31,7 @@ class ConvertAction(InterfaceAction):
|
||||
partial(self.convert_ebook, False, bulk=True))
|
||||
cm.addSeparator()
|
||||
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)
|
||||
self.qaction.setMenu(cm)
|
||||
self.qaction.triggered.connect(self.convert_ebook)
|
||||
|
@ -19,7 +19,9 @@ class PluginWidget(QWidget, Ui_Form):
|
||||
('bib_entry', 0), #mixed
|
||||
('bibfile_enc', 0), #utf-8
|
||||
('bibfile_enctag', 0), #strict
|
||||
('impcit', True) ]
|
||||
('impcit', True),
|
||||
('addfiles', False),
|
||||
]
|
||||
|
||||
sync_enabled = False
|
||||
formats = set(['bib'])
|
||||
@ -49,7 +51,7 @@ class PluginWidget(QWidget, Ui_Form):
|
||||
opt_value = gprefs.get(self.name + '_' + opt[0], opt[1])
|
||||
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
||||
getattr(self, opt[0]).setCurrentIndex(opt_value)
|
||||
elif opt[0] == 'impcit' :
|
||||
elif opt[0] in ['impcit', 'addfiles'] :
|
||||
getattr(self, opt[0]).setChecked(opt_value)
|
||||
else:
|
||||
getattr(self, opt[0]).setText(opt_value)
|
||||
@ -76,7 +78,7 @@ class PluginWidget(QWidget, Ui_Form):
|
||||
for opt in self.OPTION_FIELDS:
|
||||
if opt[0] in ['bibfile_enc', 'bibfile_enctag', 'bib_entry']:
|
||||
opt_value = getattr(self,opt[0]).currentIndex()
|
||||
elif opt[0] == 'impcit' :
|
||||
elif opt[0] in ['impcit', 'addfiles'] :
|
||||
opt_value = getattr(self, opt[0]).isChecked()
|
||||
else :
|
||||
opt_value = unicode(getattr(self, opt[0]).text())
|
||||
|
@ -47,7 +47,7 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" rowspan="12">
|
||||
<item row="1" column="1" rowspan="11">
|
||||
<widget class="QListWidget" name="db_fields">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
@ -141,6 +141,13 @@
|
||||
</widget>
|
||||
</item>
|
||||
<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">
|
||||
<property name="text">
|
||||
<string>Expression to form the BibTeX citation tag:</string>
|
||||
|
@ -151,12 +151,27 @@ class DateEdit(QDateEdit):
|
||||
def set_to_today(self):
|
||||
self.setDate(now())
|
||||
|
||||
def set_to_clear(self):
|
||||
self.setDate(UNDEFINED_QDATE)
|
||||
|
||||
class DateTime(Base):
|
||||
|
||||
def setup_ui(self, parent):
|
||||
cm = self.col_metadata
|
||||
self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(parent),
|
||||
QLabel(''), QPushButton(_('Set \'%s\' to today')%cm['name'], parent)]
|
||||
self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(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]
|
||||
format = cm['display'].get('date_format','')
|
||||
if not format:
|
||||
@ -165,7 +180,8 @@ class DateTime(Base):
|
||||
w.setCalendarPopup(True)
|
||||
w.setMinimumDate(UNDEFINED_QDATE)
|
||||
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):
|
||||
if val is None:
|
||||
@ -470,11 +486,48 @@ class BulkBase(Base):
|
||||
self.setter(val)
|
||||
|
||||
def commit(self, book_ids, notify=False):
|
||||
if not self.a_c_checkbox.isChecked():
|
||||
return
|
||||
val = self.gui_val
|
||||
val = self.normalize_ui_val(val)
|
||||
if val != self.initial_val:
|
||||
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):
|
||||
|
||||
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:
|
||||
val = False
|
||||
if value is not None and value != val:
|
||||
return 'nochange'
|
||||
return None
|
||||
value = val
|
||||
return value
|
||||
|
||||
def setup_ui(self, parent):
|
||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
|
||||
QComboBox(parent)]
|
||||
w = self.widgets[1]
|
||||
items = [_('Yes'), _('No'), _('Undefined'), _('Do not change')]
|
||||
icons = [I('ok.png'), I('list_remove.png'), I('blank.png'), I('blank.png')]
|
||||
self.make_widgets(parent, QComboBox)
|
||||
items = [_('Yes'), _('No'), _('Undefined')]
|
||||
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
|
||||
self.main_widget.blockSignals(True)
|
||||
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):
|
||||
val = self.widgets[1].currentIndex()
|
||||
return {3: 'nochange', 2: None, 1: False, 0: True}[val]
|
||||
val = self.main_widget.currentIndex()
|
||||
return {2: None, 1: False, 0: True}[val]
|
||||
|
||||
def setter(self, val):
|
||||
val = {'nochange': 3, None: 2, False: 1, True: 0}[val]
|
||||
self.widgets[1].setCurrentIndex(val)
|
||||
val = {None: 2, False: 1, True: 0}[val]
|
||||
self.main_widget.setCurrentIndex(val)
|
||||
self.ignore_change_signals = False
|
||||
|
||||
def commit(self, book_ids, notify=False):
|
||||
if not self.a_c_checkbox.isChecked():
|
||||
return
|
||||
val = self.gui_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:
|
||||
val = False
|
||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
||||
|
||||
class BulkInt(BulkBase, Int):
|
||||
pass
|
||||
class BulkInt(BulkBase):
|
||||
|
||||
class BulkFloat(BulkBase, Float):
|
||||
pass
|
||||
def setup_ui(self, parent):
|
||||
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):
|
||||
pass
|
||||
def setter(self, val):
|
||||
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):
|
||||
pass
|
||||
def getter(self):
|
||||
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):
|
||||
|
||||
def setup_ui(self, parent):
|
||||
self.make_widgets(parent, EnComboBox)
|
||||
values = self.all_values = list(self.db.all_custom(num=self.col_id))
|
||||
values.sort(key=sort_key)
|
||||
w = EnComboBox(parent)
|
||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
||||
w.setMinimumContentsLength(25)
|
||||
self.name_widget = w
|
||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
|
||||
|
||||
self.main_widget.setSizeAdjustPolicy(self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.main_widget.setMinimumContentsLength(25)
|
||||
self.widgets.append(QLabel('', parent))
|
||||
w = QWidget(parent)
|
||||
layout = QHBoxLayout(w)
|
||||
@ -555,15 +694,24 @@ class BulkSeries(BulkBase):
|
||||
layout.addWidget(self.series_start_number)
|
||||
layout.addItem(QSpacerItem(20, 10, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
||||
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):
|
||||
self.idx_widget.setChecked(False)
|
||||
for c in self.all_values:
|
||||
self.name_widget.addItem(c)
|
||||
self.name_widget.setEditText('')
|
||||
self.main_widget.addItem(c)
|
||||
self.main_widget.setEditText('')
|
||||
self.a_c_checkbox.setChecked(False)
|
||||
|
||||
def getter(self):
|
||||
n = unicode(self.name_widget.currentText()).strip()
|
||||
n = unicode(self.main_widget.currentText()).strip()
|
||||
i = self.idx_widget.checkState()
|
||||
f = self.force_number.checkState()
|
||||
s = self.series_start_number.value()
|
||||
@ -571,6 +719,8 @@ class BulkSeries(BulkBase):
|
||||
return n, i, f, s, r
|
||||
|
||||
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 = None if clear else self.normalize_ui_val(val)
|
||||
if clear or val != '':
|
||||
@ -598,9 +748,9 @@ class BulkEnumeration(BulkBase, Enumeration):
|
||||
|
||||
def get_initial_value(self, book_ids):
|
||||
value = None
|
||||
ret_value = None
|
||||
first = True
|
||||
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)
|
||||
if val and val not in self.col_metadata['display']['enum_values']:
|
||||
if not dialog_shown:
|
||||
@ -610,44 +760,32 @@ class BulkEnumeration(BulkBase, Enumeration):
|
||||
self.col_metadata['name']),
|
||||
show=True, show_copy_button=False)
|
||||
dialog_shown = True
|
||||
ret_value = ' nochange '
|
||||
elif (value is not None and value != val) or (val and i != 0):
|
||||
ret_value = ' nochange '
|
||||
if first:
|
||||
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 ret_value
|
||||
|
||||
def setup_ui(self, parent):
|
||||
self.parent = parent
|
||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent),
|
||||
QComboBox(parent)]
|
||||
w = self.widgets[1]
|
||||
self.make_widgets(parent, QComboBox)
|
||||
vals = self.col_metadata['display']['enum_values']
|
||||
w.addItem('Do Not Change')
|
||||
w.addItem('')
|
||||
for v in vals:
|
||||
w.addItem(v)
|
||||
self.main_widget.blockSignals(True)
|
||||
self.main_widget.addItem('')
|
||||
self.main_widget.addItems(vals)
|
||||
self.main_widget.blockSignals(False)
|
||||
|
||||
def getter(self):
|
||||
if self.widgets[1].currentIndex() == 0:
|
||||
return ' nochange '
|
||||
return unicode(self.widgets[1].currentText())
|
||||
return unicode(self.main_widget.currentText())
|
||||
|
||||
def setter(self, val):
|
||||
if val == ' nochange ':
|
||||
self.widgets[1].setCurrentIndex(0)
|
||||
else:
|
||||
if val is None:
|
||||
self.widgets[1].setCurrentIndex(1)
|
||||
self.main_widget.setCurrentIndex(0)
|
||||
else:
|
||||
self.widgets[1].setCurrentIndex(self.widgets[1].findText(val))
|
||||
|
||||
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)
|
||||
self.main_widget.setCurrentIndex(self.main_widget.findText(val))
|
||||
self.ignore_change_signals = False
|
||||
|
||||
class RemoveTags(QWidget):
|
||||
|
||||
@ -658,11 +796,10 @@ class RemoveTags(QWidget):
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.tags_box = CompleteLineEdit(parent, values)
|
||||
layout.addWidget(self.tags_box, stretch = 1)
|
||||
# self.tags_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
|
||||
layout.addWidget(self.tags_box, stretch=3)
|
||||
self.checkbox = QCheckBox(_('Remove all tags'), parent)
|
||||
layout.addWidget(self.checkbox)
|
||||
layout.addStretch(1)
|
||||
self.setLayout(layout)
|
||||
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.sort(key=sort_key)
|
||||
if self.col_metadata['is_multiple']:
|
||||
w = CompleteLineEdit(parent, values)
|
||||
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
self.widgets = [QLabel('&'+self.col_metadata['name']+': ' +
|
||||
_('tags to add'), parent), w]
|
||||
self.adding_widget = w
|
||||
self.make_widgets(parent, CompleteLineEdit,
|
||||
extra_label_text=_('tags to add'))
|
||||
self.main_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||
self.adding_widget = self.main_widget
|
||||
|
||||
w = RemoveTags(parent, values)
|
||||
self.widgets.append(QLabel('&'+self.col_metadata['name']+': ' +
|
||||
_('tags to remove'), parent))
|
||||
self.widgets.append(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:
|
||||
w = EnComboBox(parent)
|
||||
w.setSizeAdjustPolicy(w.AdjustToMinimumContentsLengthWithIcon)
|
||||
w.setMinimumContentsLength(25)
|
||||
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
|
||||
self.make_widgets(parent, EnComboBox)
|
||||
self.main_widget.setSizeAdjustPolicy(
|
||||
self.main_widget.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.main_widget.setMinimumContentsLength(25)
|
||||
self.ignore_change_signals = False
|
||||
|
||||
def initialize(self, book_ids):
|
||||
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:
|
||||
val = self.get_initial_value(book_ids)
|
||||
self.initial_val = val = self.normalize_db_val(val)
|
||||
idx = None
|
||||
self.main_widget.blockSignals(True)
|
||||
for i, c in enumerate(self.all_values):
|
||||
if c == val:
|
||||
idx = i
|
||||
self.widgets[1].addItem(c)
|
||||
self.widgets[1].setEditText('')
|
||||
self.main_widget.addItem(c)
|
||||
self.main_widget.setEditText('')
|
||||
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):
|
||||
if not self.a_c_checkbox.isChecked():
|
||||
return
|
||||
if self.col_metadata['is_multiple']:
|
||||
remove_all, adding, rtext = self.gui_val
|
||||
remove = set()
|
||||
@ -740,7 +883,7 @@ class BulkText(BulkBase):
|
||||
unicode(self.adding_widget.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:
|
||||
val = None
|
||||
return val
|
||||
|
@ -8,15 +8,12 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, sys
|
||||
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4.Qt import QDialog, SIGNAL
|
||||
|
||||
from calibre.customize.ui import config
|
||||
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
|
||||
|
||||
class Catalog(QDialog, Ui_Dialog):
|
||||
class Catalog(ResizableDialog, Ui_Dialog):
|
||||
''' Catalog Dialog builder'''
|
||||
|
||||
def __init__(self, parent, dbspec, ids, db):
|
||||
@ -24,10 +21,8 @@ class Catalog(QDialog, Ui_Dialog):
|
||||
from calibre import prints as info
|
||||
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
|
||||
|
||||
# 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.format.currentIndexChanged.connect(self.show_plugin_tab)
|
||||
self.connect(self.buttonBox.button(QtGui.QDialogButtonBox.Apply),
|
||||
SIGNAL("clicked()"),
|
||||
self.apply)
|
||||
self.buttonBox.button(self.buttonBox.Apply).clicked.connect(self.apply)
|
||||
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):
|
||||
cf = unicode(self.format.currentText()).lower()
|
||||
while self.tabs.count() > 1:
|
||||
@ -157,8 +154,9 @@ class Catalog(QDialog, Ui_Dialog):
|
||||
dynamic.set('catalog_last_used_title', self.catalog_title)
|
||||
self.catalog_sync = bool(self.sync.isChecked())
|
||||
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
|
||||
self.save_catalog_settings()
|
||||
if self.tabs.count() > 1:
|
||||
@ -166,4 +164,9 @@ class Catalog(QDialog, Ui_Dialog):
|
||||
|
||||
def accept(self):
|
||||
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)
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
<string>Generate catalog</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/library.png</normaloff>:/images/library.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
@ -31,7 +31,38 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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">
|
||||
<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">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
@ -82,13 +113,6 @@
|
||||
<item row="1" column="2">
|
||||
<widget class="QLineEdit" name="title"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="sync">
|
||||
<property name="text">
|
||||
<string>&Send catalog to device automatically</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
@ -96,30 +120,31 @@
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>299</height>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="sync">
|
||||
<property name="text">
|
||||
<string>&Send catalog to device automatically</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</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>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../../work/calibre/resources/images.qrc"/>
|
||||
<include location="../../../../resources/images.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
@ -3,16 +3,132 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
import os
|
||||
import os, shutil
|
||||
|
||||
from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \
|
||||
QPushButton, QDialogButtonBox, QApplication, QTreeWidgetItem, \
|
||||
QLineEdit, Qt
|
||||
QLineEdit, Qt, QProgressBar, QSize, QTimer
|
||||
|
||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||
from calibre.library.check_library import CheckLibrary, CHECKS
|
||||
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):
|
||||
pass
|
||||
|
@ -39,6 +39,12 @@ class MessageBox(QDialog, Ui_Dialog):
|
||||
self.det_msg.setPlainText(det_msg)
|
||||
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:
|
||||
self.show_det_msg = _('Show &details')
|
||||
self.hide_det_msg = _('Hide &details')
|
||||
@ -47,11 +53,6 @@ class MessageBox(QDialog, Ui_Dialog):
|
||||
self.det_msg_toggle.setToolTip(
|
||||
_('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.addAction(self.copy_action)
|
||||
|
@ -208,6 +208,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
from calibre.gui2 import config
|
||||
title = unicode(self.title.text()).strip()
|
||||
author = unicode(self.authors.text()).strip()
|
||||
if author.endswith('&'):
|
||||
author = author[:-1]
|
||||
if not title or not author:
|
||||
return error_dialog(self, _('Specify title and author'),
|
||||
_('You must specify a title and author before generating '
|
||||
|
@ -259,14 +259,14 @@ class Scheduler(QObject):
|
||||
if self.oldest > 0:
|
||||
delta = timedelta(days=self.oldest)
|
||||
try:
|
||||
ids = self.recipe_model.db.tags_older_than(_('News'), delta)
|
||||
ids = list(self.recipe_model.db.tags_older_than(_('News'),
|
||||
delta))
|
||||
except:
|
||||
# Should never happen
|
||||
ids = []
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
if ids:
|
||||
ids = list(ids)
|
||||
if ids:
|
||||
self.delete_old_news.emit(ids)
|
||||
QTimer.singleShot(60 * 60 * 1000, self.oldest_check)
|
||||
|
@ -87,7 +87,7 @@ class MainWindow(QMainWindow):
|
||||
fe = sio.getvalue()
|
||||
prints(fe, file=sys.stderr)
|
||||
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)
|
||||
except BaseException:
|
||||
pass
|
||||
|
@ -64,6 +64,8 @@ class TagDelegate(QItemDelegate): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_minus': 2}
|
||||
|
||||
class TagsView(QTreeView): # {{{
|
||||
|
||||
refresh_required = pyqtSignal()
|
||||
@ -177,9 +179,16 @@ class TagsView(QTreeView): # {{{
|
||||
return joiner.join(tokens)
|
||||
|
||||
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())
|
||||
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)
|
||||
|
||||
def conditional_clear(self, search_string):
|
||||
@ -187,7 +196,7 @@ class TagsView(QTreeView): # {{{
|
||||
self.clear()
|
||||
|
||||
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:
|
||||
return
|
||||
try:
|
||||
@ -201,11 +210,10 @@ class TagsView(QTreeView): # {{{
|
||||
self.user_category_edit.emit(category)
|
||||
return
|
||||
if action == 'search':
|
||||
self.tags_marked.emit(('not ' if negate else '') +
|
||||
category + ':"=' + key + '"')
|
||||
self._toggle(index, set_to=search_state)
|
||||
return
|
||||
if action == 'search_category':
|
||||
self.tags_marked.emit(category + ':' + str(not negate))
|
||||
self.tags_marked.emit(key + ':' + search_state)
|
||||
return
|
||||
if action == 'manage_searches':
|
||||
self.saved_search_edit.emit(category)
|
||||
@ -270,20 +278,16 @@ class TagsView(QTreeView): # {{{
|
||||
partial(self.context_menu_handler,
|
||||
action='edit_author_sort', index=tag_id))
|
||||
# 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,
|
||||
_('Search for %s')%tag_name,
|
||||
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,
|
||||
_('Search for everything but %s')%tag_name,
|
||||
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()
|
||||
# Hide/Show/Restore categories
|
||||
self.context_menu.addAction(_('Hide category %s') % category,
|
||||
@ -299,11 +303,11 @@ class TagsView(QTreeView): # {{{
|
||||
self.context_menu.addAction(self.search_icon,
|
||||
_('Search for books in category %s')%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,
|
||||
_('Search for books not in category %s')%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
|
||||
self.context_menu.addSeparator()
|
||||
if key in ['tags', 'publisher', 'series'] or \
|
||||
@ -528,9 +532,15 @@ class TagTreeItem(object): # {{{
|
||||
return QVariant(self.tooltip)
|
||||
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 set_to is None:
|
||||
self.tag.state = (self.tag.state + 1)%3
|
||||
else:
|
||||
self.tag.state = set_to
|
||||
|
||||
def child_tags(self):
|
||||
res = []
|
||||
@ -1014,11 +1024,15 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
def clear_state(self):
|
||||
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
|
||||
item = index.internalPointer()
|
||||
if item.type == TagTreeItem.TAG:
|
||||
item.toggle()
|
||||
item.toggle(set_to=set_to)
|
||||
if exclusive:
|
||||
self.reset_all_states(except_=item.tag)
|
||||
self.dataChanged.emit(index, index)
|
||||
@ -1040,8 +1054,9 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
category_item = self.root_item.children[row_index]
|
||||
for tag_item in category_item.child_tags():
|
||||
tag = tag_item.tag
|
||||
if tag.state > 0:
|
||||
prefix = ' not ' if tag.state == 2 else ''
|
||||
if tag.state != TAG_SEARCH_STATES['clear']:
|
||||
prefix = ' not ' if tag.state == TAG_SEARCH_STATES['mark_minus'] \
|
||||
else ''
|
||||
category = key if key != 'news' else 'tag'
|
||||
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)))
|
||||
|
@ -408,7 +408,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
def booklists(self):
|
||||
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
|
||||
default_prefs = None
|
||||
try:
|
||||
@ -441,6 +441,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
||||
self.apply_named_search_restriction(db.prefs['gui_restriction'])
|
||||
if olddb is not None:
|
||||
try:
|
||||
if call_close:
|
||||
olddb.conn.close()
|
||||
except:
|
||||
import traceback
|
||||
|
@ -33,10 +33,10 @@ from calibre.gui2.dialogs.progress import ProgressDialog
|
||||
|
||||
class Device(object):
|
||||
|
||||
output_profile = 'default'
|
||||
output_profile = 'generic_eink'
|
||||
output_format = 'EPUB'
|
||||
name = 'Default'
|
||||
manufacturer = 'Default'
|
||||
name = 'Generic e-ink device'
|
||||
manufacturer = 'Generic'
|
||||
id = 'default'
|
||||
supports_color = False
|
||||
|
||||
@ -63,6 +63,18 @@ class Device(object):
|
||||
recs['dont_grayscale'] = True
|
||||
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):
|
||||
|
||||
@ -206,12 +218,21 @@ class iPhone(Device):
|
||||
|
||||
class Android(Device):
|
||||
|
||||
name = 'Adroid phone + WordPlayer/Aldiko'
|
||||
name = 'Android phone'
|
||||
output_format = 'EPUB'
|
||||
manufacturer = 'Android'
|
||||
id = 'android'
|
||||
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):
|
||||
|
||||
name = 'Hanlin V3'
|
||||
@ -268,9 +289,9 @@ def get_manufacturers():
|
||||
mans = set([])
|
||||
for x in get_devices():
|
||||
mans.add(x.manufacturer)
|
||||
if 'Default' in mans:
|
||||
mans.remove('Default')
|
||||
return ['Default'] + sorted(mans)
|
||||
if Device.manufacturer in mans:
|
||||
mans.remove(Device.manufacturer)
|
||||
return [Device.manufacturer] + sorted(mans)
|
||||
|
||||
def get_devices_of(manufacturer):
|
||||
ans = [d for d in get_devices() if d.manufacturer == manufacturer]
|
||||
@ -402,22 +423,6 @@ class StanzaPage(QWizardPage, StanzaUI):
|
||||
except:
|
||||
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):
|
||||
|
||||
@ -430,6 +435,8 @@ class DevicePage(QWizardPage, DeviceUI):
|
||||
self.registerField("device", self.device_view)
|
||||
|
||||
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.manufacturer_view.setModel(self.man_model)
|
||||
previous = dynamic.get('welcome_wizard_device', False)
|
||||
@ -477,8 +484,6 @@ class DevicePage(QWizardPage, DeviceUI):
|
||||
return KindlePage.ID
|
||||
if dev is iPhone:
|
||||
return StanzaPage.ID
|
||||
if dev is Android:
|
||||
return WordPlayerPage.ID
|
||||
return FinishPage.ID
|
||||
|
||||
class MoveMonitor(QObject):
|
||||
@ -753,13 +758,11 @@ class Wizard(QWizard):
|
||||
self.set_finish_text()
|
||||
self.kindle_page = KindlePage()
|
||||
self.stanza_page = StanzaPage()
|
||||
self.word_player_page = WordPlayerPage()
|
||||
self.setPage(self.library_page.ID, self.library_page)
|
||||
self.setPage(self.device_page.ID, self.device_page)
|
||||
self.setPage(self.finish_page.ID, self.finish_page)
|
||||
self.setPage(self.kindle_page.ID, self.kindle_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
|
||||
nh, nw = min_available_height()-75, available_width()-30
|
||||
|
@ -27,7 +27,7 @@
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<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 name="wordWrap">
|
||||
<bool>true</bool>
|
||||
|
@ -49,7 +49,7 @@ class MetadataBackup(Thread): # {{{
|
||||
def run(self):
|
||||
while self.keep_running:
|
||||
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()
|
||||
if id_ is None:
|
||||
continue
|
||||
|
@ -24,10 +24,9 @@ from calibre.utils.logging import default_log as log
|
||||
from calibre.utils.zipfile import ZipFile, ZipInfo
|
||||
from calibre.utils.magick.draw import thumbnail
|
||||
|
||||
FIELDS = ['all', 'author_sort', 'authors', 'comments',
|
||||
'cover', 'formats', 'id', 'isbn', 'ondevice', 'pubdate', 'publisher', 'rating',
|
||||
'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
|
||||
'uuid']
|
||||
FIELDS = ['all', 'title', 'author_sort', 'authors', 'comments',
|
||||
'cover', 'formats','id', 'isbn', 'ondevice', 'pubdate', 'publisher',
|
||||
'rating', 'series_index', 'series', 'size', 'tags', 'timestamp', 'uuid']
|
||||
|
||||
#Allowed fields for template
|
||||
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
|
||||
@ -252,6 +251,15 @@ class BIBTEX(CatalogPlugin): # {{{
|
||||
"Default: '%default'\n"
|
||||
"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',
|
||||
default = '{authors}{id}',
|
||||
dest = 'bib_cit',
|
||||
@ -298,7 +306,7 @@ class BIBTEX(CatalogPlugin): # {{{
|
||||
from calibre.utils.bibtex import BibTeX
|
||||
|
||||
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
|
||||
#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))
|
||||
|
||||
elif field == 'formats' :
|
||||
item = u', '.join([format.rpartition('.')[2].lower() for format in item])
|
||||
bibtex_entry.append(u'formats = "%s"' % item)
|
||||
#Add file path if format is selected
|
||||
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' :
|
||||
bibtex_entry.append(u'volume = "%s"' % int(item))
|
||||
@ -511,15 +524,26 @@ class BIBTEX(CatalogPlugin): # {{{
|
||||
else :
|
||||
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
|
||||
template_citation = preprocess_template(opts.bib_cit)
|
||||
|
||||
#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
|
||||
nb_entries = len(data)
|
||||
|
||||
#check in book strict if all is ok else throw a warning into log
|
||||
if bib_entry == 'book' :
|
||||
nb_books = len(filter(check_entry_book_valid, data))
|
||||
@ -533,9 +557,7 @@ class BIBTEX(CatalogPlugin): # {{{
|
||||
|
||||
for entry in data:
|
||||
outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
|
||||
bibtexc, citation_bibtex))
|
||||
|
||||
outfile.close()
|
||||
bibtexc, citation_bibtex, addfiles_bibtex))
|
||||
# }}}
|
||||
|
||||
class EPUB_MOBI(CatalogPlugin):
|
||||
|
@ -21,7 +21,7 @@ from calibre.library.field_metadata import FieldMetadata, TagsIcons
|
||||
from calibre.library.schema_upgrades import SchemaUpgrade
|
||||
from calibre.library.caches import ResultCache
|
||||
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.ebooks.metadata import string_to_authors, authors_to_string
|
||||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
@ -618,9 +618,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
'''
|
||||
with self.dirtied_lock:
|
||||
dc_sequence = self.dirtied_cache.get(book_id, None)
|
||||
# print 'clear_dirty: check book', book_id, dc_sequence
|
||||
# print 'clear_dirty: check book', book_id, dc_sequence
|
||||
if dc_sequence is None or sequence is None or dc_sequence == sequence:
|
||||
# print 'needs to be cleaned'
|
||||
# print 'needs to be cleaned'
|
||||
self.conn.execute('DELETE FROM metadata_dirtied WHERE book=?',
|
||||
(book_id,))
|
||||
self.conn.commit()
|
||||
@ -629,7 +629,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
except:
|
||||
pass
|
||||
elif dc_sequence is not None:
|
||||
# print 'book needs to be done again'
|
||||
# print 'book needs to be done again'
|
||||
pass
|
||||
|
||||
def dump_metadata(self, book_ids=None, remove_from_dirtied=True,
|
||||
@ -661,12 +661,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
changed = False
|
||||
for book in book_ids:
|
||||
with self.dirtied_lock:
|
||||
# print 'dirtied: check id', book
|
||||
# print 'dirtied: check id', book
|
||||
if book in self.dirtied_cache:
|
||||
self.dirtied_cache[book] = self.dirtied_sequence
|
||||
self.dirtied_sequence += 1
|
||||
continue
|
||||
# print 'book not already dirty'
|
||||
# print 'book not already dirty'
|
||||
try:
|
||||
self.conn.execute(
|
||||
'INSERT INTO metadata_dirtied (book) VALUES (?)',
|
||||
@ -720,7 +720,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
# thread has not done the work between the put and the get_metadata
|
||||
with self.dirtied_lock:
|
||||
sequence = self.dirtied_cache.get(idx, None)
|
||||
# print 'get_md_for_dump', idx, sequence
|
||||
# print 'get_md_for_dump', idx, sequence
|
||||
try:
|
||||
# While a book is being created, the path is empty. Don't bother to
|
||||
# try to write the opf, because it will go to the wrong folder.
|
||||
@ -827,7 +827,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
try:
|
||||
book_ids = self.data.parse(query)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return identical_book_ids
|
||||
for book_id in book_ids:
|
||||
@ -2797,82 +2796,3 @@ books_series_link feeds
|
||||
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
|
||||
|
@ -17,6 +17,7 @@ class DBPrefs(dict):
|
||||
dict.__init__(self)
|
||||
self.db = db
|
||||
self.defaults = {}
|
||||
self.disable_setting = False
|
||||
for key, val in self.db.conn.get('SELECT key,val FROM preferences'):
|
||||
try:
|
||||
val = self.raw_to_object(val)
|
||||
@ -45,6 +46,8 @@ class DBPrefs(dict):
|
||||
self.db.conn.commit()
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
if self.disable_setting:
|
||||
return
|
||||
raw = self.to_raw(val)
|
||||
self.db.conn.execute('DELETE FROM preferences WHERE key=?', (key,))
|
||||
self.db.conn.execute('INSERT INTO preferences (key,val) VALUES (?,?)', (key,
|
||||
|
Loading…
x
Reference in New Issue
Block a user