This commit is contained in:
GRiker 2012-09-12 03:33:10 -06:00
commit 1dc4dd91dc
43 changed files with 964 additions and 405 deletions

View File

@ -35,7 +35,7 @@
- title: "Add an option under Preferences->Look & Feel->Book Details to hide the cover in the book details panel"
- title: "The Calibre Companion Android app that allows wireless connection of Android device to calibre is out of beta. See https://play.google.com/stor/apps/details?id=com.multipie.calibreandroid"
- title: "The Calibre Companion Android app that allows wireless connection of Android device to calibre is out of beta. See https://play.google.com/store/apps/details?id=com.multipie.calibreandroid"
bug fixes:
- title: "Fix sorting by author not working in the device view in calibre when connected to iTunes"

View File

@ -43,38 +43,38 @@ class Arcamax(BasicNewsRecipe):
feeds = []
for title, url in [
######## COMICS - GENERAL ########
#(u"9 Chickweed Lane", u"http://www.arcamax.com/ninechickweedlane"),
#(u"Agnes", u"http://www.arcamax.com/agnes"),
#(u"Andy Capp", u"http://www.arcamax.com/andycapp"),
#(u"9 Chickweed Lane", #u"http://www.arcamax.com/thefunnies/ninechickweedlane"),
#(u"Agnes", u"http://www.arcamax.com/thefunnies/agnes"),
#(u"Andy Capp", #u"http://www.arcamax.com/thefunnies/andycapp"),
(u"BC", u"http://www.arcamax.com/thefunnies/bc"),
#(u"Baby Blues", u"http://www.arcamax.com/babyblues"),
#(u"Beetle Bailey", u"http://www.arcamax.com/beetlebailey"),
#(u"Baby Blues", #u"http://www.arcamax.com/thefunnies/babyblues"),
#(u"Beetle Bailey", #u"http://www.arcamax.com/thefunnies/beetlebailey"),
(u"Blondie", u"http://www.arcamax.com/thefunnies/blondie"),
#u"Boondocks", u"http://www.arcamax.com/boondocks"),
#(u"Cathy", u"http://www.arcamax.com/cathy"),
#(u"Daddys Home", u"http://www.arcamax.com/daddyshome"),
#u"Boondocks", u"http://www.arcamax.com/thefunnies/boondocks"),
#(u"Cathy", u"http://www.arcamax.com/thefunnies/cathy"),
#(u"Daddys Home", #u"http://www.arcamax.com/thefunnies/daddyshome"),
(u"Dilbert", u"http://www.arcamax.com/thefunnies/dilbert"),
#(u"Dinette Set", u"http://www.arcamax.com/thedinetteset"),
#(u"Dinette Set", #u"http://www.arcamax.com/thefunnies/thedinetteset"),
(u"Dog Eat Doug", u"http://www.arcamax.com/thefunnies/dogeatdoug"),
(u"Doonesbury", u"http://www.arcamax.com/thefunnies/doonesbury"),
#(u"Dustin", u"http://www.arcamax.com/dustin"),
#(u"Dustin", u"http://www.arcamax.com/thefunnies/dustin"),
(u"Family Circus", u"http://www.arcamax.com/thefunnies/familycircus"),
(u"Garfield", u"http://www.arcamax.com/thefunnies/garfield"),
#(u"Get Fuzzy", u"http://www.arcamax.com/getfuzzy"),
#(u"Girls and Sports", u"http://www.arcamax.com/girlsandsports"),
#(u"Hagar the Horrible", u"http://www.arcamax.com/hagarthehorrible"),
#(u"Heathcliff", u"http://www.arcamax.com/heathcliff"),
#(u"Jerry King Cartoons", u"http://www.arcamax.com/humorcartoon"),
#(u"Luann", u"http://www.arcamax.com/luann"),
#(u"Momma", u"http://www.arcamax.com/momma"),
#(u"Mother Goose and Grimm", u"http://www.arcamax.com/mothergooseandgrimm"),
#(u"Get Fuzzy", #u"http://www.arcamax.com/thefunnies/getfuzzy"),
#(u"Girls and Sports", #u"http://www.arcamax.com/thefunnies/girlsandsports"),
#(u"Hagar the Horrible", #u"http://www.arcamax.com/thefunnies/hagarthehorrible"),
#(u"Heathcliff", #u"http://www.arcamax.com/thefunnies/heathcliff"),
#(u"Jerry King Cartoons", #u"http://www.arcamax.com/thefunnies/humorcartoon"),
#(u"Luann", u"http://www.arcamax.com/thefunnies/luann"),
#(u"Momma", u"http://www.arcamax.com/thefunnies/momma"),
#(u"Mother Goose and Grimm", #u"http://www.arcamax.com/thefunnies/mothergooseandgrimm"),
(u"Mutts", u"http://www.arcamax.com/thefunnies/mutts"),
#(u"Non Sequitur", u"http://www.arcamax.com/nonsequitur"),
#(u"Pearls Before Swine", u"http://www.arcamax.com/pearlsbeforeswine"),
#(u"Pickles", u"http://www.arcamax.com/pickles"),
#(u"Red and Rover", u"http://www.arcamax.com/redandrover"),
#(u"Rubes", u"http://www.arcamax.com/rubes"),
#(u"Rugrats", u"http://www.arcamax.com/rugrats"),
#(u"Non Sequitur", #u"http://www.arcamax.com/thefunnies/nonsequitur"),
#(u"Pearls Before Swine", #u"http://www.arcamax.com/thefunnies/pearlsbeforeswine"),
#(u"Pickles", u"http://www.arcamax.com/thefunnies/pickles"),
#(u"Red and Rover", #u"http://www.arcamax.com/thefunnies/redandrover"),
#(u"Rubes", u"http://www.arcamax.com/thefunnies/rubes"),
#(u"Rugrats", u"http://www.arcamax.com/thefunnies/rugrats"),
(u"Speed Bump", u"http://www.arcamax.com/thefunnies/speedbump"),
(u"Wizard of Id", u"http://www.arcamax.com/thefunnies/wizardofid"),
(u"Zits", u"http://www.arcamax.com/thefunnies/zits"),

View File

@ -1,14 +1,17 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1306097511(BasicNewsRecipe):
title = u'Birmingham post'
description = 'News for Birmingham UK'
timefmt = ''
description = 'Author D.Asbury. News for Birmingham UK'
#timefmt = ''
# last update 8/9/12
__author__ = 'Dave Asbury'
cover_url = 'http://1.bp.blogspot.com/_GwWyq5eGw9M/S9BHPHxW55I/AAAAAAAAB6Q/iGCWl0egGzg/s320/Birmingham+post+Lite+front.JPG'
cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/161987_9010212100_2035706408_n.jpg'
oldest_article = 2
max_articles_per_feed = 12
linearize_tables = True
remove_empty_feeds = True
remove_javascript = True
no_stylesheets = True
#auto_cleanup = True
language = 'en_GB'
@ -17,11 +20,12 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
keep_only_tags = [
dict(name='h1',attrs={'id' : 'article-headline'}),
dict(attrs={'id' : 'article-header'}),
#dict(name='h1',attrs={'id' : 'article-header'}),
dict(attrs={'class':['article-meta-author','article-meta-date','article main','art-o art-align-center otm-1 ']}),
dict(name='div',attrs={'class' : 'article-image full'}),
dict(attrs={'clas' : 'art-o art-align-center otm-1 '}),
dict(name='div',attrs={'class' : 'article main'}),
dict(name='div',attrs={'class' : 'article-image full'}),
dict(attrs={'clas' : 'art-o art-align-center otm-1 '}),
dict(name='div',attrs={'class' : 'article main'}),
#dict(name='p')
#dict(attrs={'id' : 'three-col'})
]
@ -37,11 +41,9 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
(u'Bloggs & Comments',u'http://www.birminghampost.net/comment/rss.xml')
]
extra_css = '''
body {font: sans-serif medium;}'
h1 {text-align : center; font-family:Arial,Helvetica,sans-serif; font-size:20px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold;}
h2 {text-align : center;color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:15px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; }
span{ font-size:9.5px; font-weight:bold;font-style:italic}
p { text-align: justify; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal;}
'''
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;text-align:center;}
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''

View File

@ -1,12 +1,11 @@
from calibre import browser
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1325006965(BasicNewsRecipe):
title = u'Countryfile.com'
#cover_url = 'http://www.countryfile.com/sites/default/files/imagecache/160px_wide/cover/2_1.jpg'
__author__ = 'Dave Asbury'
description = 'The official website of Countryfile Magazine'
# last updated 15/4/12
# last updated 9/9//12
language = 'en_GB'
oldest_article = 30
max_articles_per_feed = 25
@ -17,13 +16,14 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
def get_cover_url(self):
soup = self.index_to_soup('http://www.countryfile.com/')
cov = soup.find(attrs={'class' : 'imagecache imagecache-160px_wide imagecache-linked imagecache-160px_wide_linked'})
#print '******** ',cov,' ***'
print '******** ',cov,' ***'
cov2 = str(cov)
cov2=cov2[124:-90]
#print '******** ',cov2,' ***'
cov2=cov2[140:223]
print '******** ',cov2,' ***'
#cov2='http://www.countryfile.com/sites/default/files/imagecache/160px_wide/cover/1b_0.jpg'
# try to get cover - if can't get known cover
br = browser()
br.set_handle_redirect(False)
try:
br.open_novisit(cov2)

View File

@ -0,0 +1,87 @@
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
from collections import OrderedDict
class HistoryToday(BasicNewsRecipe):
title = 'History Today'
__author__ = 'Rick Shang'
description = 'UK-based magazine, publishing articles and book reviews covering all types and periods of history.'
language = 'en'
category = 'news'
encoding = 'UTF-8'
remove_tags = [dict(name='div',attrs={'class':['print-logo','print-site_name','print-breadcrumb']}),
dict(name='div', attrs={'id':['ht-tools','ht-tools2','ht-tags']})]
no_javascript = True
no_stylesheets = True
needs_subscription = True
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open('http://www.historytoday.com/user/login')
br.select_form(nr=1)
br['name'] = self.username
br['pass'] = self.password
res = br.submit()
raw = res.read()
if 'Session limit exceeded' in raw:
br.select_form(nr=1)
control=br.find_control('sid').items[1]
sid = []
br['sid']=sid.join(control)
br.submit()
return br
def parse_index(self):
#Find date
soup0 = self.index_to_soup('http://www.historytoday.com/')
dates = self.tag_to_string(soup0.find('div',attrs={'id':'block-block-226'}).span)
self.timefmt = u' [%s]'%dates
#Go to issue
soup = self.index_to_soup('http://www.historytoday.com/contents')
cover = soup.find('div',attrs={'id':'content-area'}).find('img')['src']
self.cover_url=cover
#Go to the main body
div = soup.find ('div', attrs={'class':'region region-content-bottom'})
feeds = OrderedDict()
section_title = ''
for section in div.findAll('div', attrs={'id':re.compile("block\-views\-contents.*")}):
section_title = self.tag_to_string(section.find('h2',attrs={'class':'title'}))
sectionbody=section.find('div', attrs={'class':'view-content'})
for article in sectionbody.findAll('div',attrs={'class':re.compile("views\-row.*")}):
articles = []
subarticle = []
subarticle = article.findAll('div')
if len(subarticle) < 2:
continue
title=self.tag_to_string(subarticle[0])
originalurl="http://www.historytoday.com" + subarticle[0].span.a['href'].strip()
originalpage=self.index_to_soup(originalurl)
printurl=originalpage.find('div',attrs = {'id':'ht-tools'}).a['href'].strip()
url="http://www.historytoday.com" + printurl
desc=self.tag_to_string(subarticle[1])
articles.append({'title':title, 'url':url, 'description':desc, 'date':''})
if articles:
if section_title not in feeds:
feeds[section_title] = []
feeds[section_title] += articles
ans = [(key, val) for key, val in feeds.iteritems()]
return ans
def cleanup(self):
self.browser.open('http://www.historytoday.com/logout')

