mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
bb6686a3b3
@ -62,6 +62,18 @@ div.description {
|
|||||||
text-indent: 1em;
|
text-indent: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attempt to minimize widows and orphans by logically grouping chunks
|
||||||
|
* Recommend enabling for iPad
|
||||||
|
* Some reports of problems with Sony ereaders, presumably ADE engines
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
div.logical_group {
|
||||||
|
display:inline-block;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
p.date_index {
|
p.date_index {
|
||||||
font-size:x-large;
|
font-size:x-large;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
|
@ -1,17 +1,67 @@
|
|||||||
|
# -*- coding: utf-8
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__author__ = 'Luis Hernandez'
|
||||||
|
__copyright__ = 'Luis Hernandez<tolyluis@gmail.com>'
|
||||||
|
description = 'Periódico gratuito en español - v0.5 - 25 Jan 2011'
|
||||||
|
|
||||||
|
'''
|
||||||
|
www.20minutos.es
|
||||||
|
'''
|
||||||
|
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class AdvancedUserRecipe1295310874(BasicNewsRecipe):
|
class AdvancedUserRecipe1294946868(BasicNewsRecipe):
|
||||||
title = u'20 Minutos (Boletin)'
|
|
||||||
__author__ = 'Luis Hernandez'
|
title = u'20 Minutos'
|
||||||
description = 'Periódico gratuito en español'
|
publisher = u'Grupo 20 Minutos'
|
||||||
|
|
||||||
|
__author__ = u'Luis Hernández'
|
||||||
|
description = u'Periódico gratuito en español'
|
||||||
cover_url = 'http://estaticos.20minutos.es/mmedia/especiales/corporativo/css/img/logotipos_grupo20minutos.gif'
|
cover_url = 'http://estaticos.20minutos.es/mmedia/especiales/corporativo/css/img/logotipos_grupo20minutos.gif'
|
||||||
language = 'es'
|
|
||||||
|
|
||||||
oldest_article = 2
|
oldest_article = 5
|
||||||
max_articles_per_feed = 50
|
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={'id':['content']})
|
||||||
|
,dict(name='div', attrs={'class':['boxed','description','lead','article-content']})
|
||||||
|
,dict(name='span', attrs={'class':['photo-bar']})
|
||||||
|
,dict(name='ul', attrs={'class':['article-author']})
|
||||||
|
]
|
||||||
|
|
||||||
|
remove_tags_before = dict(name='ul' , attrs={'class':['servicios-sub']})
|
||||||
|
remove_tags_after = dict(name='div' , attrs={'class':['related-news','col']})
|
||||||
|
|
||||||
|
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='ul', attrs={'class':['article-user-actions','stripped-list']})
|
||||||
|
]
|
||||||
|
|
||||||
|
feeds = [
|
||||||
|
(u'Portada' , u'http://www.20minutos.es/rss/')
|
||||||
|
,(u'Nacional' , u'http://www.20minutos.es/rss/nacional/')
|
||||||
|
,(u'Internacional' , u'http://www.20minutos.es/rss/internacional/')
|
||||||
|
,(u'Economia' , u'http://www.20minutos.es/rss/economia/')
|
||||||
|
,(u'Deportes' , u'http://www.20minutos.es/rss/deportes/')
|
||||||
|
,(u'Tecnologia' , u'http://www.20minutos.es/rss/tecnologia/')
|
||||||
|
,(u'Gente - TV' , u'http://www.20minutos.es/rss/gente-television/')
|
||||||
|
,(u'Motor' , u'http://www.20minutos.es/rss/motor/')
|
||||||
|
,(u'Salud' , u'http://www.20minutos.es/rss/belleza-y-salud/')
|
||||||
|
,(u'Viajes' , u'http://www.20minutos.es/rss/viajes/')
|
||||||
|
,(u'Vivienda' , u'http://www.20minutos.es/rss/vivienda/')
|
||||||
|
,(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'Comunidad20' , u'http://www.20minutos.es/rss/zona20/')
|
||||||
|
]
|
||||||
|
|
||||||
feeds = [(u'VESPERTINO', u'http://20minutos.feedsportal.com/c/32489/f/478284/index.rss')
|
|
||||||
, (u'DEPORTES', u'http://20minutos.feedsportal.com/c/32489/f/478286/index.rss')
|
|
||||||
, (u'CULTURA', u'http://www.20minutos.es/rss/ocio/')
|
|
||||||
, (u'TV', u'http://20minutos.feedsportal.com/c/32489/f/490877/index.rss')
|
|
||||||
]
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2008-2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
newyorker.com
|
newyorker.com
|
||||||
'''
|
'''
|
||||||
@ -54,10 +54,10 @@ class NewYorker(BasicNewsRecipe):
|
|||||||
,dict(attrs={'id':['show-header','show-footer'] })
|
,dict(attrs={'id':['show-header','show-footer'] })
|
||||||
]
|
]
|
||||||
remove_attributes = ['lang']
|
remove_attributes = ['lang']
|
||||||
feeds = [(u'The New Yorker', u'http://feeds.newyorker.com/services/rss/feeds/everything.xml')]
|
feeds = [(u'The New Yorker', u'http://www.newyorker.com/services/rss/feeds/everything.xml')]
|
||||||
|
|
||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
return url + '?printable=true'
|
return 'http://www.newyorker.com' + url + '?printable=true'
|
||||||
|
|
||||||
def image_url_processor(self, baseurl, url):
|
def image_url_processor(self, baseurl, url):
|
||||||
return url.strip()
|
return url.strip()
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
'''
|
'''
|
||||||
@ -28,6 +27,10 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
# previous paid versions of the new york times to best sent to the back issues folder on the kindle
|
# previous paid versions of the new york times to best sent to the back issues folder on the kindle
|
||||||
replaceKindleVersion = False
|
replaceKindleVersion = False
|
||||||
|
|
||||||
|
# download higher resolution images than the small thumbnails typically included in the article
|
||||||
|
# the down side of having large beautiful images is the file size is much larger, on the order of 7MB per paper
|
||||||
|
useHighResImages = True
|
||||||
|
|
||||||
# includeSections: List of sections to include. If empty, all sections found will be included.
|
# includeSections: List of sections to include. If empty, all sections found will be included.
|
||||||
# Otherwise, only the sections named will be included. For example,
|
# Otherwise, only the sections named will be included. For example,
|
||||||
#
|
#
|
||||||
@ -90,7 +93,6 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
(u'Sunday Magazine',u'magazine'),
|
(u'Sunday Magazine',u'magazine'),
|
||||||
(u'Week in Review',u'weekinreview')]
|
(u'Week in Review',u'weekinreview')]
|
||||||
|
|
||||||
|
|
||||||
if headlinesOnly:
|
if headlinesOnly:
|
||||||
title='New York Times Headlines'
|
title='New York Times Headlines'
|
||||||
description = 'Headlines from the New York Times'
|
description = 'Headlines from the New York Times'
|
||||||
@ -127,7 +129,7 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
|
|
||||||
earliest_date = date.today() - timedelta(days=oldest_article)
|
earliest_date = date.today() - timedelta(days=oldest_article)
|
||||||
|
|
||||||
__author__ = 'GRiker/Kovid Goyal/Nick Redding'
|
__author__ = 'GRiker/Kovid Goyal/Nick Redding/Ben Collier'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
requires_version = (0, 7, 5)
|
requires_version = (0, 7, 5)
|
||||||
|
|
||||||
@ -149,7 +151,7 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
'dottedLine',
|
'dottedLine',
|
||||||
'entry-meta',
|
'entry-meta',
|
||||||
'entry-response module',
|
'entry-response module',
|
||||||
'icon enlargeThis',
|
#'icon enlargeThis', #removed to provide option for high res images
|
||||||
'leftNavTabs',
|
'leftNavTabs',
|
||||||
'metaFootnote',
|
'metaFootnote',
|
||||||
'module box nav',
|
'module box nav',
|
||||||
@ -163,7 +165,23 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
'entry-tags', #added for DealBook
|
'entry-tags', #added for DealBook
|
||||||
'footer promos clearfix', #added for DealBook
|
'footer promos clearfix', #added for DealBook
|
||||||
'footer links clearfix', #added for DealBook
|
'footer links clearfix', #added for DealBook
|
||||||
'inlineImage module', #added for DealBook
|
'tabsContainer', #added for other blog downloads
|
||||||
|
'column lastColumn', #added for other blog downloads
|
||||||
|
'pageHeaderWithLabel', #added for other gadgetwise downloads
|
||||||
|
'column two', #added for other blog downloads
|
||||||
|
'column two last', #added for other blog downloads
|
||||||
|
'column three', #added for other blog downloads
|
||||||
|
'column three last', #added for other blog downloads
|
||||||
|
'column four',#added for other blog downloads
|
||||||
|
'column four last',#added for other blog downloads
|
||||||
|
'column last', #added for other blog downloads
|
||||||
|
'timestamp published', #added for other blog downloads
|
||||||
|
'entry entry-related',
|
||||||
|
'subNavigation tabContent active', #caucus blog navigation
|
||||||
|
'columnGroup doubleRule',
|
||||||
|
'mediaOverlay slideshow',
|
||||||
|
'headlinesOnly multiline flush',
|
||||||
|
'wideThumb',
|
||||||
re.compile('^subNavigation'),
|
re.compile('^subNavigation'),
|
||||||
re.compile('^leaderboard'),
|
re.compile('^leaderboard'),
|
||||||
re.compile('^module'),
|
re.compile('^module'),
|
||||||
@ -254,7 +272,7 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
def exclude_url(self,url):
|
def exclude_url(self,url):
|
||||||
if not url.startswith("http"):
|
if not url.startswith("http"):
|
||||||
return True
|
return True
|
||||||
if not url.endswith(".html") and 'dealbook.nytimes.com' not in url: #added for DealBook
|
if not url.endswith(".html") and 'dealbook.nytimes.com' not in url and 'blogs.nytimes.com' not in url: #added for DealBook
|
||||||
return True
|
return True
|
||||||
if 'nytimes.com' not in url:
|
if 'nytimes.com' not in url:
|
||||||
return True
|
return True
|
||||||
@ -592,19 +610,84 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
self.log("Skipping article dated %s" % date_str)
|
self.log("Skipping article dated %s" % date_str)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
kicker_tag = soup.find(attrs={'class':'kicker'})
|
#all articles are from today, no need to print the date on every page
|
||||||
if kicker_tag: # remove Op_Ed author head shots
|
try:
|
||||||
tagline = self.tag_to_string(kicker_tag)
|
if not self.webEdition:
|
||||||
if tagline=='Op-Ed Columnist':
|
date_tag = soup.find(True,attrs={'class': ['dateline','date']})
|
||||||
img_div = soup.find('div','inlineImage module')
|
if date_tag:
|
||||||
if img_div:
|
date_tag.extract()
|
||||||
img_div.extract()
|
except:
|
||||||
|
self.log("Error removing the published date")
|
||||||
|
|
||||||
|
if self.useHighResImages:
|
||||||
|
try:
|
||||||
|
#open up all the "Enlarge this Image" pop-ups and download the full resolution jpegs
|
||||||
|
enlargeThisList = soup.findAll('div',{'class':'icon enlargeThis'})
|
||||||
|
if enlargeThisList:
|
||||||
|
for popupref in enlargeThisList:
|
||||||
|
popupreflink = popupref.find('a')
|
||||||
|
if popupreflink:
|
||||||
|
reflinkstring = str(popupreflink['href'])
|
||||||
|
refstart = reflinkstring.find("javascript:pop_me_up2('") + len("javascript:pop_me_up2('")
|
||||||
|
refend = reflinkstring.find(".html", refstart) + len(".html")
|
||||||
|
reflinkstring = reflinkstring[refstart:refend]
|
||||||
|
|
||||||
|
popuppage = self.browser.open(reflinkstring)
|
||||||
|
popuphtml = popuppage.read()
|
||||||
|
popuppage.close()
|
||||||
|
if popuphtml:
|
||||||
|
st = time.localtime()
|
||||||
|
year = str(st.tm_year)
|
||||||
|
month = "%.2d" % st.tm_mon
|
||||||
|
day = "%.2d" % st.tm_mday
|
||||||
|
imgstartpos = popuphtml.find('http://graphics8.nytimes.com/images/' + year + '/' + month +'/' + day +'/') + len('http://graphics8.nytimes.com/images/' + year + '/' + month +'/' + day +'/')
|
||||||
|
highResImageLink = 'http://graphics8.nytimes.com/images/' + year + '/' + month +'/' + day +'/' + popuphtml[imgstartpos:popuphtml.find('.jpg',imgstartpos)+4]
|
||||||
|
popupSoup = BeautifulSoup(popuphtml)
|
||||||
|
highResTag = popupSoup.find('img', {'src':highResImageLink})
|
||||||
|
if highResTag:
|
||||||
|
try:
|
||||||
|
newWidth = highResTag['width']
|
||||||
|
newHeight = highResTag['height']
|
||||||
|
imageTag = popupref.parent.find("img")
|
||||||
|
except:
|
||||||
|
self.log("Error: finding width and height of img")
|
||||||
|
popupref.extract()
|
||||||
|
if imageTag:
|
||||||
|
try:
|
||||||
|
imageTag['src'] = highResImageLink
|
||||||
|
imageTag['width'] = newWidth
|
||||||
|
imageTag['height'] = newHeight
|
||||||
|
except:
|
||||||
|
self.log("Error setting the src width and height parameters")
|
||||||
|
except Exception:
|
||||||
|
self.log("Error pulling high resolution images")
|
||||||
|
|
||||||
|
try:
|
||||||
|
#remove "Related content" bar
|
||||||
|
runAroundsFound = soup.findAll('div',{'class':['articleInline runaroundLeft','articleInline doubleRule runaroundLeft','articleInline runaroundLeft firstArticleInline']})
|
||||||
|
if runAroundsFound:
|
||||||
|
for runAround in runAroundsFound:
|
||||||
|
#find all section headers
|
||||||
|
hlines = runAround.findAll(True ,{'class':['sectionHeader','sectionHeader flushBottom']})
|
||||||
|
if hlines:
|
||||||
|
for hline in hlines:
|
||||||
|
hline.extract()
|
||||||
|
except:
|
||||||
|
self.log("Error removing related content bar")
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
#in case pulling images failed, delete the enlarge this text
|
||||||
|
enlargeThisList = soup.findAll('div',{'class':'icon enlargeThis'})
|
||||||
|
if enlargeThisList:
|
||||||
|
for popupref in enlargeThisList:
|
||||||
|
popupref.extract()
|
||||||
|
except:
|
||||||
|
self.log("Error removing Enlarge this text")
|
||||||
|
|
||||||
return self.strip_anchors(soup)
|
return self.strip_anchors(soup)
|
||||||
|
|
||||||
def postprocess_html(self,soup, True):
|
def postprocess_html(self,soup, True):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.one_picture_per_article:
|
if self.one_picture_per_article:
|
||||||
# Remove all images after first
|
# Remove all images after first
|
||||||
@ -766,6 +849,8 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
try:
|
try:
|
||||||
if len(article.text_summary.strip()) == 0:
|
if len(article.text_summary.strip()) == 0:
|
||||||
articlebodies = soup.findAll('div',attrs={'class':'articleBody'})
|
articlebodies = soup.findAll('div',attrs={'class':'articleBody'})
|
||||||
|
if not articlebodies: #added to account for blog formats
|
||||||
|
articlebodies = soup.findAll('div', attrs={'class':'entry-content'}) #added to account for blog formats
|
||||||
if articlebodies:
|
if articlebodies:
|
||||||
for articlebody in articlebodies:
|
for articlebody in articlebodies:
|
||||||
if articlebody:
|
if articlebody:
|
||||||
@ -774,13 +859,14 @@ class NYTimes(BasicNewsRecipe):
|
|||||||
refparagraph = self.massageNCXText(self.tag_to_string(p,use_alt=False)).strip()
|
refparagraph = self.massageNCXText(self.tag_to_string(p,use_alt=False)).strip()
|
||||||
#account for blank paragraphs and short paragraphs by appending them to longer ones
|
#account for blank paragraphs and short paragraphs by appending them to longer ones
|
||||||
if len(refparagraph) > 0:
|
if len(refparagraph) > 0:
|
||||||
if len(refparagraph) > 70: #approximately one line of text
|
if len(refparagraph) > 140: #approximately two lines of text
|
||||||
article.summary = article.text_summary = shortparagraph + refparagraph
|
article.summary = article.text_summary = shortparagraph + refparagraph
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
shortparagraph = refparagraph + " "
|
shortparagraph = refparagraph + " "
|
||||||
if shortparagraph.strip().find(" ") == -1 and not shortparagraph.strip().endswith(":"):
|
if shortparagraph.strip().find(" ") == -1 and not shortparagraph.strip().endswith(":"):
|
||||||
shortparagraph = shortparagraph + "- "
|
shortparagraph = shortparagraph + "- "
|
||||||
|
|
||||||
except:
|
except:
|
||||||
self.log("Error creating article descriptions")
|
self.log("Error creating article descriptions")
|
||||||
return
|
return
|
||||||
|
33
resources/recipes/sinfest.recipe
Normal file
33
resources/recipes/sinfest.recipe
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Nadid <nadid.skywalker at gmail.com>'
|
||||||
|
'''
|
||||||
|
http://www.sinfest.net
|
||||||
|
'''
|
||||||
|
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class SinfestBig(BasicNewsRecipe):
|
||||||
|
title = 'Sinfest'
|
||||||
|
__author__ = 'nadid'
|
||||||
|
description = 'Sinfest'
|
||||||
|
reverse_article_order = False
|
||||||
|
oldest_article = 5
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
use_embedded_content = True
|
||||||
|
encoding = 'utf-8'
|
||||||
|
publisher = 'Tatsuya Ishida/Museworks'
|
||||||
|
category = 'comic'
|
||||||
|
language = 'en'
|
||||||
|
|
||||||
|
conversion_options = {
|
||||||
|
'comments' : description
|
||||||
|
,'tags' : category
|
||||||
|
,'language' : language
|
||||||
|
,'publisher' : publisher
|
||||||
|
}
|
||||||
|
|
||||||
|
feeds = [(u'SinFest', u'http://henrik.nyh.se/scrapers/sinfest.rss' )]
|
||||||
|
def get_article_url(self, article):
|
||||||
|
return article.get('link')
|
||||||
|
|
@ -22,7 +22,7 @@ class FOLDER_DEVICE_FOR_CONFIG(USBMS):
|
|||||||
PRODUCT_ID = [0xffff]
|
PRODUCT_ID = [0xffff]
|
||||||
BCD = [0xffff]
|
BCD = [0xffff]
|
||||||
DEVICE_PLUGBOARD_NAME = 'FOLDER_DEVICE'
|
DEVICE_PLUGBOARD_NAME = 'FOLDER_DEVICE'
|
||||||
|
SUPPORTS_SUB_DIRS = True
|
||||||
|
|
||||||
class FOLDER_DEVICE(USBMS):
|
class FOLDER_DEVICE(USBMS):
|
||||||
type = _('Device Interface')
|
type = _('Device Interface')
|
||||||
|
9
src/calibre/ebooks/metadata/sources/__init__.py
Normal file
9
src/calibre/ebooks/metadata/sources/__init__.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ from lxml import etree
|
|||||||
import cssutils
|
import cssutils
|
||||||
|
|
||||||
from calibre.ebooks.oeb.base import OPF1_NS, OPF2_NS, OPF2_NSMAP, DC11_NS, \
|
from calibre.ebooks.oeb.base import OPF1_NS, OPF2_NS, OPF2_NSMAP, DC11_NS, \
|
||||||
DC_NSES, OPF
|
DC_NSES, OPF, xml2text
|
||||||
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, OEB_IMAGES, \
|
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, OEB_IMAGES, \
|
||||||
PAGE_MAP_MIME, JPEG_MIME, NCX_MIME, SVG_MIME
|
PAGE_MAP_MIME, JPEG_MIME, NCX_MIME, SVG_MIME
|
||||||
from calibre.ebooks.oeb.base import XMLDECL_RE, COLLAPSE_RE, \
|
from calibre.ebooks.oeb.base import XMLDECL_RE, COLLAPSE_RE, \
|
||||||
@ -423,7 +423,7 @@ class OEBReader(object):
|
|||||||
path, frag = urldefrag(href)
|
path, frag = urldefrag(href)
|
||||||
if path not in self.oeb.manifest.hrefs:
|
if path not in self.oeb.manifest.hrefs:
|
||||||
continue
|
continue
|
||||||
title = ' '.join(xpath(anchor, './/text()'))
|
title = xml2text(anchor)
|
||||||
title = COLLAPSE_RE.sub(' ', title.strip())
|
title = COLLAPSE_RE.sub(' ', title.strip())
|
||||||
if href not in titles:
|
if href not in titles:
|
||||||
order.append(href)
|
order.append(href)
|
||||||
|
@ -550,6 +550,14 @@ def choose_dir(window, name, title, default_dir='~'):
|
|||||||
if dir:
|
if dir:
|
||||||
return dir[0]
|
return dir[0]
|
||||||
|
|
||||||
|
def choose_osx_app(window, name, title, default_dir='/Applications'):
|
||||||
|
fd = FileDialog(title=title, parent=window, name=name, mode=QFileDialog.ExistingFile,
|
||||||
|
default_dir=default_dir)
|
||||||
|
app = fd.get_files()
|
||||||
|
fd.setParent(None)
|
||||||
|
if app:
|
||||||
|
return app
|
||||||
|
|
||||||
def choose_files(window, name, title,
|
def choose_files(window, name, title,
|
||||||
filters=[], all_files=True, select_only_single_file=False):
|
filters=[], all_files=True, select_only_single_file=False):
|
||||||
'''
|
'''
|
||||||
|
@ -9,7 +9,7 @@ import os, datetime
|
|||||||
|
|
||||||
from PyQt4.Qt import pyqtSignal, QModelIndex, QThread, Qt
|
from PyQt4.Qt import pyqtSignal, QModelIndex, QThread, Qt
|
||||||
|
|
||||||
from calibre.gui2 import error_dialog, gprefs
|
from calibre.gui2 import error_dialog
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, NavigableString
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, NavigableString
|
||||||
from calibre import strftime
|
from calibre import strftime
|
||||||
from calibre.gui2.actions import InterfaceAction
|
from calibre.gui2.actions import InterfaceAction
|
||||||
@ -165,10 +165,12 @@ class FetchAnnotationsAction(InterfaceAction):
|
|||||||
ka_soup.insert(0,divTag)
|
ka_soup.insert(0,divTag)
|
||||||
return ka_soup
|
return ka_soup
|
||||||
|
|
||||||
|
'''
|
||||||
def mark_book_as_read(self,id):
|
def mark_book_as_read(self,id):
|
||||||
read_tag = gprefs.get('catalog_epub_mobi_read_tag')
|
read_tag = gprefs.get('catalog_epub_mobi_read_tag')
|
||||||
if read_tag:
|
if read_tag:
|
||||||
self.db.set_tags(id, [read_tag], append=True)
|
self.db.set_tags(id, [read_tag], append=True)
|
||||||
|
'''
|
||||||
|
|
||||||
def canceled(self):
|
def canceled(self):
|
||||||
self.pd.hide()
|
self.pd.hide()
|
||||||
@ -201,10 +203,12 @@ class FetchAnnotationsAction(InterfaceAction):
|
|||||||
# Update library comments
|
# Update library comments
|
||||||
self.db.set_comment(id, mi.comments)
|
self.db.set_comment(id, mi.comments)
|
||||||
|
|
||||||
|
'''
|
||||||
# Update 'read' tag except for Catalogs/Clippings
|
# Update 'read' tag except for Catalogs/Clippings
|
||||||
if bm.value.percent_read >= self.FINISHED_READING_PCT_THRESHOLD:
|
if bm.value.percent_read >= self.FINISHED_READING_PCT_THRESHOLD:
|
||||||
if not set(mi.tags).intersection(ignore_tags):
|
if not set(mi.tags).intersection(ignore_tags):
|
||||||
self.mark_book_as_read(id)
|
self.mark_book_as_read(id)
|
||||||
|
'''
|
||||||
|
|
||||||
# Add bookmark file to id
|
# Add bookmark file to id
|
||||||
self.db.add_format_with_hooks(id, bm.value.bookmark_extension,
|
self.db.add_format_with_hooks(id, bm.value.bookmark_extension,
|
||||||
|
@ -390,7 +390,7 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
#self.dbref = weakref.ref(self.gui.library_view.model().db)
|
#self.dbref = weakref.ref(self.gui.library_view.model().db)
|
||||||
#self.before_mem = memory()/1024**2
|
#self.before_mem = memory()/1024**2
|
||||||
self.gui.library_moved(loc)
|
self.gui.library_moved(loc)
|
||||||
#QTimer.singleShot(1000, self.debug_leak)
|
#QTimer.singleShot(5000, self.debug_leak)
|
||||||
|
|
||||||
def debug_leak(self):
|
def debug_leak(self):
|
||||||
import gc
|
import gc
|
||||||
@ -398,7 +398,7 @@ class ChooseLibraryAction(InterfaceAction):
|
|||||||
ref = self.dbref
|
ref = self.dbref
|
||||||
for i in xrange(3): gc.collect()
|
for i in xrange(3): gc.collect()
|
||||||
if ref() is not None:
|
if ref() is not None:
|
||||||
print 11111, ref()
|
print 'DB object alive:', ref()
|
||||||
for r in gc.get_referrers(ref())[:10]:
|
for r in gc.get_referrers(ref())[:10]:
|
||||||
print r
|
print r
|
||||||
print
|
print
|
||||||
|
@ -335,7 +335,7 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
return
|
return
|
||||||
|
'''
|
||||||
if new_state == 0:
|
if new_state == 0:
|
||||||
# unchecked
|
# unchecked
|
||||||
self.merge_source_field.setEnabled(False)
|
self.merge_source_field.setEnabled(False)
|
||||||
@ -348,6 +348,7 @@ class PluginWidget(QWidget,Ui_Form):
|
|||||||
self.merge_before.setEnabled(True)
|
self.merge_before.setEnabled(True)
|
||||||
self.merge_after.setEnabled(True)
|
self.merge_after.setEnabled(True)
|
||||||
self.include_hr.setEnabled(True)
|
self.include_hr.setEnabled(True)
|
||||||
|
'''
|
||||||
|
|
||||||
def header_note_source_field_changed(self,new_index):
|
def header_note_source_field_changed(self,new_index):
|
||||||
'''
|
'''
|
||||||
|
@ -42,9 +42,15 @@ class SearchAndReplaceWidget(Widget, Ui_Form):
|
|||||||
def break_cycles(self):
|
def break_cycles(self):
|
||||||
Widget.break_cycles(self)
|
Widget.break_cycles(self)
|
||||||
|
|
||||||
self.opt_sr1_search.doc_update.disconnect()
|
def d(x):
|
||||||
self.opt_sr2_search.doc_update.disconnect()
|
try:
|
||||||
self.opt_sr3_search.doc_update.disconnect()
|
x.disconnect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
d(self.opt_sr1_search)
|
||||||
|
d(self.opt_sr2_search)
|
||||||
|
d(self.opt_sr3_search)
|
||||||
|
|
||||||
self.opt_sr1_search.break_cycles()
|
self.opt_sr1_search.break_cycles()
|
||||||
self.opt_sr2_search.break_cycles()
|
self.opt_sr2_search.break_cycles()
|
||||||
|
@ -250,22 +250,27 @@ class Scheduler(QObject):
|
|||||||
|
|
||||||
self.timer = QTimer(self)
|
self.timer = QTimer(self)
|
||||||
self.timer.start(int(self.INTERVAL * 60 * 1000))
|
self.timer.start(int(self.INTERVAL * 60 * 1000))
|
||||||
self.oldest_timer = QTimer()
|
|
||||||
self.connect(self.oldest_timer, SIGNAL('timeout()'), self.oldest_check)
|
|
||||||
self.connect(self.timer, SIGNAL('timeout()'), self.check)
|
self.connect(self.timer, SIGNAL('timeout()'), self.check)
|
||||||
self.oldest = gconf['oldest_news']
|
self.oldest = gconf['oldest_news']
|
||||||
self.oldest_timer.start(int(60 * 60 * 1000))
|
|
||||||
QTimer.singleShot(5 * 1000, self.oldest_check)
|
QTimer.singleShot(5 * 1000, self.oldest_check)
|
||||||
self.database_changed = self.recipe_model.database_changed
|
self.database_changed = self.recipe_model.database_changed
|
||||||
|
|
||||||
def oldest_check(self):
|
def oldest_check(self):
|
||||||
if self.oldest > 0:
|
if self.oldest > 0:
|
||||||
delta = timedelta(days=self.oldest)
|
delta = timedelta(days=self.oldest)
|
||||||
ids = self.recipe_model.db.tags_older_than(_('News'), delta)
|
try:
|
||||||
|
ids = self.recipe_model.db.tags_older_than(_('News'), delta)
|
||||||
|
except:
|
||||||
|
# Should never happen
|
||||||
|
ids = []
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
if ids:
|
if ids:
|
||||||
ids = list(ids)
|
ids = list(ids)
|
||||||
if ids:
|
if ids:
|
||||||
self.delete_old_news.emit(ids)
|
self.delete_old_news.emit(ids)
|
||||||
|
QTimer.singleShot(60 * 60 * 1000, self.oldest_check)
|
||||||
|
|
||||||
|
|
||||||
def show_dialog(self, *args):
|
def show_dialog(self, *args):
|
||||||
self.lock.lock()
|
self.lock.lock()
|
||||||
|
@ -98,6 +98,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
self.collapse_model = 'disable'
|
self.collapse_model = 'disable'
|
||||||
else:
|
else:
|
||||||
self.collapse_model = gprefs['tags_browser_partition_method']
|
self.collapse_model = gprefs['tags_browser_partition_method']
|
||||||
|
self.search_icon = QIcon(I('search.png'))
|
||||||
|
|
||||||
def set_pane_is_visible(self, to_what):
|
def set_pane_is_visible(self, to_what):
|
||||||
pv = self.pane_is_visible
|
pv = self.pane_is_visible
|
||||||
@ -199,6 +200,10 @@ class TagsView(QTreeView): # {{{
|
|||||||
if action == 'manage_categories':
|
if action == 'manage_categories':
|
||||||
self.user_category_edit.emit(category)
|
self.user_category_edit.emit(category)
|
||||||
return
|
return
|
||||||
|
if action == 'search':
|
||||||
|
self.tags_marked.emit(('not ' if negate else '') +
|
||||||
|
category + ':"=' + key + '"')
|
||||||
|
return
|
||||||
if action == 'search_category':
|
if action == 'search_category':
|
||||||
self.tags_marked.emit(category + ':' + str(not negate))
|
self.tags_marked.emit(category + ':' + str(not negate))
|
||||||
return
|
return
|
||||||
@ -208,6 +213,7 @@ class TagsView(QTreeView): # {{{
|
|||||||
if action == 'edit_author_sort':
|
if action == 'edit_author_sort':
|
||||||
self.author_sort_edit.emit(self, index)
|
self.author_sort_edit.emit(self, index)
|
||||||
return
|
return
|
||||||
|
|
||||||
if action == 'hide':
|
if action == 'hide':
|
||||||
self.hidden_categories.add(category)
|
self.hidden_categories.add(category)
|
||||||
elif action == 'show':
|
elif action == 'show':
|
||||||
@ -248,19 +254,36 @@ class TagsView(QTreeView): # {{{
|
|||||||
if key not in self.db.field_metadata:
|
if key not in self.db.field_metadata:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# If the user right-clicked on an editable item, then offer
|
# Did the user click on a leaf node?
|
||||||
# the possibility of renaming that item
|
if tag_name:
|
||||||
if tag_name and \
|
# If the user right-clicked on an editable item, then offer
|
||||||
(key in ['authors', 'tags', 'series', 'publisher', 'search'] or \
|
# the possibility of renaming that item.
|
||||||
self.db.field_metadata[key]['is_custom'] and \
|
if key in ['authors', 'tags', 'series', 'publisher', 'search'] or \
|
||||||
self.db.field_metadata[key]['datatype'] != 'rating'):
|
(self.db.field_metadata[key]['is_custom'] and \
|
||||||
self.context_menu.addAction(_('Rename \'%s\'')%tag_name,
|
self.db.field_metadata[key]['datatype'] != 'rating'):
|
||||||
partial(self.context_menu_handler, action='edit_item',
|
# Add the 'rename' items
|
||||||
category=tag_item, index=index))
|
self.context_menu.addAction(_('Rename %s')%tag_name,
|
||||||
if key == 'authors':
|
partial(self.context_menu_handler, action='edit_item',
|
||||||
self.context_menu.addAction(_('Edit sort for \'%s\'')%tag_name,
|
category=tag_item, index=index))
|
||||||
partial(self.context_menu_handler,
|
if key == 'authors':
|
||||||
action='edit_author_sort', index=tag_id))
|
self.context_menu.addAction(_('Edit sort for %s')%tag_name,
|
||||||
|
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))
|
||||||
|
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))
|
||||||
self.context_menu.addSeparator()
|
self.context_menu.addSeparator()
|
||||||
# Hide/Show/Restore categories
|
# Hide/Show/Restore categories
|
||||||
self.context_menu.addAction(_('Hide category %s') % category,
|
self.context_menu.addAction(_('Hide category %s') % category,
|
||||||
@ -272,14 +295,15 @@ class TagsView(QTreeView): # {{{
|
|||||||
partial(self.context_menu_handler, action='show', category=col))
|
partial(self.context_menu_handler, action='show', category=col))
|
||||||
|
|
||||||
# search by category
|
# search by category
|
||||||
self.context_menu.addAction(
|
if key != 'search':
|
||||||
_('Search for books in category %s')%category,
|
self.context_menu.addAction(self.search_icon,
|
||||||
partial(self.context_menu_handler, action='search_category',
|
_('Search for books in category %s')%category,
|
||||||
category=key, negate=False))
|
partial(self.context_menu_handler, action='search_category',
|
||||||
self.context_menu.addAction(
|
category=key, negate=False))
|
||||||
_('Search for books not in category %s')%category,
|
self.context_menu.addAction(self.search_icon,
|
||||||
partial(self.context_menu_handler, action='search_category',
|
_('Search for books not in category %s')%category,
|
||||||
category=key, negate=True))
|
partial(self.context_menu_handler, action='search_category',
|
||||||
|
category=key, negate=True))
|
||||||
# Offer specific editors for tags/series/publishers/saved searches
|
# Offer specific editors for tags/series/publishers/saved searches
|
||||||
self.context_menu.addSeparator()
|
self.context_menu.addSeparator()
|
||||||
if key in ['tags', 'publisher', 'series'] or \
|
if key in ['tags', 'publisher', 'series'] or \
|
||||||
@ -703,7 +727,11 @@ class TagsModel(QAbstractItemModel): # {{{
|
|||||||
for user_cat in sorted(self.db.prefs.get('user_categories', {}).keys(),
|
for user_cat in sorted(self.db.prefs.get('user_categories', {}).keys(),
|
||||||
key=sort_key):
|
key=sort_key):
|
||||||
cat_name = '@' + user_cat # add the '@' to avoid name collision
|
cat_name = '@' + user_cat # add the '@' to avoid name collision
|
||||||
tb_cats.add_user_category(label=cat_name, name=user_cat)
|
try:
|
||||||
|
tb_cats.add_user_category(label=cat_name, name=user_cat)
|
||||||
|
except ValueError:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
if len(saved_searches().names()):
|
if len(saved_searches().names()):
|
||||||
tb_cats.add_search_category(label='search', name=_('Searches'))
|
tb_cats.add_search_category(label='search', name=_('Searches'))
|
||||||
|
|
||||||
|
@ -638,8 +638,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
if mb is not None:
|
|
||||||
mb.flush()
|
|
||||||
self.hide_windows()
|
self.hide_windows()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -42,6 +42,8 @@ class MetadataBackup(Thread): # {{{
|
|||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.keep_running = False
|
self.keep_running = False
|
||||||
|
|
||||||
|
def break_cycles(self):
|
||||||
# Break cycles so that this object doesn't hold references to db
|
# Break cycles so that this object doesn't hold references to db
|
||||||
self.do_write = self.get_metadata_for_dump = self.clear_dirtied = \
|
self.do_write = self.get_metadata_for_dump = self.clear_dirtied = \
|
||||||
self.set_dirtied = self.db = None
|
self.set_dirtied = self.db = None
|
||||||
@ -57,7 +59,10 @@ class MetadataBackup(Thread): # {{{
|
|||||||
except:
|
except:
|
||||||
# Happens during interpreter shutdown
|
# Happens during interpreter shutdown
|
||||||
break
|
break
|
||||||
|
if not self.keep_running:
|
||||||
|
break
|
||||||
|
|
||||||
|
self.in_limbo = id_
|
||||||
try:
|
try:
|
||||||
path, mi = self.get_metadata_for_dump(id_)
|
path, mi = self.get_metadata_for_dump(id_)
|
||||||
except:
|
except:
|
||||||
@ -72,10 +77,10 @@ class MetadataBackup(Thread): # {{{
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# at this point the dirty indication is off
|
# at this point the dirty indication is off
|
||||||
|
|
||||||
if mi is None:
|
if mi is None:
|
||||||
continue
|
continue
|
||||||
self.in_limbo = id_
|
if not self.keep_running:
|
||||||
|
break
|
||||||
|
|
||||||
# Give the GUI thread a chance to do something. Python threads don't
|
# Give the GUI thread a chance to do something. Python threads don't
|
||||||
# have priorities, so this thread would naturally keep the processor
|
# have priorities, so this thread would naturally keep the processor
|
||||||
@ -89,6 +94,9 @@ class MetadataBackup(Thread): # {{{
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if not self.keep_running:
|
||||||
|
break
|
||||||
|
|
||||||
time.sleep(0.1) # Give the GUI thread a chance to do something
|
time.sleep(0.1) # Give the GUI thread a chance to do something
|
||||||
try:
|
try:
|
||||||
self.do_write(path, raw)
|
self.do_write(path, raw)
|
||||||
@ -102,7 +110,10 @@ class MetadataBackup(Thread): # {{{
|
|||||||
prints('Failed to write backup metadata for id:', id_,
|
prints('Failed to write backup metadata for id:', id_,
|
||||||
'again, giving up')
|
'again, giving up')
|
||||||
continue
|
continue
|
||||||
self.in_limbo = None
|
|
||||||
|
self.in_limbo = None
|
||||||
|
self.flush()
|
||||||
|
self.break_cycles()
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
'Used during shutdown to ensure that a dirtied book is not missed'
|
'Used during shutdown to ensure that a dirtied book is not missed'
|
||||||
@ -111,6 +122,7 @@ class MetadataBackup(Thread): # {{{
|
|||||||
self.db.dirtied([self.in_limbo])
|
self.db.dirtied([self.in_limbo])
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
self.in_limbo = None
|
||||||
|
|
||||||
def write(self, path, raw):
|
def write(self, path, raw):
|
||||||
with lopen(path, 'wb') as f:
|
with lopen(path, 'wb') as f:
|
||||||
|
@ -1820,6 +1820,9 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
self.booksByTitle_noSeriesPrefix = nspt
|
self.booksByTitle_noSeriesPrefix = nspt
|
||||||
|
|
||||||
# Loop through the books by title
|
# Loop through the books by title
|
||||||
|
# Generate one divRunningTag per initial letter for the purposes of
|
||||||
|
# minimizing widows and orphans on readers that can handle large
|
||||||
|
# <divs> styled as inline-block
|
||||||
title_list = self.booksByTitle
|
title_list = self.booksByTitle
|
||||||
if not self.useSeriesPrefixInTitlesSection:
|
if not self.useSeriesPrefixInTitlesSection:
|
||||||
title_list = self.booksByTitle_noSeriesPrefix
|
title_list = self.booksByTitle_noSeriesPrefix
|
||||||
@ -1832,7 +1835,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
divTag.insert(dtc, divRunningTag)
|
divTag.insert(dtc, divRunningTag)
|
||||||
dtc += 1
|
dtc += 1
|
||||||
divRunningTag = Tag(soup, 'div')
|
divRunningTag = Tag(soup, 'div')
|
||||||
divRunningTag['style'] = 'display:inline-block;width:100%'
|
divRunningTag['class'] = "logical_group"
|
||||||
drtc = 0
|
drtc = 0
|
||||||
current_letter = self.letter_or_symbol(book['title_sort'][0])
|
current_letter = self.letter_or_symbol(book['title_sort'][0])
|
||||||
pIndexTag = Tag(soup, "p")
|
pIndexTag = Tag(soup, "p")
|
||||||
@ -1954,6 +1957,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
drtc = 0
|
drtc = 0
|
||||||
|
|
||||||
# Loop through booksByAuthor
|
# Loop through booksByAuthor
|
||||||
|
# Each author/books group goes in an openingTag div (first) or
|
||||||
|
# a runningTag div (subsequent)
|
||||||
book_count = 0
|
book_count = 0
|
||||||
current_author = ''
|
current_author = ''
|
||||||
current_letter = ''
|
current_letter = ''
|
||||||
@ -1977,7 +1982,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
current_letter = self.letter_or_symbol(book['author_sort'][0].upper())
|
current_letter = self.letter_or_symbol(book['author_sort'][0].upper())
|
||||||
author_count = 0
|
author_count = 0
|
||||||
divOpeningTag = Tag(soup, 'div')
|
divOpeningTag = Tag(soup, 'div')
|
||||||
divOpeningTag['style'] = 'display:inline-block;width:100%'
|
divOpeningTag['class'] = "logical_group"
|
||||||
dotc = 0
|
dotc = 0
|
||||||
pIndexTag = Tag(soup, "p")
|
pIndexTag = Tag(soup, "p")
|
||||||
pIndexTag['class'] = "letter_index"
|
pIndexTag['class'] = "letter_index"
|
||||||
@ -2001,7 +2006,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
|
|||||||
|
|
||||||
# Create a divRunningTag for the rest of the authors in this letter
|
# Create a divRunningTag for the rest of the authors in this letter
|
||||||
divRunningTag = Tag(soup, 'div')
|
divRunningTag = Tag(soup, 'div')
|
||||||
divRunningTag['style'] = 'display:inline-block;width:100%'
|
divRunningTag['class'] = "logical_group"
|
||||||
drtc = 0
|
drtc = 0
|
||||||
|
|
||||||
non_series_books = 0
|
non_series_books = 0
|
||||||
|
@ -1376,10 +1376,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
def tags_older_than(self, tag, delta):
|
def tags_older_than(self, tag, delta):
|
||||||
tag = tag.lower().strip()
|
tag = tag.lower().strip()
|
||||||
now = nowf()
|
now = nowf()
|
||||||
|
tindex = self.FIELD_MAP['timestamp']
|
||||||
|
gindex = self.FIELD_MAP['tags']
|
||||||
for r in self.data._data:
|
for r in self.data._data:
|
||||||
if r is not None:
|
if r is not None:
|
||||||
if (now - r[self.FIELD_MAP['timestamp']]) > delta:
|
if (now - r[tindex]) > delta:
|
||||||
tags = r[self.FIELD_MAP['tags']]
|
tags = r[gindex]
|
||||||
if tags and tag in [x.strip() for x in
|
if tags and tag in [x.strip() for x in
|
||||||
tags.lower().split(',')]:
|
tags.lower().split(',')]:
|
||||||
yield r[self.FIELD_MAP['id']]
|
yield r[self.FIELD_MAP['id']]
|
||||||
|
@ -477,6 +477,8 @@ class FieldMetadata(dict):
|
|||||||
del self._tb_cats[key]
|
del self._tb_cats[key]
|
||||||
if key in self._search_term_map:
|
if key in self._search_term_map:
|
||||||
del self._search_term_map[key]
|
del self._search_term_map[key]
|
||||||
|
if key.lower() in self._search_term_map:
|
||||||
|
del self._search_term_map[key.lower()]
|
||||||
|
|
||||||
def cc_series_index_column_for(self, key):
|
def cc_series_index_column_for(self, key):
|
||||||
return self._tb_cats[key]['rec_index'] + 1
|
return self._tb_cats[key]['rec_index'] + 1
|
||||||
|
@ -310,7 +310,9 @@ What formats does |app| read metadata from?
|
|||||||
|
|
||||||
Where are the book files stored?
|
Where are the book files stored?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
When you first run |app|, it will ask you for a folder in which to store your books. Whenever you add a book to |app|, it will copy the book into that folder. Books in the folder are nicely arranged into sub-folders by Author and Title. Metadata about the books is stored in the file ``metadata.db`` (which is a sqlite database).
|
When you first run |app|, it will ask you for a folder in which to store your books. Whenever you add a book to |app|, it will copy the book into that folder. Books in the folder are nicely arranged into sub-folders by Author and Title. Note that the contents of this folder are automatically managed by |app|, **do not** add any files/folders manually to this folder, as they may be automatically deleted. If you want to add a file associated to a particular book, use the top right area of :guilabel:`Edit metadata` dialog to do so. Then, |app| will automatically put that file into the correct folder and move it around when the title/author changes.
|
||||||
|
|
||||||
|
Metadata about the books is stored in the file ``metadata.db`` at the top level of the library folder This file is is a sqlite database. When backing up your library make sure you copy the entire folder and all its sub-folders.
|
||||||
|
|
||||||
Why doesn't |app| let me store books in my own directory structure?
|
Why doesn't |app| let me store books in my own directory structure?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Loading…
x
Reference in New Issue
Block a user