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
49f02ae092
@ -4,6 +4,81 @@
|
|||||||
# for important features/bug fixes.
|
# for important features/bug fixes.
|
||||||
# Also, each release can have new and improved recipes.
|
# Also, each release can have new and improved recipes.
|
||||||
|
|
||||||
|
- version: 0.7.19
|
||||||
|
date: 2010-09-17
|
||||||
|
|
||||||
|
new features:
|
||||||
|
- title: "The ability to perform search and replace via regular expressions in the Bulk Edit metadata dialog"
|
||||||
|
type: major
|
||||||
|
|
||||||
|
- title: "Add an option to have calibre automatically convert straight quotes to curly quotes. Also handles em/en-dashes and ellipses. Found under 'Look & Feel' in the conversion options"
|
||||||
|
type: major
|
||||||
|
tickets: [6808]
|
||||||
|
|
||||||
|
- title: "Greatly improve sorting performance on large libraries."
|
||||||
|
type: major
|
||||||
|
|
||||||
|
- title: "Drivers for the SONY PRS-350/PRS-650 and the Sovos E-reader"
|
||||||
|
|
||||||
|
- title: "Kobo driver: Add management of the I'm Reading list on Kobo via an Im_Reading tag in calibre. See http://www.mobileread.com/forums/showthread.php?t=98906 for details"
|
||||||
|
|
||||||
|
- title: "Conversion pipeline: Add an option to control how hard line breaks are removed during preprocessing. See the Structure Detection section in the conversion options"
|
||||||
|
|
||||||
|
- title: "In the Edit metadata dialog, indicate whether the author sort value matches the author value, using a background color"
|
||||||
|
|
||||||
|
- title: "Add an option to split the toolbar into two toolbars in Preferences->Interface->Look and Feel"
|
||||||
|
|
||||||
|
- title: "EPUB Output: Improved design of the 'jacket' page created by calibre when using the 'Insert metadata at start of book' option"
|
||||||
|
|
||||||
|
- title: "PDF Input: Improve line unwrapping, handling of hyphens/dashes and quotes. Also handle more specially encoded non ASCII characters"
|
||||||
|
|
||||||
|
bug fixes:
|
||||||
|
- title: "Fix regression in filename shortening that caused loss of filename extension"
|
||||||
|
|
||||||
|
- title: "Fix various regressions that could be triggered when using search restrictions and/or multi-sorts and connecting a device"
|
||||||
|
|
||||||
|
- title: "Database: Fix possible race condition in windows when changing title/author during move of book files, that could lead to old files not being deleted"
|
||||||
|
|
||||||
|
- title: "Conversion pipeline: Don't die if rescaling of image raises an exception, just ignore and continue"
|
||||||
|
|
||||||
|
- title: "Database: Update has_cover cache when setting/removing covers so that the search returns correct results. Also fix an exception that could occur when adding books with a db that has been upgraded from very old SQL."
|
||||||
|
|
||||||
|
- title: "Workaround for bug that affects some windows installs causing white backgrounds on default covers to be rendered as yellow"
|
||||||
|
|
||||||
|
- title: "Fix handling of non-ASCII chars when rendering series in default EPUB cover"
|
||||||
|
|
||||||
|
- title: "Fix --start-in-tray switch displays hidden windows in metacity, xfwm4 and compiz"
|
||||||
|
tickets: [6806]
|
||||||
|
|
||||||
|
- title: "Conversion pipeline: When setting margins on <body> explicitly set padding to 0 to override and existing padding in the input document"
|
||||||
|
|
||||||
|
- title: "CHM Input: Ignore missing image files in the input document"
|
||||||
|
tickets: [6773]
|
||||||
|
|
||||||
|
- title: "News download: Fix bug that could break some downloads in non ASCII locales"
|
||||||
|
|
||||||
|
- title: "TXT Output: When using preserve spaces, output tab characters as a sequence of four non-breaking spaces as some readers dont handle the 09 char code."
|
||||||
|
|
||||||
|
- title: "PDB Input: Fix bug in conversion of TOC in some PML files"
|
||||||
|
|
||||||
|
new recipes:
|
||||||
|
- title: "taz.de RSS"
|
||||||
|
author: Alexander Schremmer
|
||||||
|
|
||||||
|
- title: "Brand Eins"
|
||||||
|
author: Constantin Hofstetter
|
||||||
|
|
||||||
|
improved recipes:
|
||||||
|
- Harpers (free)
|
||||||
|
- Danas
|
||||||
|
- Novosti
|
||||||
|
- ESPN
|
||||||
|
- Taz Digiabo
|
||||||
|
- Slate
|
||||||
|
- AJC
|
||||||
|
- Infobae
|
||||||
|
- NSPM
|
||||||
|
|
||||||
- version: 0.7.18
|
- version: 0.7.18
|
||||||
date: 2010-09-10
|
date: 2010-09-10
|
||||||
|
|
||||||
|
BIN
resources/images/news/popscience.png
Normal file
BIN
resources/images/news/popscience.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 737 B |
@ -27,10 +27,19 @@ class Danas(BasicNewsRecipe):
|
|||||||
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||||
.article_description,body,.lokacija{font-family: Tahoma,Arial,Helvetica,sans1,sans-serif}
|
.article_description,body,.lokacija{font-family: Tahoma,Arial,Helvetica,sans1,sans-serif}
|
||||||
.nadNaslov,h1,.preamble{font-family: Georgia,"Times New Roman",Times,serif1,serif}
|
.nadNaslov,h1,.preamble{font-family: Georgia,"Times New Roman",Times,serif1,serif}
|
||||||
.antrfileText{border-left: 2px solid #999999; margin-left: 0.8em; padding-left: 1.2em;
|
.antrfileText{border-left: 2px solid #999999;
|
||||||
margin-bottom: 0; margin-top: 0} h2,.datum,.lokacija,.autor{font-size: small}
|
margin-left: 0.8em;
|
||||||
.antrfileNaslov{border-left: 2px solid #999999; margin-left: 0.8em; padding-left: 1.2em;
|
padding-left: 1.2em;
|
||||||
font-weight:bold; margin-bottom: 0; margin-top: 0} img{margin-bottom: 0.8em}
|
margin-bottom: 0;
|
||||||
|
margin-top: 0}
|
||||||
|
h2,.datum,.lokacija,.autor{font-size: small}
|
||||||
|
.antrfileNaslov{border-left: 2px solid #999999;
|
||||||
|
margin-left: 0.8em;
|
||||||
|
padding-left: 1.2em;
|
||||||
|
font-weight:bold;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 0}
|
||||||
|
img{margin-bottom: 0.8em}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
conversion_options = {
|
conversion_options = {
|
||||||
@ -40,18 +49,7 @@ class Danas(BasicNewsRecipe):
|
|||||||
, 'language' : language
|
, 'language' : language
|
||||||
}
|
}
|
||||||
|
|
||||||
preprocess_regexps = [
|
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||||
(re.compile(u'\u0110'), lambda match: u'\u00D0')
|
|
||||||
,(re.compile(r'<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags".*?/>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
|
||||||
,(re.compile(r'<st1:place.*?>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
|
||||||
,(re.compile(r'</st1:place>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
|
||||||
,(re.compile(r'<st1:city.*?>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
|
||||||
,(re.compile(r'</st1:city>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
|
||||||
,(re.compile(r'<st1:country-region.*?>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
|
||||||
,(re.compile(r'</st1:country-region>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
|
||||||
,(re.compile(r'<st1:state.*?>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
|
||||||
,(re.compile(r'</st1:state>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
|
||||||
]
|
|
||||||
|
|
||||||
keep_only_tags = [dict(name='div', attrs={'id':'left'})]
|
keep_only_tags = [dict(name='div', attrs={'id':'left'})]
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
@ -59,7 +57,7 @@ class Danas(BasicNewsRecipe):
|
|||||||
,dict(name='div', attrs={'id':'comments'})
|
,dict(name='div', attrs={'id':'comments'})
|
||||||
,dict(name=['object','link','iframe','meta'])
|
,dict(name=['object','link','iframe','meta'])
|
||||||
]
|
]
|
||||||
remove_attributes = ['st']
|
remove_attributes = ['w:st','st']
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
(u'Politika' , u'http://www.danas.rs/rss/rss.asp?column_id=27')
|
(u'Politika' , u'http://www.danas.rs/rss/rss.asp?column_id=27')
|
||||||
@ -87,10 +85,13 @@ class Danas(BasicNewsRecipe):
|
|||||||
,(u'Zvaka u pepeljari' , u'http://www.danas.rs/rss/rss.asp?column_id=56')
|
,(u'Zvaka u pepeljari' , u'http://www.danas.rs/rss/rss.asp?column_id=56')
|
||||||
,(u'Vostani Serbie' , u'http://www.danas.rs/rss/rss.asp?column_id=57')
|
,(u'Vostani Serbie' , u'http://www.danas.rs/rss/rss.asp?column_id=57')
|
||||||
,(u'Med&Jad-a' , u'http://www.danas.rs/rss/rss.asp?column_id=58')
|
,(u'Med&Jad-a' , u'http://www.danas.rs/rss/rss.asp?column_id=58')
|
||||||
,(u'Svetlosti pozornice' , u'http://www.danas.rs/rss/rss.asp?column_id=59')
|
,(u'Svetlosti pozornice' , u'http://www.danas.rs/rss/rss.asp?column_id=59')
|
||||||
]
|
]
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
|
for tagn in ['st1:place','st1:city','st1:country-region','st1:state']:
|
||||||
|
for item in soup.body.findAll(tagn):
|
||||||
|
item.name='span'
|
||||||
for item in soup.findAll(style=True):
|
for item in soup.findAll(style=True):
|
||||||
del item['style']
|
del item['style']
|
||||||
for item in soup.findAll('a'):
|
for item in soup.findAll('a'):
|
||||||
@ -98,7 +99,7 @@ class Danas(BasicNewsRecipe):
|
|||||||
item.extract()
|
item.extract()
|
||||||
for item in soup.findAll('img'):
|
for item in soup.findAll('img'):
|
||||||
if not item.has_key('alt'):
|
if not item.has_key('alt'):
|
||||||
item['alt'] = 'image'
|
item['alt'] = 'image'
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
|
@ -8,6 +8,7 @@ espn.com
|
|||||||
'''
|
'''
|
||||||
import re
|
import re
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from calibre.ptempfile import TemporaryFile
|
||||||
|
|
||||||
class ESPN(BasicNewsRecipe):
|
class ESPN(BasicNewsRecipe):
|
||||||
|
|
||||||
@ -78,12 +79,19 @@ class ESPN(BasicNewsRecipe):
|
|||||||
def get_browser(self):
|
def get_browser(self):
|
||||||
br = BasicNewsRecipe.get_browser()
|
br = BasicNewsRecipe.get_browser()
|
||||||
br.set_handle_refresh(False)
|
br.set_handle_refresh(False)
|
||||||
if self.username is not None and self.password is not None:
|
url = ('https://r.espn.go.com/members/v3_1/login')
|
||||||
br.open('http://espn.com')#('http://espn.go.com/#myespn')
|
raw = br.open(url).read()
|
||||||
br.select_form(nr=1)
|
raw = re.sub(r'(?s)<form>.*?id="regsigninbtn".*?</form>', '', raw)
|
||||||
br.form.find_control(name='username', type='text').value = self.username
|
with TemporaryFile(suffix='.htm') as fname:
|
||||||
br.form['password'] = self.password
|
with open(fname, 'wb') as f:
|
||||||
br.submit()
|
f.write(raw)
|
||||||
|
br.open_local_file(fname)
|
||||||
|
|
||||||
|
br.form = br.forms().next()
|
||||||
|
br.form.find_control(name='username', type='text').value = self.username
|
||||||
|
br.form['password'] = self.password
|
||||||
|
br.submit().read()
|
||||||
|
br.open('http://espn.go.com').read()
|
||||||
br.set_handle_refresh(True)
|
br.set_handle_refresh(True)
|
||||||
return br
|
return br
|
||||||
|
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
|
__copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||||
'''
|
'''
|
||||||
harpers.org
|
harpers.org
|
||||||
'''
|
'''
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
from calibre.ebooks.BeautifulSoup import Tag
|
|
||||||
|
|
||||||
class Harpers(BasicNewsRecipe):
|
class Harpers(BasicNewsRecipe):
|
||||||
title = u"Harper's Magazine"
|
title = u"Harper's Magazine"
|
||||||
__author__ = u'Darko Miletic'
|
__author__ = u'Darko Miletic'
|
||||||
language = 'en'
|
language = 'en'
|
||||||
|
|
||||||
description = u"Harper's Magazine: Founded June 1850."
|
description = u"Harper's Magazine: Founded June 1850."
|
||||||
publisher = "Harper's Magazine "
|
publisher = "Harper's Magazine "
|
||||||
category = 'news, politics, USA'
|
category = 'news, politics, USA'
|
||||||
@ -21,26 +17,26 @@ class Harpers(BasicNewsRecipe):
|
|||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
use_embedded_content = False
|
use_embedded_content = False
|
||||||
|
|
||||||
html2lrf_options = [
|
conversion_options = {
|
||||||
'--comment', description
|
'comment' : description
|
||||||
, '--category', category
|
, 'tags' : category
|
||||||
, '--publisher', publisher
|
, 'publisher' : publisher
|
||||||
]
|
, 'language' : language
|
||||||
|
}
|
||||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\noverride_css=" p {text-indent: 0em; margin-top: 0em; margin-bottom: 0.5em} img {margin-top: 0em; margin-bottom: 0.4em}"'
|
|
||||||
|
|
||||||
extra_css = '''
|
extra_css = '''
|
||||||
h1{ font-family:georgia ; color:#111111; font-size:large;}
|
h1{ font-family:georgia ; color:#111111; font-size:large;}
|
||||||
.box-of-helpful{ font-family:arial ; font-size:x-small;}
|
.box-of-helpful{ font-family:arial ; font-size:x-small;}
|
||||||
p{font-family:georgia ;}
|
p{font-family:georgia ;}
|
||||||
.caption{font-family:Verdana,sans-serif;font-size:x-small;color:#666666;}
|
.caption{font-family:Verdana,sans-serif;font-size:x-small;color:#666666;}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
keep_only_tags = [ dict(name='div', attrs={'id':'cached'}) ]
|
keep_only_tags = [ dict(name='div', attrs={'id':'cached'}) ]
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name='table', attrs={'class':['rcnt','rcnt topline']})
|
dict(name='table', attrs={'class':['rcnt','rcnt topline']})
|
||||||
,dict(name=['link','object','embed'])
|
,dict(name=['link','object','embed','meta','base'])
|
||||||
]
|
]
|
||||||
|
remove_attributes = ['width','height']
|
||||||
|
|
||||||
feeds = [(u"Harper's Magazine", u'http://www.harpers.org/rss/frontpage-rss20.xml')]
|
feeds = [(u"Harper's Magazine", u'http://www.harpers.org/rss/frontpage-rss20.xml')]
|
||||||
|
|
||||||
@ -49,20 +45,13 @@ class Harpers(BasicNewsRecipe):
|
|||||||
index = 'http://harpers.org/'
|
index = 'http://harpers.org/'
|
||||||
soup = self.index_to_soup(index)
|
soup = self.index_to_soup(index)
|
||||||
link_item = soup.find(name = 'img',attrs= {'class':"cover"})
|
link_item = soup.find(name = 'img',attrs= {'class':"cover"})
|
||||||
print link_item
|
|
||||||
if link_item:
|
if link_item:
|
||||||
cover_url = 'http://harpers.org' + link_item['src']
|
cover_url = 'http://harpers.org' + link_item['src']
|
||||||
print cover_url
|
|
||||||
return cover_url
|
return cover_url
|
||||||
|
|
||||||
def preprocess_html(self, soup):
|
def preprocess_html(self, soup):
|
||||||
mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=utf-8")])
|
|
||||||
soup.head.insert(1,mcharset)
|
|
||||||
for item in soup.findAll(style=True):
|
for item in soup.findAll(style=True):
|
||||||
del item['style']
|
del item['style']
|
||||||
for item in soup.findAll(xmlns=True):
|
for item in soup.findAll(xmlns=True):
|
||||||
del item['xmlns']
|
del item['xmlns']
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,9 +33,9 @@ class HBR(BasicNewsRecipe):
|
|||||||
def get_browser(self):
|
def get_browser(self):
|
||||||
br = BasicNewsRecipe.get_browser(self)
|
br = BasicNewsRecipe.get_browser(self)
|
||||||
br.open(self.LOGIN_URL)
|
br.open(self.LOGIN_URL)
|
||||||
br.select_form(name='signInForm')
|
br.select_form(name='signin-form')
|
||||||
br['signInForm:username'] = self.username
|
br['signin-form:username'] = self.username
|
||||||
br['signInForm:password'] = self.password
|
br['signin-form:password'] = self.password
|
||||||
raw = br.submit().read()
|
raw = br.submit().read()
|
||||||
if 'My Account' not in raw:
|
if 'My Account' not in raw:
|
||||||
raise Exception('Failed to login, are you sure your username and password are correct?')
|
raise Exception('Failed to login, are you sure your username and password are correct?')
|
||||||
|
@ -37,6 +37,16 @@ class Novosti(BasicNewsRecipe):
|
|||||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||||
|
|
||||||
keep_only_tags = [dict(attrs={'class':['articleTitle','author','articleLead','articleBody']})]
|
keep_only_tags = [dict(attrs={'class':['articleTitle','author','articleLead','articleBody']})]
|
||||||
remove_tags = [dict(name=['embed','object','iframe','base'])]
|
remove_tags = [dict(name=['embed','object','iframe','base','link','meta'])]
|
||||||
|
|
||||||
feeds = [(u'Vesti', u'http://www.novosti.rs/rss/rss-vesti')]
|
feeds = [(u'Vesti', u'http://www.novosti.rs/rss/rss-vesti')]
|
||||||
|
|
||||||
|
def preprocess_html(self, soup):
|
||||||
|
for item in soup.findAll(style=True):
|
||||||
|
del item['style']
|
||||||
|
for item in soup.findAll('span', attrs={'class':'author'}):
|
||||||
|
item.name='p'
|
||||||
|
for item in soup.findAll('img'):
|
||||||
|
if not item.has_key('alt'):
|
||||||
|
item['alt'] = 'image'
|
||||||
|
return soup
|
||||||
|
|
59
resources/recipes/popscience.recipe
Normal file
59
resources/recipes/popscience.recipe
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import re
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
|
class AdvancedUserRecipe1282101454(BasicNewsRecipe):
|
||||||
|
title = 'Popular Science'
|
||||||
|
language = 'en'
|
||||||
|
__author__ = 'TonytheBookworm'
|
||||||
|
description = 'Popular Science'
|
||||||
|
publisher = 'Popular Science'
|
||||||
|
category = 'gadgets,science'
|
||||||
|
oldest_article = 7 # change this if you want more current articles. I like to go a week in
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
no_stylesheets = True
|
||||||
|
remove_javascript = True
|
||||||
|
|
||||||
|
masthead_url = 'http://www.raytheon.com/newsroom/rtnwcm/groups/Public/documents/masthead/rtn08_popscidec_masthead.jpg'
|
||||||
|
|
||||||
|
remove_tags = [dict(name='div', attrs={'id':['toolbar','main_supplements']}),
|
||||||
|
dict(name='span', attrs={'class':['comments']}),
|
||||||
|
dict(name='div', attrs={'class':['relatedinfo related-right','node_navigation','content2']}),
|
||||||
|
dict(name='ul', attrs={'class':['item-list clear-block']})]
|
||||||
|
feeds = [
|
||||||
|
|
||||||
|
('Gadgets', 'http://www.popsci.com/full-feed/gadgets'),
|
||||||
|
('Cars', 'http://www.popsci.com/full-feed/cars'),
|
||||||
|
('Science', 'http://www.popsci.com/full-feed/science'),
|
||||||
|
('Technology', 'http://www.popsci.com/full-feed/technology'),
|
||||||
|
('DIY', 'http://www.popsci.com/full-feed/diy'),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
#The following will get read of the Gallery: links when found
|
||||||
|
|
||||||
|
def preprocess_html(self, soup) :
|
||||||
|
print 'SOUP IS: ', soup
|
||||||
|
weblinks = soup.findAll(['head','h2'])
|
||||||
|
if weblinks is not None:
|
||||||
|
for link in weblinks:
|
||||||
|
if re.search('(Gallery)(:)',str(link)):
|
||||||
|
|
||||||
|
link.parent.extract()
|
||||||
|
return soup
|
||||||
|
#-----------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -8,8 +8,9 @@ __docformat__ = 'restructuredtext de'
|
|||||||
'''
|
'''
|
||||||
www.taz.de/digiabo
|
www.taz.de/digiabo
|
||||||
'''
|
'''
|
||||||
import os, urllib2, zipfile, tempfile
|
import os, urllib2, zipfile
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
|
|
||||||
class TazDigiabo(BasicNewsRecipe):
|
class TazDigiabo(BasicNewsRecipe):
|
||||||
|
|
||||||
@ -26,38 +27,39 @@ class TazDigiabo(BasicNewsRecipe):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def build_index(self):
|
def build_index(self):
|
||||||
if self.username is not None and self.password is not None:
|
domain = "http://www.taz.de"
|
||||||
domain = "http://www.taz.de"
|
|
||||||
|
|
||||||
url = domain + "/epub/"
|
url = domain + "/epub/"
|
||||||
|
|
||||||
auth_handler = urllib2.HTTPBasicAuthHandler()
|
auth_handler = urllib2.HTTPBasicAuthHandler()
|
||||||
auth_handler.add_password(realm='TAZ-ABO',
|
auth_handler.add_password(realm='TAZ-ABO',
|
||||||
uri=url,
|
uri=url,
|
||||||
user=self.username,
|
user=self.username,
|
||||||
passwd=self.password)
|
passwd=self.password)
|
||||||
opener = urllib2.build_opener(auth_handler)
|
opener = urllib2.build_opener(auth_handler)
|
||||||
urllib2.install_opener(opener)
|
urllib2.install_opener(opener)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
f = urllib2.urlopen(url)
|
f = urllib2.urlopen(url)
|
||||||
except urllib2.HTTPError:
|
except urllib2.HTTPError:
|
||||||
self.report_progress(0,_('Can\'t login to download issue'))
|
self.report_progress(0,_('Can\'t login to download issue'))
|
||||||
raise ValueError('Failed to login, check your username and'
|
raise ValueError('Failed to login, check your username and'
|
||||||
' password')
|
' password')
|
||||||
|
|
||||||
tmp = tempfile.TemporaryFile()
|
tmp = PersistentTemporaryFile(suffix='.epub')
|
||||||
self.report_progress(0,_('downloading epub'))
|
self.report_progress(0,_('downloading epub'))
|
||||||
tmp.write(f.read())
|
tmp.write(f.read())
|
||||||
|
tmp.close()
|
||||||
|
|
||||||
zfile = zipfile.ZipFile(tmp, 'r')
|
zfile = zipfile.ZipFile(tmp.name, 'r')
|
||||||
self.report_progress(0,_('extracting epub'))
|
self.report_progress(0,_('extracting epub'))
|
||||||
|
|
||||||
zfile.extractall(self.output_dir)
|
zfile.extractall(self.output_dir)
|
||||||
|
|
||||||
tmp.close()
|
tmp.close()
|
||||||
index = os.path.join(self.output_dir, 'content.opf')
|
index = os.path.join(self.output_dir, 'content.opf')
|
||||||
|
|
||||||
self.report_progress(1,_('epub downloaded and extracted'))
|
self.report_progress(1,_('epub downloaded and extracted'))
|
||||||
|
|
||||||
|
return index
|
||||||
|
|
||||||
return index
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2010, Alexander Schremmer <alex@alexanderweb.de>'
|
__copyright__ = '2010, Alexander Schremmer <alex@alexanderweb.de>'
|
||||||
|
|
||||||
from calibre.resources.recipes import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class TazRSSRecipe(BasicNewsRecipe):
|
class TazRSSRecipe(BasicNewsRecipe):
|
||||||
title = u'Taz.de (die tageszeitung) RSS Feed - German'
|
title = u'Taz.de (die tageszeitung) RSS Feed - German'
|
||||||
|
@ -197,7 +197,7 @@ class GetTranslations(Translations):
|
|||||||
class ISO639(Command):
|
class ISO639(Command):
|
||||||
|
|
||||||
description = 'Compile translations for ISO 639 codes'
|
description = 'Compile translations for ISO 639 codes'
|
||||||
XML = '/usr/lib/python2.6/site-packages/pycountry/databases/iso639.xml'
|
XML = '/usr/lib/python2.7/site-packages/pycountry/databases/iso639.xml'
|
||||||
|
|
||||||
def run(self, opts):
|
def run(self, opts):
|
||||||
src = self.XML
|
src = self.XML
|
||||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__appname__ = 'calibre'
|
__appname__ = 'calibre'
|
||||||
__version__ = '0.7.18'
|
__version__ = '0.7.19'
|
||||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
@ -424,7 +424,7 @@ class KOBO(USBMS):
|
|||||||
paths[source_id] = os.path.join(prefix, *(path.split('/')))
|
paths[source_id] = os.path.join(prefix, *(path.split('/')))
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
def update_device_database_collections(self, booklists, collections_attributes):
|
def update_device_database_collections(self, booklists, collections_attributes, oncard):
|
||||||
# debug_print('Starting update_device_database_collections', collections_attributes)
|
# debug_print('Starting update_device_database_collections', collections_attributes)
|
||||||
|
|
||||||
# Force collections_attributes to be 'tags' as no other is currently supported
|
# Force collections_attributes to be 'tags' as no other is currently supported
|
||||||
@ -433,23 +433,31 @@ class KOBO(USBMS):
|
|||||||
|
|
||||||
collections = booklists.get_collections(collections_attributes)
|
collections = booklists.get_collections(collections_attributes)
|
||||||
# debug_print('Collections', collections)
|
# debug_print('Collections', collections)
|
||||||
|
|
||||||
|
# Create a connection to the sqlite database
|
||||||
|
# Needs to be outside books collection as in the case of removing
|
||||||
|
# the last book from the collection the list of books is empty
|
||||||
|
# and the removal of the last book would not occur
|
||||||
|
connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite')
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
# Reset Im_Reading list in the database
|
||||||
|
if oncard == 'carda':
|
||||||
|
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID like \'file:///mnt/sd/%\''
|
||||||
|
elif oncard != 'carda' and oncard != 'cardb':
|
||||||
|
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID not like \'file:///mnt/sd/%\''
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor.execute (query)
|
||||||
|
except:
|
||||||
|
debug_print('Database Exception: Unable to reset Im_Reading list')
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
# debug_print('Commit: Reset Im_Reading list')
|
||||||
|
connection.commit()
|
||||||
|
|
||||||
for category, books in collections.items():
|
for category, books in collections.items():
|
||||||
if category == 'Im_Reading':
|
if category == 'Im_Reading':
|
||||||
# Create a connection to the sqlite database
|
|
||||||
connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite')
|
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
# Reset Im_Reading list in the database
|
|
||||||
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null'
|
|
||||||
try:
|
|
||||||
cursor.execute (query)
|
|
||||||
except:
|
|
||||||
debug_print('Database Exception: Unable to reset Im_Reading list')
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
# debug_print('Commit: Reset Im_Reading list')
|
|
||||||
connection.commit()
|
|
||||||
|
|
||||||
for book in books:
|
for book in books:
|
||||||
# debug_print('Title:', book.title, 'lpath:', book.path)
|
# debug_print('Title:', book.title, 'lpath:', book.path)
|
||||||
book.device_collections = ['Im_Reading']
|
book.device_collections = ['Im_Reading']
|
||||||
@ -471,8 +479,8 @@ class KOBO(USBMS):
|
|||||||
connection.commit()
|
connection.commit()
|
||||||
# debug_print('Database: Commit create Im_Reading list')
|
# debug_print('Database: Commit create Im_Reading list')
|
||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
connection.close()
|
connection.close()
|
||||||
|
|
||||||
# debug_print('Finished update_device_database_collections', collections_attributes)
|
# debug_print('Finished update_device_database_collections', collections_attributes)
|
||||||
|
|
||||||
@ -494,12 +502,16 @@ class KOBO(USBMS):
|
|||||||
|
|
||||||
#debug_print('KOBO: collection fields:', collections)
|
#debug_print('KOBO: collection fields:', collections)
|
||||||
for i, blist in blists.items():
|
for i, blist in blists.items():
|
||||||
self.update_device_database_collections(blist, collections)
|
if i == 0:
|
||||||
|
oncard = 'main'
|
||||||
|
else:
|
||||||
|
oncard = 'carda'
|
||||||
|
self.update_device_database_collections(blist, collections, oncard)
|
||||||
|
|
||||||
USBMS.sync_booklists(self, booklists, end_session=end_session)
|
USBMS.sync_booklists(self, booklists, end_session=end_session)
|
||||||
#debug_print('KOBO: finished sync_booklists')
|
#debug_print('KOBO: finished sync_booklists')
|
||||||
|
|
||||||
def rebuild_collections(self, booklist, oncard):
|
def rebuild_collections(self, booklist, oncard):
|
||||||
collections_attributes = []
|
collections_attributes = []
|
||||||
self.update_device_database_collections(booklist, collections_attributes)
|
self.update_device_database_collections(booklist, collections_attributes, oncard)
|
||||||
|
|
||||||
|
@ -260,8 +260,8 @@ class HTMLPreProcessor(object):
|
|||||||
(re.compile(u'¸\s*(<br.*?>)*\s*C', re.UNICODE), lambda match: u'Ç'),
|
(re.compile(u'¸\s*(<br.*?>)*\s*C', re.UNICODE), lambda match: u'Ç'),
|
||||||
|
|
||||||
# ˛
|
# ˛
|
||||||
(re.compile(u'˛\s*(<br.*?>)*\s*a', re.UNICODE), lambda match: u'ą'),
|
(re.compile(u'\s*˛\s*(<br.*?>)*\s*a', re.UNICODE), lambda match: u'ą'),
|
||||||
(re.compile(u'˛\s*(<br.*?>)*\s*A', re.UNICODE), lambda match: u'Ą'),
|
(re.compile(u'\s*˛\s*(<br.*?>)*\s*A', re.UNICODE), lambda match: u'Ą'),
|
||||||
(re.compile(u'˛\s*(<br.*?>)*\s*e', re.UNICODE), lambda match: u'ę'),
|
(re.compile(u'˛\s*(<br.*?>)*\s*e', re.UNICODE), lambda match: u'ę'),
|
||||||
(re.compile(u'˛\s*(<br.*?>)*\s*E', re.UNICODE), lambda match: u'Ę'),
|
(re.compile(u'˛\s*(<br.*?>)*\s*E', re.UNICODE), lambda match: u'Ę'),
|
||||||
|
|
||||||
|
@ -176,6 +176,7 @@ def get_metadata(stream, extract_cover=True):
|
|||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
mi.timestamp = None
|
||||||
return mi
|
return mi
|
||||||
|
|
||||||
def get_quick_metadata(stream):
|
def get_quick_metadata(stream):
|
||||||
|
@ -173,6 +173,8 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
'''
|
'''
|
||||||
rows = [r.row() for r in \
|
rows = [r.row() for r in \
|
||||||
self.gui.library_view.selectionModel().selectedRows()]
|
self.gui.library_view.selectionModel().selectedRows()]
|
||||||
|
m = self.gui.library_view.model()
|
||||||
|
ids = [m.id(r) for r in rows]
|
||||||
if not rows or len(rows) == 0:
|
if not rows or len(rows) == 0:
|
||||||
d = error_dialog(self.gui, _('Cannot edit metadata'),
|
d = error_dialog(self.gui, _('Cannot edit metadata'),
|
||||||
_('No books selected'))
|
_('No books selected'))
|
||||||
@ -191,6 +193,7 @@ class EditMetadataAction(InterfaceAction):
|
|||||||
self.gui.tags_view.recount()
|
self.gui.tags_view.recount()
|
||||||
if self.gui.cover_flow:
|
if self.gui.cover_flow:
|
||||||
self.gui.cover_flow.dataChanged()
|
self.gui.cover_flow.dataChanged()
|
||||||
|
self.gui.library_view.select_rows(ids)
|
||||||
|
|
||||||
# Merge books {{{
|
# Merge books {{{
|
||||||
def merge_books(self, safe_merge=False):
|
def merge_books(self, safe_merge=False):
|
||||||
|
@ -707,6 +707,10 @@ class DeviceMixin(object): # {{{
|
|||||||
'''
|
'''
|
||||||
Called when a device is connected to the computer.
|
Called when a device is connected to the computer.
|
||||||
'''
|
'''
|
||||||
|
# This can happen as this function is called in a queued connection and
|
||||||
|
# the user could have yanked the device in the meantime
|
||||||
|
if connected and not self.device_manager.is_device_connected:
|
||||||
|
connected = False
|
||||||
self.set_device_menu_items_state(connected)
|
self.set_device_menu_items_state(connected)
|
||||||
if connected:
|
if connected:
|
||||||
self.device_manager.get_device_information(\
|
self.device_manager.get_device_information(\
|
||||||
|
@ -4,8 +4,10 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
'''Dialog to edit metadata in bulk'''
|
'''Dialog to edit metadata in bulk'''
|
||||||
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
import re
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog, QGridLayout
|
from PyQt4.Qt import QDialog, QGridLayout
|
||||||
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
||||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||||
@ -83,7 +85,6 @@ class Worker(Thread):
|
|||||||
w.commit(self.ids)
|
w.commit(self.ids)
|
||||||
self.db.bulk_modify_tags(self.ids, add=add, remove=remove,
|
self.db.bulk_modify_tags(self.ids, add=add, remove=remove,
|
||||||
notify=False)
|
notify=False)
|
||||||
self.db.clean()
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
@ -101,6 +102,13 @@ class Worker(Thread):
|
|||||||
|
|
||||||
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
||||||
|
|
||||||
|
s_r_functions = {
|
||||||
|
'' : lambda x: x,
|
||||||
|
_('Lower Case') : lambda x: x.lower(),
|
||||||
|
_('Upper Case') : lambda x: x.upper(),
|
||||||
|
_('Title Case') : lambda x: x.title(),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, window, rows, db):
|
def __init__(self, window, rows, db):
|
||||||
QDialog.__init__(self, window)
|
QDialog.__init__(self, window)
|
||||||
Ui_MetadataBulkDialog.__init__(self)
|
Ui_MetadataBulkDialog.__init__(self)
|
||||||
@ -127,12 +135,193 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
self.series.currentIndexChanged[int].connect(self.series_changed)
|
self.series.currentIndexChanged[int].connect(self.series_changed)
|
||||||
self.series.editTextChanged.connect(self.series_changed)
|
self.series.editTextChanged.connect(self.series_changed)
|
||||||
self.tag_editor_button.clicked.connect(self.tag_editor)
|
self.tag_editor_button.clicked.connect(self.tag_editor)
|
||||||
|
|
||||||
if len(db.custom_column_label_map) == 0:
|
if len(db.custom_column_label_map) == 0:
|
||||||
self.central_widget.tabBar().setVisible(False)
|
self.central_widget.removeTab(1)
|
||||||
else:
|
else:
|
||||||
self.create_custom_column_editors()
|
self.create_custom_column_editors()
|
||||||
|
|
||||||
|
self.prepare_search_and_replace()
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
|
def prepare_search_and_replace(self):
|
||||||
|
self.search_for.initialize('bulk_edit_search_for')
|
||||||
|
self.replace_with.initialize('bulk_edit_replace_with')
|
||||||
|
self.test_text.initialize('bulk_edit_test_test')
|
||||||
|
fields = ['']
|
||||||
|
fm = self.db.field_metadata
|
||||||
|
for f in fm:
|
||||||
|
if (f in ['author_sort'] or (
|
||||||
|
fm[f]['datatype'] == 'text' or fm[f]['datatype'] == 'series')
|
||||||
|
and fm[f].get('search_terms', None)
|
||||||
|
and f not in ['formats', 'ondevice']):
|
||||||
|
fields.append(f)
|
||||||
|
fields.sort()
|
||||||
|
self.search_field.addItems(fields)
|
||||||
|
self.search_field.setMaxVisibleItems(min(len(fields), 20))
|
||||||
|
offset = 10
|
||||||
|
self.s_r_number_of_books = min(7, len(self.ids))
|
||||||
|
for i in range(1,self.s_r_number_of_books+1):
|
||||||
|
w = QtGui.QLabel(self.tabWidgetPage3)
|
||||||
|
w.setText(_('Book %d:')%i)
|
||||||
|
self.gridLayout1.addWidget(w, i+offset, 0, 1, 1)
|
||||||
|
w = QtGui.QLineEdit(self.tabWidgetPage3)
|
||||||
|
w.setReadOnly(True)
|
||||||
|
name = 'book_%d_text'%i
|
||||||
|
setattr(self, name, w)
|
||||||
|
self.book_1_text.setObjectName(name)
|
||||||
|
self.gridLayout1.addWidget(w, i+offset, 1, 1, 1)
|
||||||
|
w = QtGui.QLineEdit(self.tabWidgetPage3)
|
||||||
|
w.setReadOnly(True)
|
||||||
|
name = 'book_%d_result'%i
|
||||||
|
setattr(self, name, w)
|
||||||
|
self.book_1_text.setObjectName(name)
|
||||||
|
self.gridLayout1.addWidget(w, i+offset, 2, 1, 1)
|
||||||
|
|
||||||
|
self.s_r_heading.setText('<p>'+
|
||||||
|
_('Search and replace in text fields using '
|
||||||
|
'regular expressions. The search text is an '
|
||||||
|
'arbitrary python-compatible regular expression. '
|
||||||
|
'The replacement text can contain backreferences '
|
||||||
|
'to parenthesized expressions in the pattern. '
|
||||||
|
'The search is not anchored, and can match and '
|
||||||
|
'replace multiple times on the same string. See '
|
||||||
|
'<a href="http://docs.python.org/library/re.html"> '
|
||||||
|
'this reference</a> '
|
||||||
|
'for more information, and in particular the \'sub\' '
|
||||||
|
'function.') + '<p>' + _(
|
||||||
|
'Note: <b>you can destroy your library</b> '
|
||||||
|
'using this feature. Changes are permanent. There '
|
||||||
|
'is no undo function. You are strongly encouraged '
|
||||||
|
'to back up your library before proceeding.'))
|
||||||
|
self.s_r_error = None
|
||||||
|
self.s_r_obj = None
|
||||||
|
|
||||||
|
self.replace_func.addItems(sorted(self.s_r_functions.keys()))
|
||||||
|
self.search_field.currentIndexChanged[str].connect(self.s_r_field_changed)
|
||||||
|
self.replace_func.currentIndexChanged[str].connect(self.s_r_paint_results)
|
||||||
|
self.search_for.editTextChanged[str].connect(self.s_r_paint_results)
|
||||||
|
self.replace_with.editTextChanged[str].connect(self.s_r_paint_results)
|
||||||
|
self.test_text.editTextChanged[str].connect(self.s_r_paint_results)
|
||||||
|
self.central_widget.setCurrentIndex(0)
|
||||||
|
|
||||||
|
def s_r_field_changed(self, txt):
|
||||||
|
txt = unicode(txt)
|
||||||
|
for i in range(0, self.s_r_number_of_books):
|
||||||
|
if txt:
|
||||||
|
fm = self.db.field_metadata[txt]
|
||||||
|
id = self.ids[i]
|
||||||
|
val = self.db.get_property(id, index_is_id=True,
|
||||||
|
loc=fm['rec_index'])
|
||||||
|
if val is None:
|
||||||
|
val = ''
|
||||||
|
if fm['is_multiple']:
|
||||||
|
val = [t.strip() for t in val.split(fm['is_multiple']) if t.strip()]
|
||||||
|
if val:
|
||||||
|
val.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()))
|
||||||
|
val = val[0]
|
||||||
|
if txt == 'authors':
|
||||||
|
val = val.replace('|', ',')
|
||||||
|
else:
|
||||||
|
val = ''
|
||||||
|
else:
|
||||||
|
val = ''
|
||||||
|
w = getattr(self, 'book_%d_text'%(i+1))
|
||||||
|
w.setText(val)
|
||||||
|
self.s_r_paint_results(None)
|
||||||
|
|
||||||
|
def s_r_set_colors(self):
|
||||||
|
if self.s_r_error is not None:
|
||||||
|
col = 'rgb(255, 0, 0, 20%)'
|
||||||
|
self.test_result.setText(self.s_r_error.message)
|
||||||
|
else:
|
||||||
|
col = 'rgb(0, 255, 0, 20%)'
|
||||||
|
self.test_result.setStyleSheet('QLineEdit { color: black; '
|
||||||
|
'background-color: %s; }'%col)
|
||||||
|
for i in range(0,self.s_r_number_of_books):
|
||||||
|
getattr(self, 'book_%d_result'%(i+1)).setText('')
|
||||||
|
|
||||||
|
def s_r_func(self, match):
|
||||||
|
rf = self.s_r_functions[unicode(self.replace_func.currentText())]
|
||||||
|
rv = unicode(self.replace_with.text())
|
||||||
|
val = match.expand(rv)
|
||||||
|
return rf(val)
|
||||||
|
|
||||||
|
def s_r_paint_results(self, txt):
|
||||||
|
self.s_r_error = None
|
||||||
|
self.s_r_set_colors()
|
||||||
|
try:
|
||||||
|
self.s_r_obj = re.compile(unicode(self.search_for.text()))
|
||||||
|
except re.error as e:
|
||||||
|
self.s_r_obj = None
|
||||||
|
self.s_r_error = e
|
||||||
|
self.s_r_set_colors()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.test_result.setText(self.s_r_obj.sub(self.s_r_func,
|
||||||
|
unicode(self.test_text.text())))
|
||||||
|
except re.error as e:
|
||||||
|
self.s_r_error = e
|
||||||
|
self.s_r_set_colors()
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in range(0,self.s_r_number_of_books):
|
||||||
|
wt = getattr(self, 'book_%d_text'%(i+1))
|
||||||
|
wr = getattr(self, 'book_%d_result'%(i+1))
|
||||||
|
try:
|
||||||
|
wr.setText(self.s_r_obj.sub(self.s_r_func, unicode(wt.text())))
|
||||||
|
except re.error as e:
|
||||||
|
self.s_r_error = e
|
||||||
|
self.s_r_set_colors()
|
||||||
|
break
|
||||||
|
|
||||||
|
def do_search_replace(self):
|
||||||
|
field = unicode(self.search_field.currentText())
|
||||||
|
if not field or not self.s_r_obj:
|
||||||
|
return
|
||||||
|
|
||||||
|
fm = self.db.field_metadata[field]
|
||||||
|
|
||||||
|
def apply_pattern(val):
|
||||||
|
try:
|
||||||
|
return self.s_r_obj.sub(self.s_r_func, val)
|
||||||
|
except:
|
||||||
|
return val
|
||||||
|
|
||||||
|
for id in self.ids:
|
||||||
|
val = self.db.get_property(id, index_is_id=True,
|
||||||
|
loc=fm['rec_index'])
|
||||||
|
if val is None:
|
||||||
|
continue
|
||||||
|
if fm['is_multiple']:
|
||||||
|
res = []
|
||||||
|
for val in [t.strip() for t in val.split(fm['is_multiple'])]:
|
||||||
|
v = apply_pattern(val).strip()
|
||||||
|
if v:
|
||||||
|
res.append(v)
|
||||||
|
val = res
|
||||||
|
if fm['is_custom']:
|
||||||
|
# The standard tags and authors values want to be lists.
|
||||||
|
# All custom columns are to be strings
|
||||||
|
val = fm['is_multiple'].join(val)
|
||||||
|
elif field == 'authors':
|
||||||
|
val = [v.replace('|', ',') for v in val]
|
||||||
|
else:
|
||||||
|
val = apply_pattern(val)
|
||||||
|
|
||||||
|
if fm['is_custom']:
|
||||||
|
extra = self.db.get_custom_extra(id, label=fm['label'], index_is_id=True)
|
||||||
|
self.db.set_custom(id, val, label=fm['label'], extra=extra,
|
||||||
|
commit=False)
|
||||||
|
else:
|
||||||
|
if field == 'comments':
|
||||||
|
setter = self.db.set_comment
|
||||||
|
else:
|
||||||
|
setter = getattr(self.db, 'set_'+field)
|
||||||
|
setter(id, val, notify=False, commit=False)
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
def create_custom_column_editors(self):
|
def create_custom_column_editors(self):
|
||||||
w = self.central_widget.widget(1)
|
w = self.central_widget.widget(1)
|
||||||
layout = QGridLayout()
|
layout = QGridLayout()
|
||||||
@ -193,6 +382,11 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
if len(self.ids) < 1:
|
if len(self.ids) < 1:
|
||||||
return QDialog.accept(self)
|
return QDialog.accept(self)
|
||||||
|
|
||||||
|
if self.s_r_error is not None:
|
||||||
|
error_dialog(self, _('Search/replace invalid'),
|
||||||
|
_('Search pattern is invalid: %s')%self.s_r_error.message,
|
||||||
|
show=True)
|
||||||
|
return False
|
||||||
self.changed = bool(self.ids)
|
self.changed = bool(self.ids)
|
||||||
# Cache values from GUI so that Qt widgets are not used in
|
# Cache values from GUI so that Qt widgets are not used in
|
||||||
# non GUI thread
|
# non GUI thread
|
||||||
@ -234,6 +428,10 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
|||||||
return error_dialog(self, _('Failed'),
|
return error_dialog(self, _('Failed'),
|
||||||
self.worker.error[0], det_msg=self.worker.error[1],
|
self.worker.error[0], det_msg=self.worker.error[1],
|
||||||
show=True)
|
show=True)
|
||||||
|
|
||||||
|
self.do_search_replace()
|
||||||
|
|
||||||
|
self.db.clean()
|
||||||
return QDialog.accept(self)
|
return QDialog.accept(self)
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>526</width>
|
<width>679</width>
|
||||||
<height>499</height>
|
<height>685</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -200,14 +200,15 @@
|
|||||||
</item>
|
</item>
|
||||||
<item row="6" column="2">
|
<item row="6" column="2">
|
||||||
<widget class="QCheckBox" name="remove_all_tags">
|
<widget class="QCheckBox" name="remove_all_tags">
|
||||||
<property name="text">
|
|
||||||
<string>Remove all</string>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Check this box to remove all tags from the books.</string>
|
<string>Check this box to remove all tags from the books.</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Remove all</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item><item row="7" column="0">
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
<widget class="QLabel" name="label_7">
|
<widget class="QLabel" name="label_7">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Series:</string>
|
<string>&Series:</string>
|
||||||
@ -294,6 +295,19 @@ Future conversion of these books will use the default settings.</string>
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="12" column="0" colspan="3">
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="tab">
|
<widget class="QWidget" name="tab">
|
||||||
@ -301,6 +315,131 @@ Future conversion of these books will use the default settings.</string>
|
|||||||
<string>&Custom metadata</string>
|
<string>&Custom metadata</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QWidget" name="tabWidgetPage3">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>&Search and replace (experimental)</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetMinimumSize</enum>
|
||||||
|
</property>
|
||||||
|
<item row="1" column="0" colspan="3">
|
||||||
|
<widget class="QLabel" name="s_r_heading">
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="filler">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="xlabel_21">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search &field:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>search_field</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QLabel" name="xlabel_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Search for:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>search_for</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="2">
|
||||||
|
<widget class="QLabel" name="xlabel_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>&Replace with:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>replace_with</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QComboBox" name="search_field"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="HistoryLineEdit" name="search_for"/>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="2">
|
||||||
|
<widget class="HistoryLineEdit" name="replace_with"/>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QLabel" name="label_41">
|
||||||
|
<property name="text">
|
||||||
|
<string>Apply function &after replace:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>replace_func</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="2">
|
||||||
|
<widget class="QComboBox" name="replace_func"/>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QLabel" name="xlabel_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Test &text</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>test_text</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="2">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Test re&sult</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>test_result</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<widget class="QLabel" name="label_31">
|
||||||
|
<property name="text">
|
||||||
|
<string>Your test:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<widget class="HistoryLineEdit" name="test_text"/>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="2">
|
||||||
|
<widget class="QLineEdit" name="test_result"/>
|
||||||
|
</item>
|
||||||
|
<item row="20" column="1">
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@ -333,6 +472,11 @@ Future conversion of these books will use the default settings.</string>
|
|||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>widgets.h</header>
|
<header>widgets.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>HistoryLineEdit</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>widgets.h</header>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>authors</tabstop>
|
<tabstop>authors</tabstop>
|
||||||
|
@ -751,20 +751,22 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
try:
|
try:
|
||||||
if self.formats_changed:
|
if self.formats_changed:
|
||||||
self.sync_formats()
|
self.sync_formats()
|
||||||
title = unicode(self.title.text())
|
title = unicode(self.title.text()).strip()
|
||||||
self.db.set_title(self.id, title, notify=False)
|
self.db.set_title(self.id, title, notify=False)
|
||||||
au = unicode(self.authors.text())
|
au = unicode(self.authors.text()).strip()
|
||||||
if au:
|
if au:
|
||||||
self.db.set_authors(self.id, string_to_authors(au), notify=False)
|
self.db.set_authors(self.id, string_to_authors(au), notify=False)
|
||||||
aus = unicode(self.author_sort.text())
|
aus = unicode(self.author_sort.text()).strip()
|
||||||
if aus:
|
if aus:
|
||||||
self.db.set_author_sort(self.id, aus, notify=False, commit=False)
|
self.db.set_author_sort(self.id, aus, notify=False, commit=False)
|
||||||
self.db.set_isbn(self.id,
|
self.db.set_isbn(self.id,
|
||||||
re.sub(r'[^0-9a-zA-Z]', '', unicode(self.isbn.text())),
|
re.sub(r'[^0-9a-zA-Z]', '',
|
||||||
|
unicode(self.isbn.text()).strip()),
|
||||||
notify=False, commit=False)
|
notify=False, commit=False)
|
||||||
self.db.set_rating(self.id, 2*self.rating.value(), notify=False,
|
self.db.set_rating(self.id, 2*self.rating.value(), notify=False,
|
||||||
commit=False)
|
commit=False)
|
||||||
self.db.set_publisher(self.id, unicode(self.publisher.currentText()),
|
self.db.set_publisher(self.id,
|
||||||
|
unicode(self.publisher.currentText()).strip(),
|
||||||
notify=False, commit=False)
|
notify=False, commit=False)
|
||||||
self.db.set_tags(self.id, [x.strip() for x in
|
self.db.set_tags(self.id, [x.strip() for x in
|
||||||
unicode(self.tags.text()).split(',')], notify=False, commit=False)
|
unicode(self.tags.text()).split(',')], notify=False, commit=False)
|
||||||
@ -773,7 +775,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
|||||||
commit=False)
|
commit=False)
|
||||||
self.db.set_series_index(self.id, self.series_index.value(),
|
self.db.set_series_index(self.id, self.series_index.value(),
|
||||||
notify=False, commit=False)
|
notify=False, commit=False)
|
||||||
self.db.set_comment(self.id, unicode(self.comments.toPlainText()),
|
self.db.set_comment(self.id,
|
||||||
|
unicode(self.comments.toPlainText()).strip(),
|
||||||
notify=False, commit=False)
|
notify=False, commit=False)
|
||||||
d = self.pubdate.date()
|
d = self.pubdate.date()
|
||||||
d = qt_to_dt(d)
|
d = qt_to_dt(d)
|
||||||
|
@ -479,6 +479,36 @@ class BooksView(QTableView): # {{{
|
|||||||
sm = self.selectionModel()
|
sm = self.selectionModel()
|
||||||
sm.select(index, sm.ClearAndSelect|sm.Rows)
|
sm.select(index, sm.ClearAndSelect|sm.Rows)
|
||||||
|
|
||||||
|
def select_rows(self, identifiers, using_ids=True, change_current=True,
|
||||||
|
scroll=True):
|
||||||
|
'''
|
||||||
|
Select rows identified by identifiers. identifiers can be a set of ids,
|
||||||
|
row numbers or QModelIndexes.
|
||||||
|
'''
|
||||||
|
selmode = self.selectionMode()
|
||||||
|
self.setSelectionMode(QAbstractItemView.MultiSelection)
|
||||||
|
try:
|
||||||
|
rows = set([x.row() if hasattr(x, 'row') else x for x in
|
||||||
|
identifiers])
|
||||||
|
if using_ids:
|
||||||
|
rows = set([])
|
||||||
|
identifiers = set(identifiers)
|
||||||
|
m = self.model()
|
||||||
|
for row in range(m.rowCount(QModelIndex())):
|
||||||
|
if m.id(row) in identifiers:
|
||||||
|
rows.add(row)
|
||||||
|
if rows:
|
||||||
|
row = list(sorted(rows))[0]
|
||||||
|
if change_current:
|
||||||
|
self.set_current_row(row, select=False)
|
||||||
|
if scroll:
|
||||||
|
self.scroll_to_row(row)
|
||||||
|
self.clearSelection()
|
||||||
|
for r in rows:
|
||||||
|
self.selectRow(r)
|
||||||
|
finally:
|
||||||
|
self.setSelectionMode(selmode)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self._model.close()
|
self._model.close()
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ from calibre.gui2.init import LibraryViewMixin, LayoutMixin
|
|||||||
from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin
|
from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin
|
||||||
from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
|
from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
|
||||||
from calibre.gui2.tag_view import TagBrowserMixin
|
from calibre.gui2.tag_view import TagBrowserMixin
|
||||||
|
from calibre.utils.ordered_dict import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
class Listener(Thread): # {{{
|
class Listener(Thread): # {{{
|
||||||
@ -97,7 +98,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
|||||||
MainWindow.__init__(self, opts, parent)
|
MainWindow.__init__(self, opts, parent)
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.device_connected = None
|
self.device_connected = None
|
||||||
acmap = {}
|
acmap = OrderedDict()
|
||||||
for action in interface_actions():
|
for action in interface_actions():
|
||||||
mod, cls = action.actual_plugin.split(':')
|
mod, cls = action.actual_plugin.split(':')
|
||||||
ac = getattr(__import__(mod, fromlist=['1'], level=0), cls)(self,
|
ac = getattr(__import__(mod, fromlist=['1'], level=0), cls)(self,
|
||||||
|
@ -589,9 +589,9 @@ class ResultCache(SearchQueryParser):
|
|||||||
if field not in self.field_metadata.iterkeys():
|
if field not in self.field_metadata.iterkeys():
|
||||||
if field in ('author', 'tag', 'comment'):
|
if field in ('author', 'tag', 'comment'):
|
||||||
field += 's'
|
field += 's'
|
||||||
if field == 'date': field = 'timestamp'
|
if field == 'date': field = 'timestamp'
|
||||||
elif field == 'title': field = 'sort'
|
elif field == 'title': field = 'sort'
|
||||||
elif field == 'authors': field = 'author_sort'
|
elif field == 'authors': field = 'author_sort'
|
||||||
return field
|
return field
|
||||||
|
|
||||||
def sort(self, field, ascending, subsort=False):
|
def sort(self, field, ascending, subsort=False):
|
||||||
|
@ -143,6 +143,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
SchemaUpgrade.__init__(self)
|
SchemaUpgrade.__init__(self)
|
||||||
self.initialize_dynamic()
|
self.initialize_dynamic()
|
||||||
|
|
||||||
|
def get_property(self, idx, index_is_id=False, loc=-1):
|
||||||
|
row = self.data._data[idx] if index_is_id else self.data[idx]
|
||||||
|
if row is not None:
|
||||||
|
return row[loc]
|
||||||
|
|
||||||
def initialize_dynamic(self):
|
def initialize_dynamic(self):
|
||||||
self.field_metadata = FieldMetadata() #Ensure we start with a clean copy
|
self.field_metadata = FieldMetadata() #Ensure we start with a clean copy
|
||||||
self.prefs = DBPrefs(self)
|
self.prefs = DBPrefs(self)
|
||||||
@ -324,17 +329,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.last_update_check = self.last_modified()
|
self.last_update_check = self.last_modified()
|
||||||
|
|
||||||
|
|
||||||
def get_property(idx, index_is_id=False, loc=-1):
|
|
||||||
row = self.data._data[idx] if index_is_id else self.data[idx]
|
|
||||||
if row is not None:
|
|
||||||
return row[loc]
|
|
||||||
|
|
||||||
for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn',
|
for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn',
|
||||||
'publisher', 'rating', 'series', 'series_index', 'tags',
|
'publisher', 'rating', 'series', 'series_index', 'tags',
|
||||||
'title', 'timestamp', 'uuid', 'pubdate', 'ondevice'):
|
'title', 'timestamp', 'uuid', 'pubdate', 'ondevice'):
|
||||||
setattr(self, prop, functools.partial(get_property,
|
setattr(self, prop, functools.partial(self.get_property,
|
||||||
loc=self.FIELD_MAP['comments' if prop == 'comment' else prop]))
|
loc=self.FIELD_MAP['comments' if prop == 'comment' else prop]))
|
||||||
setattr(self, 'title_sort', functools.partial(get_property,
|
setattr(self, 'title_sort', functools.partial(self.get_property,
|
||||||
loc=self.FIELD_MAP['sort']))
|
loc=self.FIELD_MAP['sort']))
|
||||||
|
|
||||||
def initialize_database(self):
|
def initialize_database(self):
|
||||||
@ -439,7 +439,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if not f:
|
if not f:
|
||||||
continue
|
continue
|
||||||
stream = cStringIO.StringIO(f)
|
stream = cStringIO.StringIO(f)
|
||||||
self.add_format(id, format, stream, index_is_id=True, path=tpath)
|
self.add_format(id, format, stream, index_is_id=True,
|
||||||
|
path=tpath, notify=False)
|
||||||
self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id))
|
self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id))
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
@ -1128,7 +1129,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
result.append(r)
|
result.append(r)
|
||||||
return ' & '.join(result).replace('|', ',')
|
return ' & '.join(result).replace('|', ',')
|
||||||
|
|
||||||
def set_authors(self, id, authors, notify=True):
|
def set_authors(self, id, authors, notify=True, commit=True):
|
||||||
'''
|
'''
|
||||||
`authors`: A list of authors.
|
`authors`: A list of authors.
|
||||||
'''
|
'''
|
||||||
@ -1156,16 +1157,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
ss = self.author_sort_from_book(id, index_is_id=True)
|
ss = self.author_sort_from_book(id, index_is_id=True)
|
||||||
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
||||||
(ss, id))
|
(ss, id))
|
||||||
self.conn.commit()
|
if commit:
|
||||||
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['authors'],
|
self.data.set(id, self.FIELD_MAP['authors'],
|
||||||
','.join([a.replace(',', '|') for a in authors]),
|
','.join([a.replace(',', '|') for a in authors]),
|
||||||
row_is_id=True)
|
row_is_id=True)
|
||||||
self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True)
|
||||||
self.set_path(id, True)
|
self.set_path(id, index_is_id=True, commit=commit)
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
|
||||||
def set_title(self, id, title, notify=True):
|
def set_title(self, id, title, notify=True, commit=True):
|
||||||
if not title:
|
if not title:
|
||||||
return
|
return
|
||||||
if not isinstance(title, unicode):
|
if not isinstance(title, unicode):
|
||||||
@ -1176,8 +1178,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
self.data.set(id, self.FIELD_MAP['sort'], title_sort(title), row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['sort'], title_sort(title), row_is_id=True)
|
||||||
else:
|
else:
|
||||||
self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True)
|
||||||
self.set_path(id, True)
|
self.set_path(id, index_is_id=True, commit=commit)
|
||||||
self.conn.commit()
|
if commit:
|
||||||
|
self.conn.commit()
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class ContentServer(object):
|
|||||||
|
|
||||||
def sort(self, items, field, order):
|
def sort(self, items, field, order):
|
||||||
field = self.db.data.sanitize_sort_field_name(field)
|
field = self.db.data.sanitize_sort_field_name(field)
|
||||||
if field not in ('title', 'authors', 'rating', 'timestamp', 'tags', 'size', 'series'):
|
if field not in self.db.field_metadata.field_keys():
|
||||||
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
|
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
|
||||||
keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata)
|
keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata)
|
||||||
items.sort(key=keyg, reverse=not order)
|
items.sort(key=keyg, reverse=not order)
|
||||||
|
@ -81,7 +81,7 @@ Device Integration
|
|||||||
|
|
||||||
What devices does |app| support?
|
What devices does |app| support?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
At the moment |app| has full support for the SONY PRS 300/500/505/600/700/900, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle 1/2/3/DX/DXG, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, SpringDesign Alex, Kobo Reader, various Android phones and the iPhone/iPad. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
|
At the moment |app| has full support for the SONY PRS line, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle line, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, SpringDesign Alex, Kobo Reader, various Android phones and the iPhone/iPad. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
|
||||||
|
|
||||||
How can I help get my device supported in |app|?
|
How can I help get my device supported in |app|?
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
0
src/calibre/utils/smartypants.py
Executable file → Normal file
0
src/calibre/utils/smartypants.py
Executable file → Normal file
@ -10,6 +10,7 @@ import os
|
|||||||
|
|
||||||
from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation
|
from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation
|
||||||
from calibre.constants import numeric_version
|
from calibre.constants import numeric_version
|
||||||
|
from calibre import walk
|
||||||
|
|
||||||
class RecipeDisabled(Exception):
|
class RecipeDisabled(Exception):
|
||||||
pass
|
pass
|
||||||
@ -111,6 +112,10 @@ class RecipeInput(InputFormatPlugin):
|
|||||||
if f.endswith('.opf'):
|
if f.endswith('.opf'):
|
||||||
return os.path.abspath(f)
|
return os.path.abspath(f)
|
||||||
|
|
||||||
|
for f in walk('.'):
|
||||||
|
if f.endswith('.opf'):
|
||||||
|
return os.path.abspath(f)
|
||||||
|
|
||||||
def postprocess_book(self, oeb, opts, log):
|
def postprocess_book(self, oeb, opts, log):
|
||||||
if self.recipe_object is not None:
|
if self.recipe_object is not None:
|
||||||
self.recipe_object.postprocess_book(oeb, opts, log)
|
self.recipe_object.postprocess_book(oeb, opts, log)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user