View File

@ -15,11 +15,11 @@ class HoustonChronicle(BasicNewsRecipe):
remove_attributes = ['style']
auto_cleanup = True
oldest_article = 2.0
oldest_article = 3.0
#keep_only_tags = {'class':lambda x: x and ('hst-articletitle' in x or
#'hst-articletext' in x or 'hst-galleryitem' in x)}
#remove_attributes = ['xmlns']
remove_attributes = ['xmlns']
feeds = [
('News', "http://www.chron.com/rss/feed/News-270.php"),
@ -38,4 +38,4 @@ class HoustonChronicle(BasicNewsRecipe):
]

View File

@ -1,10 +1,10 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1306097511(BasicNewsRecipe):
title = u'Metro UK'
description = 'Author Dave Asbury : News as provide by The Metro -UK'
description = 'Author Dave Asbury : News from The Metro - UK'
#timefmt = ''
__author__ = 'Dave Asbury'
#last update 4/8/12
#last update 9/9/12
cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/276636_117118184990145_2132092232_n.jpg'
no_stylesheets = True
oldest_article = 1
@ -17,23 +17,24 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
language = 'en_GB'
masthead_url = 'http://e-edition.metro.co.uk/images/metro_logo.gif'
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:1.6em;}
h1{font-family:Arial,Helvetica,sans-serif; font-weight:900;font-size:1.6em;}
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:1.2em;}
p{font-family:Arial,Helvetica,sans-serif;font-size:1.0em;}
body{font-family:Helvetica,Arial,sans-serif;font-size:1.0em;}
'''
'''
keep_only_tags = [
#dict(name='h1'),
#dict(name='h2'),
#dict(name='div', attrs={'class' : ['row','article','img-cnt figure','clrd']})
#dict(name='h3'),
#dict(attrs={'class' : 'BText'}),
]
#dict(name='h1'),
#dict(name='h2'),
#dict(name='div', attrs={'class' : ['row','article','img-cnt figure','clrd']})
#dict(name='h3'),
#dict(attrs={'class' : 'BText'}),
]
remove_tags = [
dict(name='div',attrs={'class' : 'art-fd fd-gr1-b clrd'}),
dict(name='span',attrs={'class' : 'share'}),
dict(name='li'),
dict(attrs={'class' : ['twitter-share-button','header-forms','hdr-lnks','close','art-rgt','fd-gr1-b clrd google-article','news m12 clrd clr-b p5t shareBtm','item-ds csl-3-img news','c-1of3 c-last','c-1of1','pd','item-ds csl-3-img sport']}),
dict(attrs={'id' : ['','sky-left','sky-right','ftr-nav','and-ftr','notificationList','logo','miniLogo','comments-news','metro_extras']})
dict(name='li'),
dict(attrs={'class' : ['twitter-share-button','header-forms','hdr-lnks','close','art-rgt','fd-gr1-b clrd google-article','news m12 clrd clr-b p5t shareBtm','item-ds csl-3-img news','c-1of3 c-last','c-1of1','pd','item-ds csl-3-img sport']}),
dict(attrs={'id' : ['','sky-left','sky-right','ftr-nav','and-ftr','notificationList','logo','miniLogo','comments-news','metro_extras']})
]
remove_tags_before = dict(name='h1')
#remove_tags_after = dict(attrs={'id':['topic-buttons']})

View File

@ -73,14 +73,20 @@ class AdvancedUserRecipe1249039563(BasicNewsRecipe):
Change Log:
Date: 10/15/2010
Feeds updated by Martin Tarenskeen
Date: 09/09/2012
Feeds updated by Eric Lammerts
'''
feeds = [
(u'Laatste Nieuws', u'http://www.volkskrant.nl/rss/laatstenieuws.rss'),
(u'Binnenland', u'http://www.volkskrant.nl/rss/nederland.rss'),
(u'Buitenland', u'http://www.volkskrant.nl/rss/internationaal.rss'),
(u'Economie', u'http://www.volkskrant.nl/rss/economie.rss'),
(u'Sport', u'http://www.volkskrant.nl/rss/sport.rss'),
(u'Cultuur', u'http://www.volkskrant.nl/rss/kunst.rss'),
(u'Gezondheid & Wetenschap', u'http://www.volkskrant.nl/rss/wetenschap.rss'),
(u'Internet & Media', u'http://www.volkskrant.nl/rss/media.rss') ]
(u'Nieuws', u'http://www.volkskrant.nl/nieuws/rss.xml'),
(u'Binnenland', u'http://www.volkskrant.nl/nieuws/binnenland/rss.xml'),
(u'Buitenland', u'http://www.volkskrant.nl/buitenland/rss.xml'),
(u'Economie', u'http://www.volkskrant.nl/nieuws/economie/rss.xml'),
(u'Politiek', u'http://www.volkskrant.nl/politiek/rss.xml'),
(u'Sport', u'http://www.volkskrant.nl/sport/rss.xml'),
(u'Cultuur', u'http://www.volkskrant.nl/nieuws/cultuur/rss.xml'),
(u'Gezondheid & wetenschap', u'http://www.volkskrant.nl/nieuws/gezondheid--wetenschap/rss.xml'),
(u'Tech & Media', u'http://www.volkskrant.nl/tech-media/rss.xml'),
(u'Reizen', u'http://www.volkskrant.nl/nieuws/reizen/rss.xml'),
(u'Opinie', u'http://www.volkskrant.nl/opinie/rss.xml'),
(u'Opmerkelijk', u'http://www.volkskrant.nl/nieuws/opmerkelijk/rss.xml') ]

View File

@ -65,7 +65,7 @@ class WallStreetJournal(BasicNewsRecipe):
br['password'] = self.password
res = br.submit()
raw = res.read()
if '>Log Out<' not in raw:
if 'Welcome,' not in raw and '>Logout<' not in raw and '>Log Out<' not in raw:
raise ValueError('Failed to log in to wsj.com, check your '
'username and password')
return br

View File

@ -118,13 +118,13 @@ class ZeitEPUBAbo(BasicNewsRecipe):
def build_index(self):
domain = "https://premium.zeit.de"
url = domain + "/abo/zeit_digital"
url = domain + "/abo/digitalpaket"
browser = self.get_browser()
# new login process
response = browser.open(url)
# Get rid of nested form
response.set_data(response.get_data().replace('<div><form action="/abo/zeit_digital?destination=node%2F94" accept-charset="UTF-8" method="post" id="user-login-form" class="zol_inlinelabel">', ''))
response.set_data(response.get_data().replace('<div><form action="/abo/digitalpaket?destination=node%2F94" accept-charset="UTF-8" method="post" id="user-login-form" class="zol_inlinelabel">', ''))
browser.set_response(response)
browser.select_form(nr=2)
browser.form['name']=self.username
@ -177,13 +177,13 @@ class ZeitEPUBAbo(BasicNewsRecipe):
try:
self.log.warning('Trying PDF-based cover')
domain = "https://premium.zeit.de"
url = domain + "/abo/zeit_digital"
url = domain + "/abo/digitalpaket"
browser = self.get_browser()
# new login process
response=browser.open(url)
# Get rid of nested form
response.set_data(response.get_data().replace('<div><form action="/abo/zeit_digital?destination=node%2F94" accept-charset="UTF-8" method="post" id="user-login-form" class="zol_inlinelabel">', ''))
response.set_data(response.get_data().replace('<div><form action="/abo/digitalpaket?destination=node%2F94" accept-charset="UTF-8" method="post" id="user-login-form" class="zol_inlinelabel">', ''))
browser.set_response(response)
browser.select_form(nr=2)

Binary file not shown.

View File

@ -187,7 +187,7 @@ MathJax.Hub.Register.StartupHook("SVG Jax Ready",function () {
// fill it with the proper elements,
// and clean up the bbox
//
line = BBOX();
var line = BBOX();
state.first = broken; state.last = true;
this.SVGmoveLine(start,end,line,state,values);
line.Clean();

View File

@ -57,7 +57,7 @@ else:
# On linux, unicode arguments to os file functions are coerced to an ascii
# bytestring if sys.getfilesystemencoding() == 'ascii', which is
# just plain dumb. So issue a warning.
print ('WARNING: You do not have the LANG environment variable set. '
print ('WARNING: You do not have the LANG environment variable set correctly. '
'This will cause problems with non-ascii filenames. '
'Set it to something like en_US.UTF-8.\n')
except:

View File

@ -208,7 +208,7 @@ class ANDROID(USBMS):
'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC', 'LENOVO', 'ROCKCHIP',
'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD',
'PMP5097C', 'MASS', 'NOVO7', 'ZEKI', 'COBY', 'SXZ', 'USB_2.0',
'COBY_MID']
'COBY_MID', 'VS', 'AINOL']
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID',
@ -227,7 +227,8 @@ class ANDROID(USBMS):
'GT-S5830L_CARD', 'UNIVERSE', 'XT875', 'PRO', '.KOBO_VOX',
'THINKPAD_TABLET', 'SGH-T989', 'YP-G70', 'STORAGE_DEVICE',
'ADVANCED', 'SGH-I727', 'USB_FLASH_DRIVER', 'ANDROID',
'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VS']
'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E',
'NOVO7']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
@ -237,7 +238,8 @@ class ANDROID(USBMS):
'USB_2.0_DRIVER', 'I9100T', 'P999DW_SD_CARD', 'KTABLET_PC',
'FILE-CD_GADGET', 'GT-I9001_CARD', 'USB_2.0', 'XT875',
'UMS_COMPOSITE', 'PRO', '.KOBO_VOX', 'SGH-T989_CARD', 'SGH-I727',
'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E']
'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E',
'NOVO7']
OSX_MAIN_MEM = 'Android Device Main Memory'

View File

@ -110,3 +110,9 @@ class WrongDestinationError(PathError):
trying to send books to a non existant storage card.'''
pass
class BlacklistedDevice(OpenFailed):
''' Raise this error during open() when the device being opened has been
blacklisted by the user. Only used in drivers that manage device presence,
like the MTP driver. '''
pass

