diff --git a/resources/recipes/20_minutos.recipe b/resources/recipes/20_minutos.recipe index 1f862847dc..cb3002a76c 100644 --- a/resources/recipes/20_minutos.recipe +++ b/resources/recipes/20_minutos.recipe @@ -2,7 +2,7 @@ __license__ = 'GPL v3' __author__ = 'Luis Hernandez' __copyright__ = 'Luis Hernandez' -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,10 +43,12 @@ 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 = [ (u'Portada' , u'http://www.20minutos.es/rss/') @@ -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/') ] - diff --git a/resources/recipes/calgary_herald.recipe b/resources/recipes/calgary_herald.recipe index 884a951d96..109089a372 100644 --- a/resources/recipes/calgary_herald.recipe +++ b/resources/recipes/calgary_herald.recipe @@ -44,6 +44,7 @@ class CanWestPaper(BasicNewsRecipe): language = 'en_CA' __author__ = 'Nick Redding' + encoding = 'latin1' no_stylesheets = True timefmt = ' [%b %d]' extra_css = ''' @@ -97,7 +98,9 @@ class CanWestPaper(BasicNewsRecipe): atag = h1tag.find('a',href=True) if not atag: continue - url = self.url_prefix+'/news/todays-paper/'+atag['href'] + 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) title = self.tag_to_string(atag,False) diff --git a/resources/recipes/capes_n_babes.recipe b/resources/recipes/capes_n_babes.recipe new file mode 100644 index 0000000000..f8f0946316 --- /dev/null +++ b/resources/recipes/capes_n_babes.recipe @@ -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/')] diff --git a/resources/recipes/dbb.recipe b/resources/recipes/dbb.recipe new file mode 100644 index 0000000000..d137e78f7a --- /dev/null +++ b/resources/recipes/dbb.recipe @@ -0,0 +1,45 @@ +# -*- coding: utf-8 + +__license__ = 'GPL v3' +__author__ = 'Luis Hernandez' +__copyright__ = 'Luis Hernandez' + +''' +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')] diff --git a/resources/recipes/la_nueva.recipe b/resources/recipes/la_nueva.recipe new file mode 100644 index 0000000000..84b7118cf6 --- /dev/null +++ b/resources/recipes/la_nueva.recipe @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +__license__ = 'GPL v3' +__author__ = 'Luis Hernandez' +__copyright__ = 'Luis Hernandez' +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') +] diff --git a/resources/recipes/la_tribuna.recipe b/resources/recipes/la_tribuna.recipe index 11bdda8f3e..739d11cc8d 100644 --- a/resources/recipes/la_tribuna.recipe +++ b/resources/recipes/la_tribuna.recipe @@ -1,9 +1,22 @@ +# -*- coding: utf-8 -*- +__license__ = 'GPL v3' +__author__ = 'Luis Hernandez' +__copyright__ = 'Luis Hernandez' +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')] diff --git a/resources/recipes/leduc.recipe b/resources/recipes/leduc.recipe new file mode 100644 index 0000000000..79ab693115 --- /dev/null +++ b/resources/recipes/leduc.recipe @@ -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'), +] diff --git a/resources/recipes/spin_magazine.recipe b/resources/recipes/spin_magazine.recipe new file mode 100644 index 0000000000..dbac216f81 --- /dev/null +++ b/resources/recipes/spin_magazine.recipe @@ -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') + + ] diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py index 763460d2ef..44628c22db 100644 --- a/src/calibre/customize/profiles.py +++ b/src/calibre/customize/profiles.py @@ -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())) diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 02401b25e6..fcd4491fd3 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -36,9 +36,10 @@ def author_to_author_sort(author): return author author = _bracket_pat.sub('', author).strip() tokens = author.split() - tokens = tokens[-1:] + tokens[:-1] - if len(tokens) > 1 and method != 'nocomma': - tokens[0] += ',' + if tokens and tokens[-1] not in ('Inc.', 'Inc'): + tokens = tokens[-1:] + tokens[:-1] + if len(tokens) > 1 and method != 'nocomma': + tokens[0] += ',' return ' '.join(tokens) def authors_to_sort_string(authors): diff --git a/src/calibre/ebooks/pml/pmlconverter.py b/src/calibre/ebooks/pml/pmlconverter.py index d2eb2c3736..20d8c7186b 100644 --- a/src/calibre/ebooks/pml/pmlconverter.py +++ b/src/calibre/ebooks/pml/pmlconverter.py @@ -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' % (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): diff --git a/src/calibre/ebooks/txt/input.py b/src/calibre/ebooks/txt/input.py index 6ec1edb65c..2399e599ae 100644 --- a/src/calibre/ebooks/txt/input.py +++ b/src/calibre/ebooks/txt/input.py @@ -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) diff --git a/src/calibre/ebooks/txt/processor.py b/src/calibre/ebooks/txt/processor.py index 43aadc6576..546d3f1842 100644 --- a/src/calibre/ebooks/txt/processor.py +++ b/src/calibre/ebooks/txt/processor.py @@ -20,9 +20,13 @@ HTML_TEMPLATE = u' + @@ -141,6 +141,13 @@ + + + Add files path with formats? + + + + Expression to form the BibTeX citation tag: diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index c873d1ed94..360a5bcd0a 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -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) + 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 ' - value = val - if ret_value is None: - return value - return ret_value + if first: + value = val + first = False + elif value != val: + value = None + if not value: + self.ignore_change_signals = False + return 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) + if val is None: + self.main_widget.setCurrentIndex(0) else: - if val is None: - self.widgets[1].setCurrentIndex(1) - 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 diff --git a/src/calibre/gui2/dialogs/catalog.py b/src/calibre/gui2/dialogs/catalog.py index 7bb288ed20..ebca7235eb 100644 --- a/src/calibre/gui2/dialogs/catalog.py +++ b/src/calibre/gui2/dialogs/catalog.py @@ -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) + diff --git a/src/calibre/gui2/dialogs/catalog.ui b/src/calibre/gui2/dialogs/catalog.ui index 62ac7cb5af..7f3951b87e 100644 --- a/src/calibre/gui2/dialogs/catalog.ui +++ b/src/calibre/gui2/dialogs/catalog.ui @@ -14,7 +14,7 @@ Generate catalog - + :/images/library.png:/images/library.png @@ -31,81 +31,6 @@ - - - - - 0 - 0 - - - - - 650 - 575 - - - - 0 - - - - Catalog options - - - - - - Catalog &format: - - - format - - - - - - - - - - Catalog &title (existing catalog with the same title will be replaced): - - - true - - - title - - - - - - - - - - &Send catalog to device automatically - - - - - - - Qt::Vertical - - - - 20 - 299 - - - - - - - - @@ -116,10 +41,110 @@ + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 666 + 599 + + + + + 0 + + + + + + 0 + 0 + + + + + 650 + 575 + + + + 0 + + + + Catalog options + + + + + + Catalog &format: + + + format + + + + + + + + + + Catalog &title (existing catalog with the same title will be replaced): + + + true + + + title + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + &Send catalog to device automatically + + + + + + + + + + + - + diff --git a/src/calibre/gui2/dialogs/check_library.py b/src/calibre/gui2/dialogs/check_library.py index c00ee99cc0..1c199afc03 100644 --- a/src/calibre/gui2/dialogs/check_library.py +++ b/src/calibre/gui2/dialogs/check_library.py @@ -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 diff --git a/src/calibre/gui2/dialogs/message_box.py b/src/calibre/gui2/dialogs/message_box.py index 123476b734..45ab73f8a1 100644 --- a/src/calibre/gui2/dialogs/message_box.py +++ b/src/calibre/gui2/dialogs/message_box.py @@ -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) diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 00bc98cb17..ca2ccfa6d5 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -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 ' diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py index 8aa624cacc..b6a3bed3eb 100644 --- a/src/calibre/gui2/dialogs/scheduler.py +++ b/src/calibre/gui2/dialogs/scheduler.py @@ -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) diff --git a/src/calibre/gui2/main_window.py b/src/calibre/gui2/main_window.py index a2221acd3a..e068e851c2 100644 --- a/src/calibre/gui2/main_window.py +++ b/src/calibre/gui2/main_window.py @@ -87,7 +87,7 @@ class MainWindow(QMainWindow): fe = sio.getvalue() prints(fe, file=sys.stderr) msg = '%s:'%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 diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index d7b9c62ed1..677ebac083 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -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: - self.tag.state = (self.tag.state + 1)%3 + 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))) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index c0658536bb..5fe630691c 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -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,7 +441,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.apply_named_search_restriction(db.prefs['gui_restriction']) if olddb is not None: try: - olddb.conn.close() + if call_close: + olddb.conn.close() except: import traceback traceback.print_exc() diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 41b18aebba..afbe6b5d8c 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -75,13 +75,13 @@ class FilenamePattern(QWidget, Ui_Form): # has added. val_hist = [unicode(self.re.lineEdit().text())] + [unicode(self.re.itemText(i)) for i in xrange(self.re.count())] self.re.clear() - + if defaults: val = prefs.defaults['filename_pattern'] else: val = prefs['filename_pattern'] self.re.lineEdit().setText(val) - + val_hist += gprefs.get('filename_pattern_history', ['(?P.+)', '(?P<author>[^_-]+) -?\s*(?P<series>[^_0-9-]*)(?P<series_index>[0-9]*)\s*-\s*(?P<title>[^_].+) ?']) if val in val_hist: del val_hist[val_hist.index(val)] @@ -129,15 +129,15 @@ class FilenamePattern(QWidget, Ui_Form): def commit(self): pat = self.pattern().pattern prefs['filename_pattern'] = pat - + history = [] history_pats = [unicode(self.re.lineEdit().text())] + [unicode(self.re.itemText(i)) for i in xrange(self.re.count())] for p in history_pats[:14]: # Ensure we don't have duplicate items. if p and p not in history: history.append(p) - gprefs['filename_pattern_history'] = history - + gprefs['filename_pattern_history'] = history + return pat diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index 8144dcabf3..5f9f1828fa 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -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 diff --git a/src/calibre/gui2/wizard/device.ui b/src/calibre/gui2/wizard/device.ui index 229f83be9e..ea120fb79b 100644 --- a/src/calibre/gui2/wizard/device.ui +++ b/src/calibre/gui2/wizard/device.ui @@ -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> diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 67529397b3..dd4509acea 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -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 diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index f0e4778de4..e20eebc517 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -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)) @@ -510,32 +523,41 @@ class BIBTEX(CatalogPlugin): # {{{ citation_bibtex= True 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)) + if nb_books < nb_entries : + log(" WARNING: only %d entries in %d are book compatible" % (nb_books, nb_entries)) + nb_entries = nb_books - #File header - nb_entries = len(data) + outfile.write(u'%%%Calibre catalog\n%%%{0} entries in catalog\n\n'.format(nb_entries)) + outfile.write(u'@preamble{"This catalog of %d entries was generated by calibre on %s"}\n\n' + % (nb_entries, nowf().strftime("%A, %d. %B %Y %H:%M").decode(preferred_encoding))) - #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)) - if nb_books < nb_entries : - log(" WARNING: only %d entries in %d are book compatible" % (nb_books, nb_entries)) - nb_entries = nb_books - - outfile.write(u'%%%Calibre catalog\n%%%{0} entries in catalog\n\n'.format(nb_entries)) - outfile.write(u'@preamble{"This catalog of %d entries was generated by calibre on %s"}\n\n' - % (nb_entries, nowf().strftime("%A, %d. %B %Y %H:%M").decode(preferred_encoding))) - - for entry in data: - outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation, - bibtexc, citation_bibtex)) - - outfile.close() + for entry in data: + outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation, + bibtexc, citation_bibtex, addfiles_bibtex)) # }}} class EPUB_MOBI(CatalogPlugin): diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index e1a1adc4ff..3c6d4016f2 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -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 diff --git a/src/calibre/library/prefs.py b/src/calibre/library/prefs.py index 2921e1c936..233c717897 100644 --- a/src/calibre/library/prefs.py +++ b/src/calibre/library/prefs.py @@ -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,