View File

@ -288,7 +288,7 @@ class KINDLE2(KINDLE):
name = 'Kindle 2/3/4/Touch Device Interface'
description = _('Communicate with the Kindle 2/3/4/Touch eBook reader.')
FORMATS = KINDLE.FORMATS + ['pdf', 'azw4', 'pobi']
FORMATS = ['azw3'] + KINDLE.FORMATS + ['pdf', 'azw4', 'pobi']
DELETE_EXTS = KINDLE.DELETE_EXTS + ['.mbp1', '.mbs', '.sdr']
PRODUCT_ID = [0x0002, 0x0004]
@ -449,7 +449,7 @@ class KINDLE_DX(KINDLE2):
name = 'Kindle DX Device Interface'
description = _('Communicate with the Kindle DX eBook reader.')
FORMATS = KINDLE2.FORMATS[1:]
PRODUCT_ID = [0x0003]
BCD = [0x0100]
@ -462,7 +462,6 @@ class KINDLE_FIRE(KINDLE2):
description = _('Communicate with the Kindle Fire')
gui_name = 'Fire'
FORMATS = list(KINDLE2.FORMATS)
FORMATS.insert(0, 'azw3')
PRODUCT_ID = [0x0006]
BCD = [0x216, 0x100]

View File

@ -59,4 +59,7 @@ class MTPDeviceBase(DevicePlugin):
from calibre.devices.utils import build_template_regexp
return build_template_regexp(self.save_template)
def is_customizable(self):
return True

View File

@ -16,7 +16,7 @@ from calibre.constants import iswindows, numeric_version
from calibre.devices.mtp.base import debug
from calibre.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory
from calibre.utils.config import from_json, to_json, JSONConfig
from calibre.utils.date import now, isoformat
from calibre.utils.date import now, isoformat, utcnow
BASE = importlib.import_module('calibre.devices.mtp.%s.driver'%(
'windows' if iswindows else 'unix')).MTP_DEVICE
@ -47,10 +47,13 @@ class MTP_DEVICE(BASE):
from calibre.library.save_to_disk import config
self._prefs = p = JSONConfig('mtp_devices')
p.defaults['format_map'] = self.FORMATS
p.defaults['send_to'] = ['eBooks/import',
'wordplayer/calibretransfer', 'Books', 'sdcard/ebooks',
'eBooks', 'kindle']
p.defaults['send_to'] = ['Calibre_Companion', 'Books',
'eBooks/import', 'eBooks', 'wordplayer/calibretransfer',
'sdcard/ebooks', 'kindle']
p.defaults['send_template'] = config().parse().send_template
p.defaults['blacklist'] = []
p.defaults['history'] = {}
p.defaults['rules'] = []
return self._prefs
@ -74,6 +77,11 @@ class MTP_DEVICE(BASE):
self.current_library_uuid = library_uuid
self.location_paths = None
BASE.open(self, devices, library_uuid)
h = self.prefs['history']
if self.current_serial_num:
h[self.current_serial_num] = (self.current_friendly_name,
isoformat(utcnow()))
self.prefs['history'] = h
# Device information {{{
def _update_drive_info(self, storage, location_code, name=None):
@ -99,7 +107,7 @@ class MTP_DEVICE(BASE):
dinfo['mtp_prefix'] = storage.storage_prefix
raw = json.dumps(dinfo, default=to_json)
self.put_file(storage, self.DRIVEINFO, BytesIO(raw), len(raw))
self.driveinfo = dinfo
self.driveinfo[location_code] = dinfo
def get_device_information(self, end_session=True):
self.report_progress(1.0, _('Get device information...'))
@ -266,15 +274,18 @@ class MTP_DEVICE(BASE):
self.plugboards = plugboards
self.plugboard_func = pb_func
def create_upload_path(self, path, mdata, fname):
def create_upload_path(self, path, mdata, fname, routing):
from calibre.devices.utils import create_upload_path
from calibre.utils.filenames import ascii_filename as sanitize
ext = fname.rpartition('.')[-1].lower()
path = routing.get(ext, path)
filepath = create_upload_path(mdata, fname, self.save_template, sanitize,
prefix_path=path,
path_type=posixpath,
maxlen=self.MAX_PATH_LEN,
use_subdirs = True,
news_in_folder = self.NEWS_IN_FOLDER,
use_subdirs=True,
news_in_folder=self.NEWS_IN_FOLDER,
)
return tuple(x for x in filepath.split('/'))
@ -293,7 +304,7 @@ class MTP_DEVICE(BASE):
p = path
break
if p is None:
p = 'eBooks'
p = 'Books'
self.location_paths[loc] = p
return self.location_paths[on_card]
@ -322,8 +333,10 @@ class MTP_DEVICE(BASE):
self.report_progress(0, _('Transferring books to device...'))
i, total = 0, len(files)
routing = {fmt:dest for fmt,dest in self.get_pref('rules')}
for infile, fname, mi in izip(files, names, metadata):
path = self.create_upload_path(prefix, mi, fname)
path = self.create_upload_path(prefix, mi, fname, routing)
parent = self.ensure_parent(storage, path)
if hasattr(infile, 'read'):
pos = infile.tell()

View File

@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import operator, traceback, pprint, sys
import operator, traceback, pprint, sys, time
from threading import RLock
from collections import namedtuple
from functools import partial
@ -15,8 +15,8 @@ from functools import partial
from calibre import prints, as_unicode
from calibre.constants import plugins
from calibre.ptempfile import SpooledTemporaryFile
from calibre.devices.errors import OpenFailed, DeviceError
from calibre.devices.mtp.base import MTPDeviceBase, synchronous
from calibre.devices.errors import OpenFailed, DeviceError, BlacklistedDevice
from calibre.devices.mtp.base import MTPDeviceBase, synchronous, debug
MTPDevice = namedtuple('MTPDevice', 'busnum devnum vendor_id product_id '
'bcd serial manufacturer product')
@ -99,19 +99,25 @@ class MTP_DEVICE(MTPDeviceBase):
return False
p('Known MTP devices connected:')
for d in devs: p(d)
d = devs[0]
p('\nTrying to open:', d)
try:
self.open(d, 'debug')
except:
p('Opening device failed:')
p(traceback.format_exc())
return False
p('Opened', self.current_friendly_name, 'successfully')
p('Storage info:')
p(pprint.pformat(self.dev.storage_info))
self.eject()
return True
for d in devs:
p('\nTrying to open:', d)
try:
self.open(d, 'debug')
except BlacklistedDevice:
p('This device has been blacklisted by the user')
continue
except:
p('Opening device failed:')
p(traceback.format_exc())
return False
else:
p('Opened', self.current_friendly_name, 'successfully')
p('Storage info:')
p(pprint.pformat(self.dev.storage_info))
self.post_yank_cleanup()
return True
return False
@synchronous
def create_device(self, connected_device):
@ -167,6 +173,12 @@ class MTP_DEVICE(MTPDeviceBase):
if not storage:
self.blacklisted_devices.add(connected_device)
raise OpenFailed('No storage found for device %s'%(connected_device,))
snum = self.dev.serial_number
if snum in self.prefs.get('blacklist', []):
self.blacklisted_devices.add(connected_device)
self.dev = None
raise BlacklistedDevice(
'The %s device has been blacklisted by the user'%(connected_device,))
self._main_id = storage[0]['id']
self._carda_id = self._cardb_id = None
if len(storage) > 1:
@ -176,11 +188,13 @@ class MTP_DEVICE(MTPDeviceBase):
self.current_friendly_name = self.dev.friendly_name
if not self.current_friendly_name:
self.current_friendly_name = self.dev.model_name or _('Unknown MTP device')
self.current_serial_num = self.dev.serial_number
self.current_serial_num = snum
@property
def filesystem_cache(self):
if self._filesystem_cache is None:
st = time.time()
debug('Loading filesystem metadata...')
from calibre.devices.mtp.filesystem_cache import FilesystemCache
with self.lock:
storage, all_items, all_errs = [], [], []
@ -208,6 +222,7 @@ class MTP_DEVICE(MTPDeviceBase):
self.current_friendly_name,
self.format_errorstack(all_errs)))
self._filesystem_cache = FilesystemCache(storage, all_items)
debug('Filesystem metadata loaded in %g seconds'%(time.time()-st))
return self._filesystem_cache
@synchronous

View File

@ -15,8 +15,8 @@ from itertools import chain
from calibre import as_unicode, prints
from calibre.constants import plugins, __appname__, numeric_version
from calibre.ptempfile import SpooledTemporaryFile
from calibre.devices.errors import OpenFailed, DeviceError
from calibre.devices.mtp.base import MTPDeviceBase
from calibre.devices.errors import OpenFailed, DeviceError, BlacklistedDevice
from calibre.devices.mtp.base import MTPDeviceBase, debug
class ThreadingViolation(Exception):
@ -163,6 +163,9 @@ class MTP_DEVICE(MTPDeviceBase):
p('\nTrying to open:', pnp_id)
try:
self.open(pnp_id, 'debug-detection')
except BlacklistedDevice:
p('This device has been blacklisted by the user')
continue
except:
p('Open failed:')
p(traceback.format_exc())
@ -172,7 +175,7 @@ class MTP_DEVICE(MTPDeviceBase):
p('Opened', self.current_friendly_name, 'successfully')
p('Device info:')
p(pprint.pformat(self.dev.data))
self.eject()
self.post_yank_cleanup()
return True
p('No suitable MTP devices found')
return False
@ -196,6 +199,8 @@ class MTP_DEVICE(MTPDeviceBase):
@property
def filesystem_cache(self):
if self._filesystem_cache is None:
debug('Loading filesystem metadata...')
st = time.time()
from calibre.devices.mtp.filesystem_cache import FilesystemCache
ts = self.total_space()
all_storage = []
@ -215,6 +220,7 @@ class MTP_DEVICE(MTPDeviceBase):
all_storage.append(storage)
items.append(id_map.itervalues())
self._filesystem_cache = FilesystemCache(all_storage, chain(*items))
debug('Filesystem metadata loaded in %g seconds'%(time.time()-st))
return self._filesystem_cache
@same_thread
@ -225,7 +231,6 @@ class MTP_DEVICE(MTPDeviceBase):
self._main_id = self._carda_id = self._cardb_id = None
self.dev = self._filesystem_cache = None
@same_thread
def post_yank_cleanup(self):
self.currently_connected_pnp_id = self.current_friendly_name = None
@ -256,6 +261,13 @@ class MTP_DEVICE(MTPDeviceBase):
if not storage:
self.blacklisted_devices.add(connected_device)
raise OpenFailed('No storage found for device %s'%(connected_device,))
snum = devdata.get('serial_number', None)
if snum in self.prefs.get('blacklist', []):
self.blacklisted_devices.add(connected_device)
self.dev = None
raise BlacklistedDevice(
'The %s device has been blacklisted by the user'%(connected_device,))
self._main_id = storage[0]['id']
if len(storage) > 1:
self._carda_id = storage[1]['id']
@ -266,7 +278,7 @@ class MTP_DEVICE(MTPDeviceBase):
self.current_friendly_name = devdata.get('model_name',
_('Unknown MTP device'))
self.currently_connected_pnp_id = connected_device
self.current_serial_num = devdata.get('serial_number', None)
self.current_serial_num = snum
@same_thread
def get_basic_device_information(self):

View File

@ -329,8 +329,48 @@ class DeviceScanner(object):
return device.is_usb_connected(self.devices, debug=debug,
only_presence=only_presence)
def test_for_mem_leak():
from calibre.utils.mem import memory, gc_histogram, diff_hists
import gc
gc.disable()
scanner = DeviceScanner()
scanner.scan()
memory() # load the psutil library
for i in xrange(3): gc.collect()
for reps in (1, 10, 100, 1000):
for i in xrange(3): gc.collect()
h1 = gc_histogram()
startmem = memory()
for i in xrange(reps):
scanner.scan()
for i in xrange(3): gc.collect()
usedmem = memory(startmem)
prints('Memory used in %d repetitions of scan(): %.5f KB'%(reps,
1024*usedmem))
prints('Differences in python object counts:')
diff_hists(h1, gc_histogram())
prints()
if not iswindows:
return
for reps in (1, 10, 100, 1000):
for i in xrange(3): gc.collect()
h1 = gc_histogram()
startmem = memory()
for i in xrange(reps):
win_pnp_drives()
for i in xrange(3): gc.collect()
usedmem = memory(startmem)
prints('Memory used in %d repetitions of pnp_scan(): %.5f KB'%(reps,
1024*usedmem))
prints('Differences in python object counts:')
diff_hists(h1, gc_histogram())
prints()
def main(args=sys.argv):
test_for_mem_leak()
return 0
if __name__ == '__main__':

View File

@ -84,6 +84,8 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
PREFIX = ''
BACKLOADING_ERROR_MESSAGE = None
SAVE_TEMPLATE = '{title} - {authors} ({id})'
# Some network protocol constants
BASE_PACKET_LEN = 4096
PROTOCOL_VERSION = 1

View File

@ -974,6 +974,9 @@ class Device(DeviceConfig, DevicePlugin):
def get_carda_ebook_dir(self, for_upload=False):
return self.EBOOK_DIR_CARD_A
def get_cardb_ebook_dir(self, for_upload=False):
return self.EBOOK_DIR_CARD_B
def _sanity_check(self, on_card, files):
from calibre.devices.utils import sanity_check
sanity_check(on_card, files, self.card_prefix(), self.free_space())

View File

@ -66,6 +66,7 @@ class RecipeInput(InputFormatPlugin):
if os.access(recipe_or_file, os.R_OK):
self.recipe_source = open(recipe_or_file, 'rb').read()
recipe = compile_recipe(self.recipe_source)
log('Using custom recipe')
else:
from calibre.web.feeds.recipes.collection import \
get_builtin_recipe_by_title
@ -87,12 +88,15 @@ class RecipeInput(InputFormatPlugin):
'back to builtin one')
builtin = True
if builtin:
log('Using bundled builtin recipe')
raw = get_builtin_recipe_by_title(title, log=log,
download_recipe=False)
if raw is None:
raise ValueError('Failed to find builtin recipe: '+title)
recipe = compile_recipe(raw)
self.recipe_source = raw
else:
log('Using downloaded builtin recipe')
if recipe is None:
raise ValueError('%r is not a valid recipe file or builtin recipe' %

View File

@ -1009,6 +1009,8 @@ OptionRecommendation(name='search_replace',
pr(0., _('Running transforms on ebook...'))
self.oeb.plumber_output_format = self.output_fmt or ''
from calibre.ebooks.oeb.transforms.guide import Clean
Clean()(self.oeb, self.opts)
pr(0.1)
@ -1120,7 +1122,7 @@ OptionRecommendation(name='search_replace',
self.log.info('Creating %s...'%self.output_plugin.name)
our = CompositeProgressReporter(0.67, 1., self.ui_reporter)
self.output_plugin.report_progress = our
our(0., _('Creating')+' %s'%self.output_plugin.name)
our(0., _('Running %s plugin')%self.output_plugin.name)
with self.output_plugin:
self.output_plugin.convert(self.oeb, self.output, self.input_plugin,
self.opts, self.log)

View File

@ -39,6 +39,7 @@ class MathJax
showMathMenu: false,
extensions: ["tex2jax.js", "asciimath2jax.js", "mml2jax.js"],
jax: ["input/TeX","input/MathML","input/AsciiMath","output/SVG"],
SVG : { linebreaks : { automatic : true } },
TeX: {
extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js"]
}

View File

@ -267,34 +267,43 @@ class Stylizer(object):
rules.sort()
self.rules = rules
self._styles = {}
pseudo_pat = re.compile(ur':(first-letter|first-line|link|hover|visited|active|focus)', re.I)
for _, _, cssdict, text, _ in rules:
fl = ':first-letter' in text
if fl:
text = text.replace(':first-letter', '')
fl = pseudo_pat.search(text)
if fl is not None:
text = text.replace(fl.group(), '')
selector = get_css_selector(text)
matches = selector(tree, self.logger)
if fl:
from lxml.builder import ElementMaker
E = ElementMaker(namespace=XHTML_NS)
for elem in matches:
for x in elem.iter():
if x.text:
punctuation_chars = []
text = unicode(x.text)
while text:
if not unicodedata.category(text[0]).startswith('P'):
break
punctuation_chars.append(text[0])
text = text[1:]
if fl is not None:
fl = fl.group(1)
if fl == 'first-letter' and getattr(self.oeb,
'plumber_output_format', '').lower() == u'mobi':
# Fake first-letter
from lxml.builder import ElementMaker
E = ElementMaker(namespace=XHTML_NS)
for elem in matches:
for x in elem.iter():
if x.text:
punctuation_chars = []
text = unicode(x.text)
while text:
category = unicodedata.category(text[0])
if category[0] not in {'P', 'Z'}:
break
punctuation_chars.append(text[0])
text = text[1:]
special_text = u''.join(punctuation_chars) + \
(text[0] if text else u'')
span = E.span(special_text)
span.tail = text[1:]
x.text = None
x.insert(0, span)
self.style(span)._update_cssdict(cssdict)
break
special_text = u''.join(punctuation_chars) + \
(text[0] if text else u'')
span = E.span(special_text)
span.tail = text[1:]
x.text = None
x.insert(0, span)
self.style(span)._update_cssdict(cssdict)
break
else: # Element pseudo-class
for elem in matches:
self.style(elem)._update_pseudo_class(fl, cssdict)
else:
for elem in matches:
self.style(elem)._update_cssdict(cssdict)
@ -495,6 +504,7 @@ class Style(object):
self._height = None
self._lineHeight = None
self._bgcolor = None
self._pseudo_classes = {}
stylizer._styles[element] = self
def set(self, prop, val):
@ -506,6 +516,11 @@ class Style(object):
def _update_cssdict(self, cssdict):
self._style.update(cssdict)
def _update_pseudo_class(self, name, cssdict):
orig = self._pseudo_classes.get(name, {})
orig.update(cssdict)
self._pseudo_classes[name] = orig
def _apply_style_attr(self, url_replacer=None):
attrib = self._element.attrib
if 'style' not in attrib:
@ -778,3 +793,14 @@ class Style(object):
def cssdict(self):
return dict(self._style)
def pseudo_classes(self, filter_css):
if filter_css:
css = copy.deepcopy(self._pseudo_classes)
for psel, cssdict in css.iteritems():
for k in filter_css:
cssdict.pop(k, None)
else:
css = self._pseudo_classes
return {k:v for k, v in css.iteritems() if v}

View File

@ -222,7 +222,7 @@ class CSSFlattener(object):
value = 0.0
cssdict[property] = "%0.5fem" % (value / fsize)
def flatten_node(self, node, stylizer, names, styles, psize, item_id):
def flatten_node(self, node, stylizer, names, styles, pseudo_styles, psize, item_id):
if not isinstance(node.tag, basestring) \
or namespace(node.tag) != XHTML_NS:
return
@ -357,25 +357,51 @@ class CSSFlattener(object):
cssdict.get('text-align', None) not in ('center', 'right')):
cssdict['text-indent'] = "%1.1fem" % indent_size
if cssdict:
items = cssdict.items()
items.sort()
css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
classes = node.get('class', '').strip() or 'calibre'
klass = STRIPNUM.sub('', classes.split()[0].replace('_', ''))
if css in styles:
match = styles[css]
else:
match = klass + str(names[klass] or '')
styles[css] = match
names[klass] += 1
node.attrib['class'] = match
pseudo_classes = style.pseudo_classes(self.filter_css)
if cssdict or pseudo_classes:
keep_classes = set()
if cssdict:
items = cssdict.items()
items.sort()
css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
classes = node.get('class', '').strip() or 'calibre'
klass = STRIPNUM.sub('', classes.split()[0].replace('_', ''))
if css in styles:
match = styles[css]
else:
match = klass + str(names[klass] or '')
styles[css] = match
names[klass] += 1
node.attrib['class'] = match
keep_classes.add(match)
for psel, cssdict in pseudo_classes.iteritems():
items = sorted(cssdict.iteritems())
css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
pstyles = pseudo_styles[psel]
if css in pstyles:
match = pstyles[css]
else:
# We have to use a different class for each psel as
# otherwise you can have incorrect styles for a situation
# like: a:hover { color: red } a:link { color: blue } a.x:hover { color: green }
# If the pcalibre class for a:hover and a:link is the same,
# then the class attribute for a.x tags will contain both
# that class and the class for a.x:hover, which is wrong.
klass = 'pcalibre'
match = klass + str(names[klass] or '')
pstyles[css] = match
names[klass] += 1
keep_classes.add(match)
node.attrib['class'] = ' '.join(keep_classes)
elif 'class' in node.attrib:
del node.attrib['class']
if 'style' in node.attrib:
del node.attrib['style']
for child in node:
self.flatten_node(child, stylizer, names, styles, psize, item_id)
self.flatten_node(child, stylizer, names, styles, pseudo_styles, psize, item_id)
def flatten_head(self, item, href, global_href):
html = item.data
@ -446,7 +472,7 @@ class CSSFlattener(object):
def flatten_spine(self):
names = defaultdict(int)
styles = {}
styles, pseudo_styles = {}, defaultdict(dict)
for item in self.oeb.spine:
html = item.data
stylizer = self.stylizers[item]
@ -454,10 +480,20 @@ class CSSFlattener(object):
self.specializer(item, stylizer)
body = html.find(XHTML('body'))
fsize = self.context.dest.fbase
self.flatten_node(body, stylizer, names, styles, fsize, item.id)
self.flatten_node(body, stylizer, names, styles, pseudo_styles, fsize, item.id)
items = [(key, val) for (val, key) in styles.items()]
items.sort()
# :hover must come after link and :active must come after :hover
psels = sorted(pseudo_styles.iterkeys(), key=lambda x :
{'hover':1, 'active':2}.get(x, 0))
for psel in psels:
styles = pseudo_styles[psel]
if not styles: continue
x = sorted(((k+':'+psel, v) for v, k in styles.iteritems()))
items.extend(x)
css = ''.join(".%s {\n%s;\n}\n\n" % (key, val) for key, val in items)
href = self.replace_css(css)
global_css = self.collect_global_css()
for item in self.oeb.spine:

View File

@ -11,6 +11,7 @@ from PyQt4.Qt import QToolButton, QMenu, pyqtSignal, QIcon, QTimer
from calibre.gui2.actions import InterfaceAction
from calibre.utils.smtp import config as email_config
from calibre.utils.config import tweaks
from calibre.constants import iswindows, isosx
from calibre.customize.ui import is_disabled
from calibre.devices.bambook.driver import BAMBOOK
@ -84,10 +85,12 @@ class ShareConnMenu(QMenu): # {{{
action=self.toggle_server_action, group=gr)
def server_state_changed(self, running):
from calibre.utils.mdns import get_external_ip
from calibre.utils.mdns import get_external_ip, verify_ipV4_address
text = _('Start Content Server')
if running:
text = _('Stop Content Server') + ' [%s]'%get_external_ip()
listen_on = (verify_ipV4_address(tweaks['server_listen_on']) or
get_external_ip())
text = _('Stop Content Server') + ' [%s]'%listen_on
self.toggle_server_action.setText(text)
def hide_smartdevice_menus(self):

View File

@ -80,7 +80,7 @@ class EditMetadataAction(InterfaceAction):
Dispatcher(self.metadata_downloaded),
ensure_fields=ensure_fields)
def cleanup_bulk_download(self, tdir):
def cleanup_bulk_download(self, tdir, *args):
try:
shutil.rmtree(tdir, ignore_errors=True)
except:
@ -108,22 +108,26 @@ class EditMetadataAction(InterfaceAction):
'Proceed with updating the metadata in your library?')%len(id_map)
show_copy_button = False
checkbox_msg = None
if failed_ids or failed_covers:
show_copy_button = True
num = len(failed_ids.union(failed_covers))
msg += '<p>'+_('Could not download metadata and/or covers for %d of the books. Click'
' "Show details" to see which books.')%num
checkbox_msg = _('Show the &failed books in the main book list '
'after updating metadata')
payload = (id_map, tdir, log_file, lm_map)
self.gui.proceed_question(self.apply_downloaded_metadata,
payload, log_file,
_('Download log'), _('Download complete'), msg,
payload = (id_map, tdir, log_file, lm_map,
failed_ids.union(failed_covers))
self.gui.proceed_question(self.apply_downloaded_metadata, payload,
log_file, _('Download log'), _('Download complete'), msg,
det_msg=det_msg, show_copy_button=show_copy_button,
cancel_callback=lambda x:self.cleanup_bulk_download(tdir),
log_is_file=True)
cancel_callback=partial(self.cleanup_bulk_download, tdir),
log_is_file=True, checkbox_msg=checkbox_msg,
checkbox_checked=False)
def apply_downloaded_metadata(self, payload):
good_ids, tdir, log_file, lm_map = payload
def apply_downloaded_metadata(self, payload, *args):
good_ids, tdir, log_file, lm_map, failed_ids = payload
if not good_ids:
return
@ -162,8 +166,18 @@ class EditMetadataAction(InterfaceAction):
cov = None
id_map[bid] = (opf, cov)
self.apply_metadata_changes(id_map, callback=lambda x:
self.cleanup_bulk_download(tdir))
restrict_to_failed = bool(args and args[0])
if restrict_to_failed:
db.data.set_marked_ids(failed_ids)
self.apply_metadata_changes(id_map,
callback=partial(self.downloaded_metadata_applied, tdir,
restrict_to_failed))
def downloaded_metadata_applied(self, tdir, restrict_to_failed, *args):
if restrict_to_failed:
self.gui.search.set_search_string('marked:true')
self.cleanup_bulk_download(tdir)
# }}}

View File

@ -152,8 +152,16 @@ def render_data(mi, use_roman_numbers=True, all_fields=False):
scheme = u'devpath' if isdevice else u'path'
url = prepare_string_for_xml(path if isdevice else
unicode(mi.id), True)
link = u'<a href="%s:%s" title="%s">%s</a>' % (scheme, url,
prepare_string_for_xml(path, True), _('Click to open'))
pathstr = _('Click to open')
extra = ''
if isdevice:
durl = url
if durl.startswith('mtp:::'):
durl = ':::'.join( (durl.split(':::'))[2:] )
extra = '<br><span style="font-size:smaller">%s</span>'%(
prepare_string_for_xml(durl))
link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url,
prepare_string_for_xml(path, True), pathstr, extra)
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, link)))
elif field == 'formats':
if isdevice: continue

View File

@ -612,7 +612,7 @@ class GenericRulesTable(QTableWidget):
first_rule_name = unicode(self.cellWidget(first-1,self.COLUMNS['NAME']['ordinal']).text()).strip()
message = _("Are you sure you want to delete '%s'?") % (first_rule_name)
if len(rows) > 1:
message = _('Are you sure you want to delete rules #%d-%d?') % (first, last)
message = _('Are you sure you want to delete rules #%(first)d-%(last)d?') % dict(first=first, last=last)
if not question_dialog(self, _('Delete Rule'), message, show_copy_button=False):
return
first_sel_row = self.currentRow()

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>588</width>
<height>342</height>
<height>416</height>
</rect>
</property>
<property name="windowTitle">
@ -91,23 +91,33 @@
<item row="0" column="1">
<widget class="QComboBox" name="opt_mobi_file_type"/>
</item>
<item row="1" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Personal Doc tag:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="3" column="1">
<widget class="QLineEdit" name="opt_personal_doc"/>
</item>
<item row="2" column="0" colspan="2">
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_share_not_sync">
<property name="text">
<string>Enable sharing of book content via Facebook, etc. WARNING: Disables last read syncing</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&lt;b&gt;WARNING:&lt;/b&gt; Various Kindle devices have trouble displaying the new or both MOBI filetypes. If you wish to use the new format on your device, convert to AZW3 instead of MOBI.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -24,7 +24,8 @@ from calibre.gui2 import (config, error_dialog, Dispatcher, dynamic,
from calibre.ebooks.metadata import authors_to_string
from calibre import preferred_encoding, prints, force_unicode, as_unicode
from calibre.utils.filenames import ascii_filename
from calibre.devices.errors import FreeSpaceError, WrongDestinationError
from calibre.devices.errors import (FreeSpaceError, WrongDestinationError,
BlacklistedDevice)
from calibre.devices.apple.driver import ITUNES_ASYNC
from calibre.devices.folder_device.driver import FOLDER_DEVICE
from calibre.devices.bambook.driver import BAMBOOK, BAMBOOKWifi
@ -252,6 +253,9 @@ class DeviceManager(Thread): # {{{
if cd is not None:
try:
dev.open(cd, self.current_library_uuid)
except BlacklistedDevice as e:
prints('Ignoring blacklisted device: %s'%
as_unicode(e))
except:
prints('Error while trying to open %s (Driver: %s)'%
(cd, dev))

View File

@ -11,11 +11,13 @@ import weakref
from PyQt4.Qt import (QWidget, QListWidgetItem, Qt, QToolButton, QLabel,
QTabWidget, QGridLayout, QListWidget, QIcon, QLineEdit, QVBoxLayout,
QPushButton)
QPushButton, QGroupBox, QScrollArea, QHBoxLayout, QComboBox,
pyqtSignal, QSizePolicy, QDialog, QDialogButtonBox)
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.utils.date import parse_date
class FormatsConfig(QWidget): # {{{
@ -85,7 +87,7 @@ class TemplateConfig(QWidget): # {{{
m.setBuddy(t)
l.addWidget(m, 0, 0, 1, 2)
l.addWidget(t, 1, 0, 1, 1)
b = self.b = QPushButton(_('Template editor'))
b = self.b = QPushButton(_('&Template editor'))
l.addWidget(b, 1, 1, 1, 1)
b.clicked.connect(self.edit_template)
@ -136,6 +138,152 @@ class SendToConfig(QWidget): # {{{
# }}}
class IgnoredDevices(QWidget): # {{{
def __init__(self, devs, blacklist):
QWidget.__init__(self)
self.l = l = QVBoxLayout()
self.setLayout(l)
self.la = la = QLabel('<p>'+_(
'''Select the devices to be <b>ignored</b>. calibre will not
connect to devices with a checkmark next to their names.'''))
la.setWordWrap(True)
l.addWidget(la)
self.f = f = QListWidget(self)
l.addWidget(f)
devs = [(snum, (x[0], parse_date(x[1]))) for snum, x in
devs.iteritems()]
for dev, x in sorted(devs, key=lambda x:x[1][1], reverse=True):
name = x[0]
name = '%s [%s]'%(name, dev)
item = QListWidgetItem(name, f)
item.setData(Qt.UserRole, dev)
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
item.setCheckState(Qt.Checked if dev in blacklist else Qt.Unchecked)
@property
def blacklist(self):
return [unicode(self.f.item(i).data(Qt.UserRole).toString()) for i in
xrange(self.f.count()) if self.f.item(i).checkState()==Qt.Checked]
def ignore_device(self, snum):
for i in xrange(self.f.count()):
i = self.f.item(i)
c = unicode(i.data(Qt.UserRole).toString())
if c == snum:
i.setCheckState(Qt.Checked)
break
# }}}
# Rules {{{
class Rule(QWidget):
remove = pyqtSignal(object)
def __init__(self, rule=None):
QWidget.__init__(self)
self.l = l = QHBoxLayout()
self.setLayout(l)
self.l1 = l1 = QLabel(_('Send the '))
l.addWidget(l1)
self.fmt = f = QComboBox(self)
l.addWidget(f)
self.l2 = l2 = QLabel(_(' format to the folder: '))
l.addWidget(l2)
self.folder = f = QLineEdit(self)
f.setPlaceholderText(_('Folder on the device'))
l.addWidget(f)
self.rb = rb = QPushButton(QIcon(I('list_remove.png')),
_('&Remove rule'), self)
l.addWidget(rb)
rb.clicked.connect(self.removed)
for fmt in sorted(BOOK_EXTENSIONS):
self.fmt.addItem(fmt.upper(), fmt.lower())
self.fmt.setCurrentIndex(0)
if rule is not None:
fmt, folder = rule
idx = self.fmt.findText(fmt.upper())
if idx > -1:
self.fmt.setCurrentIndex(idx)
self.folder.setText(folder)
self.ignore = False
def removed(self):
self.remove.emit(self)
@property
def rule(self):
folder = unicode(self.folder.text()).strip()
if folder:
return (
unicode(self.fmt.itemData(self.fmt.currentIndex()).toString()),
folder
)
return None
class FormatRules(QGroupBox):
def __init__(self, rules):
QGroupBox.__init__(self, _('Format specific sending'))
self.l = l = QVBoxLayout()
self.setLayout(l)
self.la = la = QLabel('<p>'+_(
'''You can create rules that control where ebooks of a specific
format are sent to on the device. These will take precedence over
the folders specified above.'''))
la.setWordWrap(True)
l.addWidget(la)
self.sa = sa = QScrollArea(self)
sa.setWidgetResizable(True)
self.w = w = QWidget(self)
w.l = QVBoxLayout()
w.setLayout(w.l)
sa.setWidget(w)
l.addWidget(sa)
self.widgets = []
for rule in rules:
r = Rule(rule)
self.widgets.append(r)
w.l.addWidget(r)
r.remove.connect(self.remove_rule)
if not self.widgets:
self.add_rule()
self.b = b = QPushButton(QIcon(I('plus.png')), _('Add a &new rule'))
l.addWidget(b)
b.clicked.connect(self.add_rule)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)
def add_rule(self):
r = Rule()
self.widgets.append(r)
self.w.l.addWidget(r)
r.remove.connect(self.remove_rule)
self.sa.verticalScrollBar().setValue(self.sa.verticalScrollBar().maximum())
def remove_rule(self, rule):
rule.setVisible(False)
rule.ignore = True
@property
def rules(self):
for w in self.widgets:
if not w.ignore:
r = w.rule
if r is not None:
yield r
# }}}
class MTPConfig(QTabWidget):
def __init__(self, device, parent=None):
@ -145,8 +293,8 @@ class MTPConfig(QTabWidget):
cd = msg = None
if device.current_friendly_name is not None:
if device.current_serial_num is None:
msg = '<p>' + _('The <b>%s</b> device has no serial number, '
'it cannot be configured'%device.current_friendly_name)
msg = '<p>' + (_('The <b>%s</b> device has no serial number, '
'it cannot be configured')%device.current_friendly_name)
else:
cd = 'device-'+device.current_serial_num
else:
@ -162,6 +310,8 @@ class MTPConfig(QTabWidget):
l = QLabel(msg)
l.setWordWrap(True)
l.setStyleSheet('QLabel { margin-left: 2em }')
l.setMinimumWidth(500)
l.setMinimumHeight(400)
self.insertTab(0, l, _('Cannot configure'))
else:
self.base = QWidget(self)
@ -169,20 +319,42 @@ class MTPConfig(QTabWidget):
l = self.base.l = QGridLayout(self.base)
self.base.setLayout(l)
self.rules = r = FormatRules(self.get_pref('rules'))
self.formats = FormatsConfig(set(BOOK_EXTENSIONS),
self.get_pref('format_map'))
self.send_to = SendToConfig(self.get_pref('send_to'))
self.template = TemplateConfig(self.get_pref('send_template'))
self.base.la = la = QLabel(_('Choose the formats to send to the %s')%self.device.current_friendly_name)
self.base.la = la = QLabel(_(
'Choose the formats to send to the %s')%self.device.current_friendly_name)
la.setWordWrap(True)
l.addWidget(la, 0, 0, 1, 1)
l.addWidget(self.formats, 1, 0, 3, 1)
l.addWidget(self.send_to, 1, 1, 1, 1)
l.addWidget(self.template, 2, 1, 1, 1)
l.setRowStretch(2, 10)
self.base.b = b = QPushButton(QIcon(I('list_remove.png')),
_('&Ignore the %s in calibre')%device.current_friendly_name,
self.base)
b.clicked.connect(self.ignore_device)
l.addWidget(b, 0, 0, 1, 2)
l.addWidget(la, 1, 0, 1, 1)
l.addWidget(self.formats, 2, 0, 3, 1)
l.addWidget(self.send_to, 2, 1, 1, 1)
l.addWidget(self.template, 3, 1, 1, 1)
l.setRowStretch(4, 10)
l.addWidget(r, 5, 0, 1, 2)
l.setRowStretch(5, 100)
self.igntab = IgnoredDevices(self.device.prefs['history'],
self.device.prefs['blacklist'])
self.addTab(self.igntab, _('Ignored devices'))
self.setCurrentIndex(0)
def ignore_device(self):
self.igntab.ignore_device(self.device.current_serial_num)
self.base.b.setEnabled(False)
self.base.b.setText(_('The %s will be ignored in calibre')%
self.device.current_friendly_name)
self.base.b.setStyleSheet('QPushButton { font-weight: bold }')
self.base.setEnabled(False)
def get_pref(self, key):
p = self.device.prefs.get(self.current_device_key, {})
if not p:
@ -194,31 +366,40 @@ class MTPConfig(QTabWidget):
return self._device()
def validate(self):
if not self.formats.validate():
return False
if not self.template.validate():
return False
if hasattr(self, 'formats'):
if not self.formats.validate():
return False
if not self.template.validate():
return False
return True
def commit(self):
p = self.device.prefs.get(self.current_device_key, {})
p.pop('format_map', None)
f = self.formats.format_map
if f and f != self.device.prefs['format_map']:
p['format_map'] = f
if hasattr(self, 'formats'):
p.pop('format_map', None)
f = self.formats.format_map
if f and f != self.device.prefs['format_map']:
p['format_map'] = f
p.pop('send_template', None)
t = self.template.template
if t and t != self.device.prefs['send_template']:
p['send_template'] = t
p.pop('send_template', None)
t = self.template.template
if t and t != self.device.prefs['send_template']:
p['send_template'] = t
p.pop('send_to', None)
s = self.send_to.value
if s and s != self.device.prefs['send_to']:
p['send_to'] = s
p.pop('send_to', None)
s = self.send_to.value
if s and s != self.device.prefs['send_to']:
p['send_to'] = s
self.device.prefs[self.current_device_key] = p
p.pop('rules', None)
r = list(self.rules.rules)
if r and r != self.device.prefs['rules']:
p['rules'] = r
self.device.prefs[self.current_device_key] = p
self.device.prefs['blacklist'] = self.igntab.blacklist
if __name__ == '__main__':
from calibre.gui2 import Application
@ -232,8 +413,16 @@ if __name__ == '__main__':
cd = dev.detect_managed_devices(s.devices)
dev.open(cd, 'test')
cw = dev.config_widget()
cw.show()
app.exec_()
d = QDialog()
d.l = QVBoxLayout()
d.setLayout(d.l)
d.l.addWidget(cw)
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
d.l.addWidget(bb)
bb.accepted.connect(d.accept)
bb.rejected.connect(d.reject)
if d.exec_() == d.Accepted:
cw.commit()
dev.shutdown()

View File

@ -11,18 +11,18 @@ from collections import namedtuple
from PyQt4.Qt import (QDialog, Qt, QLabel, QGridLayout, QPixmap,
QDialogButtonBox, QApplication, QSize, pyqtSignal, QIcon,
QPlainTextEdit)
QPlainTextEdit, QCheckBox)
from calibre.constants import __version__
from calibre.gui2.dialogs.message_box import ViewLog
Question = namedtuple('Question', 'payload callback cancel_callback '
'title msg html_log log_viewer_title log_is_file det_msg '
'show_copy_button')
'show_copy_button checkbox_msg checkbox_checked')
class ProceedQuestion(QDialog):
ask_question = pyqtSignal(object, object)
ask_question = pyqtSignal(object, object, object)
def __init__(self, parent):
QDialog.__init__(self, parent)
@ -62,10 +62,13 @@ class ProceedQuestion(QDialog):
self.bb.setStandardButtons(self.bb.Yes|self.bb.No)
self.bb.button(self.bb.Yes).setDefault(True)
self.checkbox = QCheckBox('', self)
l.addWidget(ic, 0, 0, 1, 1)
l.addWidget(msg, 0, 1, 1, 1)
l.addWidget(self.det_msg, 1, 0, 1, 2)
l.addWidget(self.bb, 2, 0, 1, 2)
l.addWidget(self.checkbox, 1, 0, 1, 2)
l.addWidget(self.det_msg, 2, 0, 1, 2)
l.addWidget(self.bb, 3, 0, 1, 2)
self.ask_question.connect(self.do_ask_question,
type=Qt.QueuedConnection)
@ -82,19 +85,28 @@ class ProceedQuestion(QDialog):
if self.questions:
payload, callback, cancel_callback = self.questions[0][:3]
self.questions = self.questions[1:]
self.ask_question.emit(callback, payload)
cb = None
if self.checkbox.isVisible():
cb = bool(self.checkbox.isChecked())
self.ask_question.emit(callback, payload, cb)
self.hide()
def reject(self):
if self.questions:
payload, callback, cancel_callback = self.questions[0][:3]
self.questions = self.questions[1:]
self.ask_question.emit(cancel_callback, payload)
cb = None
if self.checkbox.isVisible():
cb = bool(self.checkbox.isChecked())
self.ask_question.emit(cancel_callback, payload, cb)
self.hide()
def do_ask_question(self, callback, payload):
def do_ask_question(self, callback, payload, checkbox_checked):
if callable(callback):
callback(payload)
args = [payload]
if checkbox_checked is not None:
args.append(checkbox_checked)
callback(*args)
self.show_question()
def toggle_det_msg(self, *args):
@ -122,6 +134,10 @@ class ProceedQuestion(QDialog):
self.det_msg.setVisible(False)
self.det_msg_toggle.setVisible(bool(question.det_msg))
self.det_msg_toggle.setText(self.show_det_msg)
self.checkbox.setVisible(question.checkbox_msg is not None)
if question.checkbox_msg is not None:
self.checkbox.setText(question.checkbox_msg)
self.checkbox.setChecked(question.checkbox_checked)
self.do_resize()
self.show()
self.bb.button(self.bb.Yes).setDefault(True)
@ -129,10 +145,10 @@ class ProceedQuestion(QDialog):
def __call__(self, callback, payload, html_log, log_viewer_title, title,
msg, det_msg='', show_copy_button=False, cancel_callback=None,
log_is_file=False):
log_is_file=False, checkbox_msg=None, checkbox_checked=False):
'''
A non modal popup that notifies the user that a background task has
been completed. This class guarantees that onlya single popup is
been completed. This class guarantees that only a single popup is
visible at any one time. Other requests are queued and displayed after
the user dismisses the current popup.
@ -147,11 +163,18 @@ class ProceedQuestion(QDialog):
:param msg: The msg to display
:param det_msg: Detailed message
:param log_is_file: If True the html_log parameter is interpreted as
the path to a file on disk containing the log encoded with utf-8
the path to a file on disk containing the log
encoded with utf-8
:param checkbox_msg: If not None, a checkbox is displayed in the
dialog, showing this message. The callback is
called with both the payload and the state of the
checkbox as arguments.
:param checkbox_checked: If True the checkbox is checked by default.
'''
question = Question(payload, callback, cancel_callback, title, msg,
html_log, log_viewer_title, log_is_file, det_msg,
show_copy_button)
show_copy_button, checkbox_msg, checkbox_checked)
self.questions.append(question)
self.show_question()
@ -169,7 +192,8 @@ def main():
from calibre.gui2 import Application
app = Application([])
p = ProceedQuestion(None)
p(lambda p:None, None, 'ass', 'ass', 'testing', 'testing')
p(lambda p:None, None, 'ass', 'ass', 'testing', 'testing',
checkbox_msg='testing the ruddy checkbox', det_msg='details')
p.exec_()
app

View File

@ -75,7 +75,7 @@ class UpdateNotification(QDialog):
self.logo.setPixmap(QPixmap(I('lt.png')).scaled(100, 100,
Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
self.label = QLabel(('<p>'+
_('%(app)s has been updated to version <b>%(ver)s</b>. '
_('New version <b>%(ver)s</b> of %(app)s is available for download. '
'See the <a href="http://calibre-ebook.com/whats-new'
'">new features</a>.'))%dict(
app=__appname__, ver=calibre_version))

View File

@ -211,15 +211,15 @@ class CatalogBuilder(object):
(str): sort key
"""
if not book['series']:
fs = '{:<%d}!{!s}' % longest_author_sort
fs = u'{:<%d}!{!s}' % longest_author_sort
key = fs.format(capitalize(book['author_sort']),
capitalize(book['title_sort']))
else:
index = book['series_index']
integer = int(index)
fraction = index-integer
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
fs = '{:<%d}~{!s}{!s}' % longest_author_sort
series_index = u'%04d%s' % (integer, str(u'%0.4f' % fraction).lstrip(u'0'))
fs = u'{:<%d}~{!s}{!s}' % longest_author_sort
key = fs.format(capitalize(book['author_sort']),
self.generate_sort_title(book['series']),
series_index)
@ -2464,7 +2464,9 @@ class CatalogBuilder(object):
title_str=title_str,
xmlns=XHTML_NS,
)
for k, v in args.iteritems():
if isbytestring(v):
args[k] = v.decode('utf-8')
generated_html = P('catalog/template.xhtml',
data=True).decode('utf-8').format(**args)
generated_html = substitute_entites(generated_html)

View File

@ -1432,6 +1432,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
pdir = os.path.dirname(dest)
if not os.path.exists(pdir):
os.makedirs(pdir)
size = 0
if copy_function is not None:
copy_function(dest)
size = os.path.getsize(dest)
@ -1441,6 +1442,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
with lopen(dest, 'wb') as f:
shutil.copyfileobj(stream, f)
size = f.tell()
elif os.path.exists(dest):
size = os.path.getsize(dest)
self.conn.execute('INSERT OR REPLACE INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)',
(id, format.upper(), size, name))
self.update_last_modified([id], commit=False)

View File

@ -5,7 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os
import os, socket
import logging
from logging.handlers import RotatingFileHandler
@ -17,7 +17,7 @@ from calibre.utils.date import fromtimestamp
from calibre.library.server import listen_on, log_access_file, log_error_file
from calibre.library.server.utils import expose, AuthController
from calibre.utils.mdns import publish as publish_zeroconf, \
unpublish as unpublish_zeroconf, get_external_ip
unpublish as unpublish_zeroconf, get_external_ip, verify_ipV4_address
from calibre.library.server.content import ContentServer
from calibre.library.server.mobile import MobileServer
from calibre.library.server.xml import XMLServer
@ -78,6 +78,7 @@ class BonJour(SimplePlugin): # {{{
SimplePlugin.__init__(self, engine)
self.port = port
self.prefix = prefix
self.ip_address = '0.0.0.0'
@property
def mdns_services(self):
@ -90,9 +91,10 @@ class BonJour(SimplePlugin): # {{{
def start(self):
zeroconf_ip_address = verify_ipV4_address(self.ip_address)
try:
for s in self.mdns_services:
publish_zeroconf(*s)
publish_zeroconf(*s, use_ip_address=zeroconf_ip_address)
except:
import traceback
cherrypy.log.error('Failed to start BonJour:')
@ -140,6 +142,7 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
if not opts.url_prefix:
opts.url_prefix = ''
cherrypy.engine.bonjour.ip_address = listen_on
cherrypy.engine.bonjour.port = opts.port
cherrypy.engine.bonjour.prefix = opts.url_prefix

View File

@ -5,8 +5,8 @@
msgid ""
msgstr ""
"Project-Id-Version: calibre 0.8.68\n"
"POT-Creation-Date: 2012-09-07 08:49+IST\n"
"PO-Revision-Date: 2012-09-07 08:49+IST\n"
"POT-Creation-Date: 2012-09-08 17:09+IST\n"
"PO-Revision-Date: 2012-09-08 17:09+IST\n"
"Last-Translator: Automatically generated\n"
"Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n"
@ -131,8 +131,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ztxt/writer.py:27
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:108
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:109
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:426
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:434
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:439
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:447
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:166
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:397
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:400
@ -143,8 +143,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:124
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:143
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:145
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1366
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1369
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1367
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1370
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:55
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:60
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:128
@ -173,12 +173,12 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:585
#: /home/kovid/work/calibre/src/calibre/library/database2.py:593
#: /home/kovid/work/calibre/src/calibre/library/database2.py:604
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2189
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2343
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2768
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3415
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3417
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3554
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2192
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2346
#: /home/kovid/work/calibre/src/calibre/library/database2.py:2771
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3418
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3420
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3557
#: /home/kovid/work/calibre/src/calibre/library/server/content.py:250
#: /home/kovid/work/calibre/src/calibre/library/server/content.py:251
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:247
@ -1034,14 +1034,14 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1199
#: /home/kovid/work/calibre/src/calibre/library/database2.py:370
#: /home/kovid/work/calibre/src/calibre/library/database2.py:383
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3272
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3275
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:187
msgid "News"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2770
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3228
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3246
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3231
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3249
msgid "Catalog"
msgstr ""
@ -3352,8 +3352,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:70
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:163
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:401
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2232
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:292
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2142
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:140
msgid "Series"
msgid_plural "Series"
@ -3880,7 +3880,17 @@ msgstr ""
msgid "Show this confirmation again"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:546
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:332
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:134
msgid "Restart needed"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:334
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:741
msgid "Restart calibre now"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:559
msgid "Choose Files"
msgstr ""
@ -4112,7 +4122,7 @@ msgid "Merging user annotations into database"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:63
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:744
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:745
msgid "Fetch annotations (experimental)"
msgstr ""
@ -4357,7 +4367,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:403
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:197
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:933
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:934
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1004
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:114
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:128
@ -4590,14 +4600,14 @@ msgid "Main memory"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:239
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:669
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:678
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:670
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:679
msgid "Storage Card A"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:240
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:671
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:680
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:672
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:681
msgid "Storage Card B"
msgstr ""
@ -5373,7 +5383,7 @@ msgid "The specified directory could not be processed."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:274
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1087
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
msgid "No books"
msgstr ""
@ -5794,6 +5804,18 @@ msgstr ""
msgid "E-book options"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:90
msgid "Catalogs"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:99
msgid "Read book"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:105
msgid "Wishlist item"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:133
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:769
msgid "any date"
@ -5831,7 +5853,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:615
#, python-format
msgid "Are you sure you want to delete rules #%d-%d?"
msgid "Are you sure you want to delete rules #%(first)d-%(last)d?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:616
@ -7685,226 +7707,222 @@ msgstr ""
msgid "tags to remove"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:50
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:51
#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:148
msgid "No details available."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:202
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:203
msgid "Device no longer connected."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:413
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:414
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:27
msgid "Debug device detection"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:429
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:430
msgid "Get device information"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:444
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:445
msgid "Get list of books on device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:451
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:452
msgid "Prepare files for transfer from device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:462
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:463
msgid "Get annotations from device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:474
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:475
msgid "Send metadata to device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:479
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:480
msgid "Send collections to device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:529
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:530
#, python-format
msgid "Upload %d books to device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:545
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:546
msgid "Delete books from device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:563
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:564
msgid "Download books from device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:573
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:574
msgid "View book on device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:652
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:653
msgid "Set default send to device action"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:658
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:659
msgid "Send to main memory"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:660
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:661
msgid "Send to storage card A"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:662
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:663
msgid "Send to storage card B"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:667
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:676
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:668
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:677
msgid "Main Memory"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:688
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:689
msgid "Send specific format to"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:689
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:690
msgid "Send and delete from library"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:732
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:733
msgid "Eject device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:813
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:814
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:71
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:332
#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:58
msgid "Error"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:814
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:815
msgid "Error communicating with device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:843
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1416
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:844
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1417
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:260
msgid "No suitable formats"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:859
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:860
msgid "Select folder to open as device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878
msgid "Running jobs"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:879
msgid "Cannot configure the device while there are running device jobs."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:883
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:884
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:168
#, python-format
msgid "Configure %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:898
msgid "Disconnect device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:899
#, python-format
msgid "Disconnect and re-connect the %s for your changes to be applied."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:939
msgid "Error talking to device"
msgid "Restart calibre for the changes to %s to be applied."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:940
msgid "Error talking to device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:941
msgid "There was a temporary error talking to the device. Please unplug and reconnect the device or reboot."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:984
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:985
msgid "Device: "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:986
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:987
msgid " detected."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1089
msgid "selected to send"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1095
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1125
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1096
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1126
msgid "No device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1096
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1097
msgid "No device connected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1112
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1113
#, python-format
msgid "%(num)i of %(total)i Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1116
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1117
#, python-format
msgid "0 of %i Books"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1117
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1118
msgid "Choose format to send to device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1126
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1127
msgid "Cannot send: No device is connected"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1129
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1133
msgid "No card"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1130
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1134
msgid "No card"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1131
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1135
msgid "Cannot send: Device has no storage card"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1195
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1278
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1410
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1196
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1279
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1411
msgid "Auto convert the following books before uploading to the device?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1224
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1225
msgid "Sending catalogs to device."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1323
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1324
msgid "Sending news to device."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1377
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1378
msgid "Sending books to device."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1417
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1418
msgid "Could not upload the following books to the device, as no suitable formats were found. Convert the book(s) to a format supported by your device first."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1490
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1491
msgid "No space on device"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1491
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1492
msgid "<p>Cannot upload books to device there is no more free space available "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1496
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1497
msgid "Incorrect destination"
msgstr ""
@ -9471,11 +9489,6 @@ msgstr ""
msgid "Plugin <b>{0}</b> successfully installed under <b> {1} plugins</b>. You may have to restart calibre for the plugin to take effect."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:741
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:352
msgid "Restart calibre now"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:760
msgid "A problem occurred while installing this plugin. This plugin will now be uninstalled. Please post the error message in details below into the forum thread for this plugin and restart Calibre."
msgstr ""
@ -9529,8 +9542,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:87
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:156
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:397
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1342
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:288
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1254
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:113
msgid "Authors"
msgstr ""
@ -13072,11 +13085,6 @@ msgstr ""
msgid "The changes you have made require calibre be restarted immediately. You will not be allowed to set any more preferences, until you restart."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:350
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:134
msgid "Restart needed"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:48
msgid "Source"
msgstr ""
@ -14986,7 +14994,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:78
#, python-format
msgid "%(app)s has been updated to version <b>%(ver)s</b>. See the <a href=\"http://calibre-ebook.com/whats-new\">new features</a>."
msgid "New version <b>%(ver)s</b> of %(app)s is available for download. See the <a href=\"http://calibre-ebook.com/whats-new\">new features</a>."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:84
@ -16370,153 +16378,168 @@ msgid ""
"*** Adding 'By Authors' Section required for MOBI output ***"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:46
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:47
msgid "Symbols"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:382
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:273
msgid "No genres to catalog.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:384
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:275
msgid "Check 'Excluded genres' regex in E-book options.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:386
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:277
msgid "No books available to catalog"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:399
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2429
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:290
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2313
msgid "Titles"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:403
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:294
msgid "Genres"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:405
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1703
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:296
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1615
msgid "Recently Added"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:407
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1902
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:298
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1814
msgid "Recently Read"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:409
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:300
msgid "Descriptions"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:634
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:525
msgid "<p>Inconsistent Author Sort values for Author<br/>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:651
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:542
msgid "Warning: Inconsistent Author Sort values for Author '{!s}':\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:785
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:676
msgid "Sorting database"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:983
msgid "Fetching database"
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:751
msgid "Sorting titles"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1023
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:763
msgid ""
"No books to catalog.\n"
"Check 'Excluded books' rules in E-book options.\n"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1025
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:765
msgid "No books available to include in catalog"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1983
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1895
msgid "Genres HTML"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2409
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2293
msgid "Titles HTML"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2604
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2606
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2608
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2488
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2490
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2492
msgid "by "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2745
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2629
msgid "Descriptions HTML"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2749
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2633
msgid "Description HTML"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2884
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2768
msgid "NCX header"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2959
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2843
msgid "NCX for Descriptions"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3080
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2964
msgid "NCX for Series"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3156
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3041
#, python-format
msgid "Series beginning with %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3199
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3043
#, python-format
msgid "Series beginning with '%s'"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3087
msgid "NCX for Titles"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3277
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3166
#, python-format
msgid "Titles beginning with %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3318
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3168
#, python-format
msgid "Titles beginning with '%s'"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3210
msgid "NCX for Authors"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3388
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3281
#, python-format
msgid "Authors beginning with %s"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3283
#, python-format
msgid "Authors beginning with '%s'"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3428
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3324
msgid "NCX for Recently Added"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3615
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3511
msgid "NCX for Recently Read"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3752
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3648
msgid "NCX for Genres"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3870
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3766
msgid "Generating OPF"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4242
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4138
msgid "Thumbnails"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4248
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4144
msgid "Thumbnail"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4743
#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4641
msgid "Saving NCX"
msgstr ""
@ -17107,17 +17130,17 @@ msgstr ""
msgid "creating custom column "
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3580
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3583
#, python-format
msgid "<p>Migrating old database to ebook library in %s<br><center>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3609
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3612
#, python-format
msgid "Copying <b>%s</b>"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3626
#: /home/kovid/work/calibre/src/calibre/library/database2.py:3629
msgid "Compacting database"
msgstr ""

View File

@ -39,6 +39,19 @@ def _get_external_ip():
#print 'ipaddr: %s' % ipaddr
return ipaddr
def verify_ipV4_address(ip_address):
result = None
if ip_address != '0.0.0.0' and ip_address != '::':
# do some more sanity checks on the address
try:
socket.inet_aton(ip_address)
if len(ip_address.split('.')) == 4:
result = ip_address
except socket.error:
# Not legal ip address
pass
return result
_ext_ip = None
def get_external_ip():
global _ext_ip
@ -93,7 +106,8 @@ def publish(desc, type, port, properties=None, add_hostname=True, use_ip_address
into the TXT record.
'''
server = start_server()
service = create_service(desc, type, port, properties, add_hostname)
service = create_service(desc, type, port, properties, add_hostname,
use_ip_address)
server.registerService(service)
def unpublish(desc, type, port, properties=None, add_hostname=True):

View File

@ -168,9 +168,9 @@ winutil_set_debug(PyObject *self, PyObject *args) {
return Py_None;
}
static LPTSTR
static LPWSTR
get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iterate) {
/* Get a the property specified by `property` from the registry for the
/* Get the property specified by `property` from the registry for the
* device enumerated by `index` in the collection `hDevInfo`. `iterate`
* will be set to `FALSE` if `index` points outside `hDevInfo`.
* :return: A string allocated on the heap containing the property or
@ -178,7 +178,7 @@ get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iter
*/
SP_DEVINFO_DATA DeviceInfoData;
DWORD DataT;
LPTSTR buffer = NULL;
LPWSTR buffer = NULL;
DWORD buffersize = 0;
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
@ -187,7 +187,7 @@ get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iter
return NULL;
}
while(!SetupDiGetDeviceRegistryProperty(
while(!SetupDiGetDeviceRegistryPropertyW(
hDevInfo,
&DeviceInfoData,
property,
@ -196,11 +196,11 @@ get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iter
buffersize,
&buffersize)) {
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
buffer = (LPTSTR)PyMem_Malloc(2*buffersize); // Twice for bug in Win2k
if (buffer != NULL) { PyMem_Free(buffer); buffer = NULL; }
buffer = (LPWSTR)PyMem_Malloc(2*buffersize); // Twice for bug in Win2k
} else {
PyMem_Free(buffer);
if (buffer != NULL) { PyMem_Free(buffer); buffer = NULL; }
PyErr_SetFromWindowsErr(0);
buffer = NULL;
break;
}
} //while
@ -209,7 +209,7 @@ get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iter
}
static BOOL
check_device_id(LPTSTR buffer, unsigned int vid, unsigned int pid) {
check_device_id(LPWSTR buffer, unsigned int vid, unsigned int pid) {
WCHAR xVid[9], dVid[9], xPid[9], dPid[9];
unsigned int j;
_snwprintf_s(xVid, 9, _TRUNCATE, L"vid_%4.4x", vid);
@ -607,31 +607,28 @@ winutil_get_removable_drives(PyObject *self, PyObject *args) {
return NULL;
}
ddebug = PyObject_IsTrue(pdebug);
// Find all removable drives
for (j = 0; j < MAX_DRIVES; j++) g_drives[j].letter = 0;
if (!get_all_removable_disks(g_drives)) return NULL;
volumes = PyDict_New();
if (volumes == NULL) return NULL;
for (j = 0; j < MAX_DRIVES; j++) g_drives[j].letter = 0;
// Find all removable drives
if (!get_all_removable_disks(g_drives)) {
return NULL;
}
if (volumes == NULL) return PyErr_NoMemory();
ddebug = PyObject_IsTrue(pdebug);
hDevInfo = create_device_info_set((LPGUID)&GUID_DEVINTERFACE_VOLUME,
NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hDevInfo == INVALID_HANDLE_VALUE) return NULL;
if (hDevInfo == INVALID_HANDLE_VALUE) { Py_DECREF(volumes); return NULL; }
// Enumerate through the set
for (i=0; iterate; i++) {
candidates = PyList_New(0);
if (candidates == NULL) return PyErr_NoMemory();
if (candidates == NULL) { Py_DECREF(volumes); return PyErr_NoMemory();}
interfaceDetailData = get_device_ancestors(hDevInfo, i, candidates, &iterate, ddebug);
if (interfaceDetailData == NULL) {
PyErr_Print(); continue;
PyErr_Print();
Py_DECREF(candidates); candidates = NULL;
continue;
}
length = wcslen(interfaceDetailData->DevicePath);
@ -653,12 +650,13 @@ winutil_get_removable_drives(PyObject *self, PyObject *args) {
key = PyBytes_FromFormat("%c", (char)g_drives[j].letter);
if (key == NULL) return PyErr_NoMemory();
PyDict_SetItem(volumes, key, candidates);
Py_DECREF(candidates);
Py_DECREF(key); key = NULL;
break;
}
}
}
Py_XDECREF(candidates); candidates = NULL;
PyMem_Free(interfaceDetailData);
} //for
@ -672,7 +670,8 @@ winutil_get_usb_devices(PyObject *self, PyObject *args) {
HDEVINFO hDevInfo;
DWORD i; BOOL iterate = TRUE;
PyObject *devices, *temp = (PyObject *)1;
LPTSTR buffer;
LPWSTR buffer;
BOOL ok = 1;
if (!PyArg_ParseTuple(args, "")) return NULL;
@ -682,8 +681,10 @@ winutil_get_usb_devices(PyObject *self, PyObject *args) {
// Create a Device information set with all USB devices
hDevInfo = create_device_info_set(NULL, L"USB", 0,
DIGCF_PRESENT | DIGCF_ALLCLASSES);
if (hDevInfo == INVALID_HANDLE_VALUE)
if (hDevInfo == INVALID_HANDLE_VALUE) {
Py_DECREF(devices);
return NULL;
}
// Enumerate through the set
for (i=0; iterate; i++) {
buffer = get_registry_property(hDevInfo, i, SPDRP_HARDWAREID, &iterate);
@ -691,16 +692,17 @@ winutil_get_usb_devices(PyObject *self, PyObject *args) {
PyErr_Print(); continue;
}
buffersize = wcslen(buffer);
for (j = 0; j < buffersize; j++) buffer[j] = tolower(buffer[j]);
for (j = 0; j < buffersize; j++) buffer[j] = towlower(buffer[j]);
temp = PyUnicode_FromWideChar(buffer, buffersize);
PyMem_Free(buffer);
if (temp == NULL) {
PyErr_NoMemory();
ok = 0;
break;
}
PyList_Append(devices, temp);
PyList_Append(devices, temp); Py_DECREF(temp); temp = NULL;
} //for
if (temp == NULL) { Py_DECREF(devices); devices = NULL; }
if (!ok) { Py_DECREF(devices); devices = NULL; }
SetupDiDestroyDeviceInfoList(hDevInfo);
return devices;
}
@ -711,7 +713,7 @@ winutil_is_usb_device_connected(PyObject *self, PyObject *args) {
unsigned int vid, pid;
HDEVINFO hDevInfo;
DWORD i; BOOL iterate = TRUE;
LPTSTR buffer;
LPWSTR buffer;
int found = FALSE;
PyObject *ans;