This commit is contained in:
GRiker 2012-07-02 05:24:03 -06:00
commit f1e89942b3
32 changed files with 323 additions and 394 deletions

View File

@ -2,19 +2,19 @@ from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1325006965(BasicNewsRecipe): class AdvancedUserRecipe1325006965(BasicNewsRecipe):
title = u'FHM UK' title = u'FHM UK'
description = 'Good News for Men' description = 'Good News for Men.'
cover_url = 'http://www.greatmagazines.co.uk/covers/large/w197/current/fhm.jpg' cover_url = 'http://www.greatmagazines.co.uk/covers/large/w197/current/fhm.jpg'
# cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/373529_38324934806_64930243_n.jpg' # cover_url = 'http://profile.ak.fbcdn.net/hprofile-ak-snc4/373529_38324934806_64930243_n.jpg'
masthead_url = 'http://www.fhm.com/App_Resources/Images/Site/re-design/logo.gif' masthead_url = 'http://www.fhm.com/App_Resources/Images/Site/re-design/logo.gif'
__author__ = 'Dave Asbury' __author__ = 'Dave Asbury'
# last updated 14/4/12 # last updated 1/7/12
language = 'en_GB' language = 'en_GB'
oldest_article = 28 oldest_article = 28
max_articles_per_feed = 12 max_articles_per_feed = 8
remove_empty_feeds = True remove_empty_feeds = True
no_stylesheets = True no_stylesheets = True
#auto_cleanup = True #auto_cleanup = True
#articles_are_obfuscated = True # articles_are_obfuscated = True
keep_only_tags = [ keep_only_tags = [
dict(name='h1'), dict(name='h1'),
dict(name='img',attrs={'id' : 'ctl00_Body_imgMainImage'}), dict(name='img',attrs={'id' : 'ctl00_Body_imgMainImage'}),
@ -28,11 +28,18 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
#] #]
feeds = [ feeds = [
(u'From the Homepage',u'http://feed43.com/0032328550253453.xml'), (u'Homepage 1',u'http://feed43.com/6655867614547036.xml'),
#http://feed43.com/8053226782885416.xml'), (u'Homepage 2',u'http://feed43.com/4167731873103110.xml'),
(u'Funny - The Very Best Of The Internet',u'http://feed43.com/4538510106331565.xml'), (u'Homepage 3',u'http://feed43.com/7667138788771570.xml'),
(u'Upgrade',u'http://feed43.com/0877305847443234.xml'), (u'Homepage 4',u'http://feed43.com/6550421522527341.xml'),
#(u'The Final Countdown', u'http://feed43.com/3576106158530118.xml'), (u'Funny - The Very Best Of The Internet',u'http://feed43.com/4538510106331565.xml'),
#(u'Gaming',u'http://feed43.com/0755006465351035.xml'), (u'Gaming',u'http://feed43.com/6537162612465672.xml'),
(u'Gaming',u'http://feed43.com/6537162612465672.xml'), (u'Girls',u'http://feed43.com/3674777224513254.xml'),
] ]
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
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

@ -3,8 +3,8 @@ __license__ = 'GPL v3'
__copyright__ = '04 December 2010, desUBIKado' __copyright__ = '04 December 2010, desUBIKado'
__author__ = 'desUBIKado' __author__ = 'desUBIKado'
__description__ = 'Daily newspaper from Aragon' __description__ = 'Daily newspaper from Aragon'
__version__ = 'v0.04' __version__ = 'v0.05'
__date__ = '6, Januery 2011' __date__ = '5, Februery 2012'
''' '''
[url]http://www.heraldo.es/[/url] [url]http://www.heraldo.es/[/url]
''' '''
@ -38,7 +38,7 @@ class heraldo(BasicNewsRecipe):
keep_only_tags = [dict(name='div', attrs={'id':['dts','com']})] keep_only_tags = [dict(name='div', attrs={'id':['dts','com']})]
remove_tags = [dict(name='a', attrs={'class':['com flo-r','enl-if','enl-df']}), remove_tags = [dict(name='a', attrs={'class':['com flo-r','enl-if','enl-df']}),
dict(name='div', attrs={'class':['brb-b-s con marg-btt','cnt-rel con']}), dict(name='div', attrs={'class':['brb-b-s con marg-btt','cnt-rel con','col5-f1']}),
dict(name='form', attrs={'class':'form'}), dict(name='form', attrs={'class':'form'}),
dict(name='ul', attrs={'id':['cont-tags','pag-1']})] dict(name='ul', attrs={'id':['cont-tags','pag-1']})]
@ -72,6 +72,9 @@ class heraldo(BasicNewsRecipe):
preprocess_regexps = [ preprocess_regexps = [
# To separate the comments with a blank line # Para separar los comentarios con una linea en blanco
(re.compile(r'<div id="com"', re.DOTALL|re.IGNORECASE), lambda match: '<br><div id="com"') (re.compile(r'<div id="com"', re.DOTALL|re.IGNORECASE), lambda match: '<br><div id="com"')
] ]

View File

@ -18,7 +18,7 @@ class TheAge(BasicNewsRecipe):
publication_type = 'newspaper' publication_type = 'newspaper'
__author__ = 'Matthew Briggs' __author__ = 'Matthew Briggs'
language = 'en_AU' language = 'en_AU'
max_articles_per_feed = 1000 max_articles_per_feed = 1000
recursions = 0 recursions = 0
remove_tags = [dict(name=['table', 'script', 'noscript', 'style']), dict(name='a', attrs={'href':'/'}), dict(name='a', attrs={'href':'/text/'})] remove_tags = [dict(name=['table', 'script', 'noscript', 'style']), dict(name='a', attrs={'href':'/'}), dict(name='a', attrs={'href':'/text/'})]
@ -47,18 +47,19 @@ class TheAge(BasicNewsRecipe):
if url.startswith('/'): if url.startswith('/'):
url = 'http://www.theage.com.au' + url url = 'http://www.theage.com.au' + url
title = self.tag_to_string(tag) title = self.tag_to_string(tag)
sections[section].append({ if url != 'http://www.theage.com.au':
'title': title, sections[section].append({
'url' : url, 'title': title,
'date' : strftime('%a, %d %b'), 'url' : url,
'description' : '', 'date' : strftime('%a, %d %b'),
'content' : '', 'description' : '',
}) 'content' : '',
})
feeds = [] feeds = []
# Insert feeds in specified order, if available # Insert feeds in specified order, if available
feedSort = [ 'National', 'World', 'Opinion', 'Columns', 'Business', 'Sport', 'Entertainment' ] feedSort = [ 'National', 'World', 'Opinion', 'Columns', 'Business', 'Sport', 'Entertainment' ]
for i in feedSort: for i in feedSort:
if i in sections: if i in sections:
@ -68,12 +69,12 @@ class TheAge(BasicNewsRecipe):
for i in feedSort: for i in feedSort:
del sections[i] del sections[i]
# Append what is left over... # Append what is left over...
for i in sections: for i in sections:
feeds.append((i,sections[i])) feeds.append((i,sections[i]))
return feeds return feeds
def get_cover_url(self): def get_cover_url(self):
@ -88,9 +89,9 @@ class TheAge(BasicNewsRecipe):
return None return None
def preprocess_html(self,soup): def preprocess_html(self,soup):
for p in soup.findAll('p'): for p in soup.findAll('p'):
# Collapse the paragraph by joining the non-tag contents # Collapse the paragraph by joining the non-tag contents
contents = [i for i in p.contents if isinstance(i,unicode)] contents = [i for i in p.contents if isinstance(i,unicode)]
@ -103,10 +104,10 @@ class TheAge(BasicNewsRecipe):
p.extract() p.extract()
continue continue
# Shrink the fine print font # Shrink the fine print font
if contents=='This material is subject to copyright and any unauthorised use, copying or mirroring is prohibited.': if contents=='This material is subject to copyright and any unauthorised use, copying or mirroring is prohibited.':
p['style'] = 'font-size:small' p['style'] = 'font-size:small'
continue continue
return soup return soup

View File

@ -2,8 +2,8 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '4 February 2011, desUBIKado' __copyright__ = '4 February 2011, desUBIKado'
__author__ = 'desUBIKado' __author__ = 'desUBIKado'
__version__ = 'v0.07' __version__ = 'v0.08'
__date__ = '13, November 2011' __date__ = '30, June 2012'
''' '''
http://www.weblogssl.com/ http://www.weblogssl.com/
''' '''
@ -33,6 +33,7 @@ class weblogssl(BasicNewsRecipe):
feeds = [ feeds = [
(u'Xataka', u'http://feeds.weblogssl.com/xataka2') (u'Xataka', u'http://feeds.weblogssl.com/xataka2')
,(u'Xataka Smart Home', u'http://feeds.weblogssl.com/Xatakahome')
,(u'Xataka Mexico', u'http://feeds.weblogssl.com/xatakamx') ,(u'Xataka Mexico', u'http://feeds.weblogssl.com/xatakamx')
,(u'Xataka M\xf3vil', u'http://feeds.weblogssl.com/xatakamovil') ,(u'Xataka M\xf3vil', u'http://feeds.weblogssl.com/xatakamovil')
,(u'Xataka Android', u'http://feeds.weblogssl.com/xatakandroid') ,(u'Xataka Android', u'http://feeds.weblogssl.com/xatakandroid')
@ -107,12 +108,14 @@ class weblogssl(BasicNewsRecipe):
# Para obtener la url original del articulo a partir de la de "feedsportal" # Para obtener la url original del articulo a partir de la de "feedsportal"
# El siguiente código es gracias al usuario "bosplans" de www.mobileread.com # El siguiente código es gracias al usuario "bosplans" de www.mobileread.com
# http://www.mobileread.com/forums/showthread.php?t=130297 # http://www.mobileread.com/forums/sho...d.php?t=130297
def get_article_url(self, article): def get_article_url(self, article):
link = article.get('link', None) link = article.get('link', None)
if link is None: if link is None:
return article return article
if link.split('/')[-4]=="xataka2":
return article.get('feedburner_origlink', article.get('link', article.get('guid')))
if link.split('/')[-1]=="story01.htm": if link.split('/')[-1]=="story01.htm":
link=link.split('/')[-2] link=link.split('/')[-2]
a=['0B','0C','0D','0E','0F','0G','0N' ,'0L0S','0A'] a=['0B','0C','0D','0E','0F','0G','0N' ,'0L0S','0A']
@ -121,6 +124,3 @@ class weblogssl(BasicNewsRecipe):
link=link.replace(a[i],b[i]) link=link.replace(a[i],b[i])
link="http://"+link link="http://"+link
return link return link

Binary file not shown.

View File

@ -21,6 +21,7 @@ defaults.
# last_free - First available integer smaller than the largest existing number # last_free - First available integer smaller than the largest existing number
# Return largest existing + 1 if no free number is found # Return largest existing + 1 if no free number is found
# const - Assign the number 1 always # const - Assign the number 1 always
# no_change - Do not change the series index
# a number - Assign that number always. The number is not in quotes. Note that # a number - Assign that number always. The number is not in quotes. Note that
# 0.0 can be used here. # 0.0 can be used here.
# Examples: # Examples:

View File

@ -97,7 +97,7 @@ Now, run configure and make::
-no-plugin-manifests is needed so that loading the plugins does not fail looking for the CRT assembly -no-plugin-manifests is needed so that loading the plugins does not fail looking for the CRT assembly
configure -opensource -release -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake configure -ltcg -opensource -release -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake
Add the path to the bin folder inside the Qt dir to your system PATH. Add the path to the bin folder inside the Qt dir to your system PATH.

View File

@ -643,7 +643,7 @@ from calibre.devices.cybook.driver import CYBOOK, ORIZON
from calibre.devices.eb600.driver import (EB600, COOL_ER, SHINEBOOK, from calibre.devices.eb600.driver import (EB600, COOL_ER, SHINEBOOK,
POCKETBOOK360, GER2, ITALICA, ECLICTO, DBOOK, INVESBOOK, POCKETBOOK360, GER2, ITALICA, ECLICTO, DBOOK, INVESBOOK,
BOOQ, ELONEX, POCKETBOOK301, MENTOR, POCKETBOOK602, BOOQ, ELONEX, POCKETBOOK301, MENTOR, POCKETBOOK602,
POCKETBOOK701, POCKETBOOK360P, PI2) POCKETBOOK701, POCKETBOOK360P, PI2, POCKETBOOK622)
from calibre.devices.iliad.driver import ILIAD from calibre.devices.iliad.driver import ILIAD
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800 from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
from calibre.devices.jetbook.driver import (JETBOOK, MIBUK, JETBOOK_MINI, from calibre.devices.jetbook.driver import (JETBOOK, MIBUK, JETBOOK_MINI,
@ -689,7 +689,7 @@ plugins += [
JETBOOK, JETBOOK_MINI, MIBUK, JETBOOK_COLOR, JETBOOK, JETBOOK_MINI, MIBUK, JETBOOK_COLOR,
SHINEBOOK, SHINEBOOK,
POCKETBOOK360, POCKETBOOK301, POCKETBOOK602, POCKETBOOK701, POCKETBOOK360P, POCKETBOOK360, POCKETBOOK301, POCKETBOOK602, POCKETBOOK701, POCKETBOOK360P,
PI2, POCKETBOOK622, PI2,
KINDLE, KINDLE2, KINDLE_DX, KINDLE_FIRE, KINDLE, KINDLE2, KINDLE_DX, KINDLE_FIRE,
NOOK, NOOK_COLOR, NOOK, NOOK_COLOR,
PRS505, PRST1, PRS505, PRST1,
@ -1511,15 +1511,6 @@ class StoreOpenBooksStore(StoreBase):
drm_free_only = True drm_free_only = True
headquarters = 'US' headquarters = 'US'
class StoreOReillyStore(StoreBase):
name = 'OReilly'
description = u'Programming and tech ebooks from OReilly.'
actual_plugin = 'calibre.gui2.store.stores.oreilly_plugin:OReillyStore'
drm_free_only = True
headquarters = 'US'
formats = ['APK', 'DAISY', 'EPUB', 'MOBI', 'PDF']
class StoreOzonRUStore(StoreBase): class StoreOzonRUStore(StoreBase):
name = 'OZON.ru' name = 'OZON.ru'
description = u'ebooks from OZON.ru' description = u'ebooks from OZON.ru'
@ -1659,7 +1650,6 @@ plugins += [
StoreMobileReadStore, StoreMobileReadStore,
StoreNextoStore, StoreNextoStore,
StoreOpenBooksStore, StoreOpenBooksStore,
StoreOReillyStore,
StoreOzonRUStore, StoreOzonRUStore,
StorePragmaticBookshelfStore, StorePragmaticBookshelfStore,
StoreRW2010Store, StoreRW2010Store,

View File

@ -101,6 +101,7 @@ class ANDROID(USBMS):
0x685b : [0x0400, 0x0226], 0x685b : [0x0400, 0x0226],
0x685e : [0x0400], 0x685e : [0x0400],
0x6860 : [0x0400], 0x6860 : [0x0400],
0x6863 : [0x226],
0x6877 : [0x0400], 0x6877 : [0x0400],
0x689e : [0x0400], 0x689e : [0x0400],
0xdeed : [0x0222], 0xdeed : [0x0222],
@ -114,7 +115,6 @@ class ANDROID(USBMS):
0xc004 : [0x0226], 0xc004 : [0x0226],
0x8801 : [0x0226, 0x0227], 0x8801 : [0x0226, 0x0227],
0xe115 : [0x0216], # PocketBook A10 0xe115 : [0x0216], # PocketBook A10
0xe107 : [0x326], # PocketBook 622
}, },
# Acer # Acer

View File

@ -251,6 +251,19 @@ class POCKETBOOK602(USBMS):
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['PB602', 'PB603', 'PB902', WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['PB602', 'PB603', 'PB902',
'PB903', 'PB'] 'PB903', 'PB']
class POCKETBOOK622(POCKETBOOK602):
name = 'PocketBook 622 Device Interface'
description = _('Communicate with the PocketBook 622 reader.')
EBOOK_DIR_MAIN = ''
VENDOR_ID = [0x0489]
PRODUCT_ID = [0xe107]
BCD = [0x0326]
VENDOR_NAME = 'LINUX'
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'FILE-STOR_GADGET'
class POCKETBOOK360P(POCKETBOOK602): class POCKETBOOK360P(POCKETBOOK602):
name = 'PocketBook 360+ Device Interface' name = 'PocketBook 360+ Device Interface'

View File

@ -152,27 +152,31 @@ class CHMInput(InputFormatPlugin):
#print "=============================" #print "============================="
log.debug('Found %d section nodes' % len(chapters)) log.debug('Found %d section nodes' % len(chapters))
htmlpath = os.path.splitext(hhcpath)[0] + ".html" htmlpath = os.path.splitext(hhcpath)[0] + ".html"
f = open(htmlpath, 'wb') with open(htmlpath, 'wb') as f:
if chapters: if chapters:
f.write('<html><head><meta http-equiv="Content-type"' f.write('<html><head><meta http-equiv="Content-type"'
' content="text/html;charset=UTF-8" /></head><body>\n') ' content="text/html;charset=UTF-8" /></head><body>\n')
path0 = chapters[0][1] path0 = chapters[0][1]
subpath = os.path.dirname(path0) subpath = os.path.dirname(path0)
base = os.path.dirname(f.name)
for chapter in chapters: for chapter in chapters:
title = chapter[0] title = chapter[0]
rsrcname = os.path.basename(chapter[1]) rsrcname = os.path.basename(chapter[1])
rsrcpath = os.path.join(subpath, rsrcname) rsrcpath = os.path.join(subpath, rsrcname)
# title should already be url encoded if (not os.path.exists(os.path.join(base, rsrcpath)) and
url = "<br /><a href=" + rsrcpath + ">" + title + " </a>\n" os.path.exists(os.path.join(base, chapter[1]))):
if isinstance(url, unicode): rsrcpath = chapter[1]
url = url.encode('utf-8')
f.write(url)
f.write("</body></html>") # title should already be url encoded
else: url = "<br /><a href=" + rsrcpath + ">" + title + " </a>\n"
f.write(hhcdata) if isinstance(url, unicode):
f.close() url = url.encode('utf-8')
f.write(url)
f.write("</body></html>")
else:
f.write(hhcdata)
return htmlpath return htmlpath

View File

@ -54,30 +54,35 @@ class Ozon(Source):
# for ozon.ru search we have to format ISBN with '-' # for ozon.ru search we have to format ISBN with '-'
isbn = _format_isbn(log, identifiers.get('isbn', None)) isbn = _format_isbn(log, identifiers.get('isbn', None))
# TODO: format isbn! ozonid = identifiers.get('ozon', None)
qItems = set([isbn, title])
if authors: unk = unicode(_('Unknown')).upper()
qItems |= frozenset(authors) if (title and title != unk) or (authors and authors != [unk]) or isbn or not ozonid:
qItems.discard(None) qItems = set([isbn, title])
qItems.discard('') if authors:
qItems = map(_quoteString, qItems) qItems |= frozenset(authors)
qItems.discard(None)
q = u' '.join(qItems).strip() qItems.discard('')
log.info(u'search string: ' + q) qItems = map(_quoteString, qItems)
if isinstance(q, unicode): q = u' '.join(qItems).strip()
q = q.encode('utf-8') log.info(u'search string: ' + q)
if not q:
return None if isinstance(q, unicode):
q = q.encode('utf-8')
search_url += quote_plus(q) if not q:
return None
search_url += quote_plus(q)
else:
search_url = self.ozon_url + '/webservices/OzonWebSvc.asmx/ItemDetail?ID=%s' % ozonid
log.debug(u'search url: %r'%search_url) log.debug(u'search url: %r'%search_url)
return search_url return search_url
# }}} # }}}
def identify(self, log, result_queue, abort, title=None, authors=None, def identify(self, log, result_queue, abort, title=None, authors=None,
identifiers={}, timeout=30): # {{{ identifiers={}, timeout=60): # {{{
from lxml import etree from lxml import etree
from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.chardet import xml_to_unicode
@ -99,7 +104,7 @@ class Ozon(Source):
try: try:
parser = etree.XMLParser(recover=True, no_network=True) parser = etree.XMLParser(recover=True, no_network=True)
feed = etree.fromstring(xml_to_unicode(raw, strip_encoding_pats=True, assume_utf8=True)[0], parser=parser) feed = etree.fromstring(xml_to_unicode(raw, strip_encoding_pats=True, assume_utf8=True)[0], parser=parser)
entries = feed.xpath('//*[local-name() = "SearchItems"]') entries = feed.xpath('//*[local-name()="SearchItems" or local-name()="ItemDetail"]')
if entries: if entries:
metadata = self.get_metadata(log, entries, title, authors, identifiers) metadata = self.get_metadata(log, entries, title, authors, identifiers)
self.get_all_details(log, metadata, abort, result_queue, identifiers, timeout) self.get_all_details(log, metadata, abort, result_queue, identifiers, timeout)
@ -112,8 +117,8 @@ class Ozon(Source):
def get_metadata(self, log, entries, title, authors, identifiers): # {{{ def get_metadata(self, log, entries, title, authors, identifiers): # {{{
# some book titles have extra characters like this # some book titles have extra characters like this
# TODO: make a twick # TODO: make a twick
reRemoveFromTitle = None #reRemoveFromTitle = None
#reRemoveFromTitle = re.compile(r'[?!:.,;+-/&%"\'=]') reRemoveFromTitle = re.compile(r'[?!:.,;+-/&%"\'=]')
title = unicode(title).upper() if title else '' title = unicode(title).upper() if title else ''
if reRemoveFromTitle: if reRemoveFromTitle:
@ -163,7 +168,7 @@ class Ozon(Source):
metadata.append(mi) metadata.append(mi)
#log.debug(u'added metadata %s %s.'%(mi.title, mi.authors)) #log.debug(u'added metadata %s %s.'%(mi.title, mi.authors))
else: else:
log.debug(u'skipped metadata %s %s. (does not match the query)'%(mi.title, mi.authors)) log.debug(u'skipped metadata %s %s. (does not match the query)'%(unicode(mi.title), mi.authors))
return metadata return metadata
# }}} # }}}
@ -301,7 +306,7 @@ class Ozon(Source):
if series: if series:
metadata.series = series metadata.series = series
xpt = u'normalize-space(substring-after(//meta[@name="description"]/@content, "ISBN"))' xpt = u'normalize-space(//*[@class="product-detail"]//text()[starts-with(., "ISBN")])'
isbn_str = doc.xpath(xpt) isbn_str = doc.xpath(xpt)
if isbn_str: if isbn_str:
all_isbns = [check_isbn(isbn) for isbn in self.isbnRegex.findall(isbn_str) if _verifyISBNIntegrity(log, isbn)] all_isbns = [check_isbn(isbn) for isbn in self.isbnRegex.findall(isbn_str) if _verifyISBNIntegrity(log, isbn)]
@ -326,7 +331,7 @@ class Ozon(Source):
# can be set before from xml search responce # can be set before from xml search responce
if not metadata.pubdate: if not metadata.pubdate:
xpt = u'normalize-space(//div[@class="product-misc"]//text()[contains(., "г.")])' xpt = u'normalize-space(substring-after(//div[@class="product-detail"]//text()[contains(., "г.")],";"))'
yearIn = doc.xpath(xpt) yearIn = doc.xpath(xpt)
if yearIn: if yearIn:
matcher = re.search(r'\d{4}', yearIn) matcher = re.search(r'\d{4}', yearIn)
@ -334,17 +339,20 @@ class Ozon(Source):
metadata.pubdate = toPubdate(log, matcher.group(0)) metadata.pubdate = toPubdate(log, matcher.group(0))
# overwrite comments from HTML if any # overwrite comments from HTML if any
xpt = u'//table[@id="detail_description"]//tr/td' xpt = u'//*[@id="detail_description"]//*[contains(text(), "От производителя")]/../node()[not(self::comment())][not(self::br)][preceding::*[contains(text(), "От производителя")]]'
from lxml.etree import ElementBase
comment_elem = doc.xpath(xpt) comment_elem = doc.xpath(xpt)
if comment_elem: if comment_elem:
comments = unicode(etree.tostring(comment_elem[0], encoding=unicode)) comments = u''
if comments: for node in comment_elem:
# cleanup root tag, TODO: remove tags like object/embeded if isinstance(node, ElementBase):
comments = re.sub(ur'\A.*?<td.*?>|</td>.*\Z', u'', comments.strip(), re.MULTILINE).strip() comments += unicode(etree.tostring(node, encoding=unicode))
if comments and (not metadata.comments or len(comments) > len(metadata.comments)): elif isinstance(node, basestring) and node.strip():
metadata.comments = comments comments += unicode(node) + u'\n'
else: if comments and (not metadata.comments or len(comments) > len(metadata.comments)):
log.debug('HTML book description skipped in favour of search service xml responce') metadata.comments = comments
else:
log.debug('HTML book description skipped in favour of search service xml responce')
else: else:
log.debug('No book description found in HTML') log.debug('No book description found in HTML')
# }}} # }}}
@ -430,7 +438,8 @@ def _translageLanguageToCode(displayLang): # {{{
u'Китайский': 'zh', u'Китайский': 'zh',
u'Японский': 'ja', u'Японский': 'ja',
u'Финский' : 'fi', u'Финский' : 'fi',
u'Польский' : 'pl',} u'Польский' : 'pl',
u'Украинский' : 'uk',}
return langTbl.get(displayLang, None) return langTbl.get(displayLang, None)
# }}} # }}}
@ -454,7 +463,7 @@ def toPubdate(log, yearAsString): # {{{
res = None res = None
if yearAsString: if yearAsString:
try: try:
res = parse_only_date(yearAsString) res = parse_only_date(u"01.01." + yearAsString)
except: except:
log.error('cannot parse to date %s'%yearAsString) log.error('cannot parse to date %s'%yearAsString)
return res return res

View File

@ -66,6 +66,7 @@ class PagedDisplay
this.in_paged_mode = false this.in_paged_mode = false
this.current_margin_side = 0 this.current_margin_side = 0
this.is_full_screen_layout = false this.is_full_screen_layout = false
this.max_col_width = -1
set_geometry: (cols_per_screen=1, margin_top=20, margin_side=40, margin_bottom=20) -> set_geometry: (cols_per_screen=1, margin_top=20, margin_side=40, margin_bottom=20) ->
this.margin_top = margin_top this.margin_top = margin_top
@ -108,6 +109,11 @@ class PagedDisplay
# Minimum column width, for the cases when the window is too # Minimum column width, for the cases when the window is too
# narrow # narrow
col_width = Math.max(100, ((ww - adjust)/n) - 2*sm) col_width = Math.max(100, ((ww - adjust)/n) - 2*sm)
if this.max_col_width > 0 and col_width > this.max_col_width
# Increase the side margin to ensure that col_width is no larger
# than max_col_width
sm += Math.ceil( (col_width - this.max_col_width) / 2*n )
col_width = Math.max(100, ((ww - adjust)/n) - 2*sm)
this.page_width = col_width + 2*sm this.page_width = col_width + 2*sm
this.screen_width = this.page_width * this.cols_per_screen this.screen_width = this.page_width * this.cols_per_screen
@ -360,5 +366,4 @@ if window?
# TODO: # TODO:
# Resizing of images # Resizing of images
# Full screen mode
# Highlight on jump_to_anchor # Highlight on jump_to_anchor

View File

@ -13,7 +13,7 @@ from lxml import etree
from calibre import guess_type, strftime from calibre import guess_type, strftime
from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML, xml2text, urldefrag
from calibre.library.comments import comments_to_html from calibre.library.comments import comments_to_html
from calibre.utils.date import is_date_undefined from calibre.utils.date import is_date_undefined
from calibre.ebooks.chardet import strip_encoding_declarations from calibre.ebooks.chardet import strip_encoding_declarations
@ -41,11 +41,25 @@ class Jacket(object):
return removed return removed
def remove_first_image(self): def remove_first_image(self):
deleted_item = None
for item in self.oeb.spine: for item in self.oeb.spine:
removed = self.remove_images(item) removed = self.remove_images(item)
if removed > 0: if removed > 0:
self.log('Removed first image') self.log('Removed first image')
body = XPath('//h:body')(item.data)
if body:
raw = xml2text(body[0]).strip()
imgs = XPath('//h:img|//svg:svg')(item.data)
if not raw and not imgs:
self.log('Removing %s as it has no content'%item.href)
self.oeb.manifest.remove(item)
deleted_item = item
break break
if deleted_item is not None:
for item in list(self.oeb.toc):
href = urldefrag(item.href)[0]
if href == deleted_item.href:
self.oeb.toc.remove(item)
def insert_metadata(self, mi): def insert_metadata(self, mi):
self.log('Inserting metadata into book...') self.log('Inserting metadata into book...')

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt, from PyQt4.Qt import (QLineEdit, QAbstractListModel, Qt,
QApplication, QCompleter, pyqtSignal) QApplication, QCompleter)
from calibre.utils.icu import sort_key, lower from calibre.utils.icu import sort_key, lower
from calibre.gui2 import NONE from calibre.gui2 import NONE
@ -56,7 +56,7 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM):
to complete non multiple fields as well. to complete non multiple fields as well.
''' '''
def __init__(self, parent=None): def __init__(self, parent=None, completer_widget=None):
QLineEdit.__init__(self, parent) QLineEdit.__init__(self, parent)
self.sep = ',' self.sep = ','
@ -66,7 +66,7 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM):
self._model = CompleteModel(parent=self) self._model = CompleteModel(parent=self)
self._completer = c = QCompleter(self._model, self) self._completer = c = QCompleter(self._model, self)
c.setWidget(self) c.setWidget(self if completer_widget is None else completer_widget)
c.setCompletionMode(QCompleter.PopupCompletion) c.setCompletionMode(QCompleter.PopupCompletion)
c.setCaseSensitivity(Qt.CaseInsensitive) c.setCaseSensitivity(Qt.CaseInsensitive)
c.setModelSorting(self._model.sorting) c.setModelSorting(self._model.sorting)
@ -158,21 +158,15 @@ class MultiCompleteLineEdit(QLineEdit, LineEditECM):
class MultiCompleteComboBox(EnComboBox): class MultiCompleteComboBox(EnComboBox):
clear_edit_text = pyqtSignal()
def __init__(self, *args): def __init__(self, *args):
EnComboBox.__init__(self, *args) EnComboBox.__init__(self, *args)
self.setLineEdit(MultiCompleteLineEdit(self)) self.le = MultiCompleteLineEdit(self, completer_widget=self)
# Needed to allow changing the case of an existing item self.setLineEdit(self.le)
# otherwise on focus out, the text is changed to the
# item that matches case insensitively def showPopup(self):
c = self.lineEdit().completer() c = self.le._completer
c.setCaseSensitivity(Qt.CaseSensitive) c.setCompletionPrefix('')
self.dummy_model = CompleteModel(self) c.complete()
c.setModel(self.dummy_model)
self.lineEdit()._completer.setWidget(self)
self.clear_edit_text.connect(self.clearEditText,
type=Qt.QueuedConnection)
def update_items_cache(self, complete_items): def update_items_cache(self, complete_items):
self.lineEdit().update_items_cache(complete_items) self.lineEdit().update_items_cache(complete_items)
@ -187,18 +181,10 @@ class MultiCompleteComboBox(EnComboBox):
self.lineEdit().set_add_separator(what) self.lineEdit().set_add_separator(what)
def show_initial_value(self, what): def show_initial_value(self, what):
''' what = unicode(what) if what else u''
Show an initial value. Handle the case of the initial value being blank
correctly (on Qt 4.8.0 having a blank value causes the first value from
the completer to be shown, when the event loop runs).
'''
what = unicode(what)
le = self.lineEdit() le = self.lineEdit()
if not what.strip(): self.setEditText(what)
self.clear_edit_text.emit() le.selectAll()
else:
self.setEditText(what)
le.selectAll()
if __name__ == '__main__': if __name__ == '__main__':
from PyQt4.Qt import QDialog, QVBoxLayout from PyQt4.Qt import QDialog, QVBoxLayout
@ -207,5 +193,8 @@ if __name__ == '__main__':
d.setLayout(QVBoxLayout()) d.setLayout(QVBoxLayout())
le = MultiCompleteComboBox(d) le = MultiCompleteComboBox(d)
d.layout().addWidget(le) d.layout().addWidget(le)
le.all_items = ['one', 'otwo', 'othree', 'ooone', 'ootwo', 'oothree'] items = ['one', 'otwo', 'othree', 'ooone', 'ootwo',
'oothree']
le.update_items_cache(items)
le.show_initial_value('')
d.exec_() d.exec_()

View File

@ -12,8 +12,8 @@ from PyQt4.Qt import QPixmap, SIGNAL
from calibre.gui2 import choose_images, error_dialog from calibre.gui2 import choose_images, error_dialog
from calibre.gui2.convert.metadata_ui import Ui_Form from calibre.gui2.convert.metadata_ui import Ui_Form
from calibre.ebooks.metadata import (authors_to_string, string_to_authors, from calibre.ebooks.metadata import (string_to_authors, MetaInformation,
MetaInformation, title_sort) title_sort)
from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2.convert import Widget from calibre.gui2.convert import Widget
@ -74,14 +74,12 @@ class MetadataWidget(Widget, Ui_Form):
mi = self.db.get_metadata(self.book_id, index_is_id=True) mi = self.db.get_metadata(self.book_id, index_is_id=True)
self.title.setText(mi.title) self.title.setText(mi.title)
if mi.publisher: self.publisher.show_initial_value(mi.publisher if mi.publisher else '')
self.publisher.setCurrentIndex(self.publisher.findText(mi.publisher))
self.author_sort.setText(mi.author_sort if mi.author_sort else '') self.author_sort.setText(mi.author_sort if mi.author_sort else '')
self.tags.setText(', '.join(mi.tags if mi.tags else [])) self.tags.setText(', '.join(mi.tags if mi.tags else []))
self.tags.update_items_cache(self.db.all_tags()) self.tags.update_items_cache(self.db.all_tags())
self.comment.html = comments_to_html(mi.comments) if mi.comments else '' self.comment.html = comments_to_html(mi.comments) if mi.comments else ''
if mi.series: self.series.show_initial_value(mi.series if mi.series else '')
self.series.setCurrentIndex(self.series.findText(mi.series))
if mi.series_index is not None: if mi.series_index is not None:
try: try:
self.series_index.setValue(mi.series_index) self.series_index.setValue(mi.series_index)
@ -118,16 +116,11 @@ class MetadataWidget(Widget, Ui_Form):
self.author.set_add_separator(tweaks['authors_completer_append_separator']) self.author.set_add_separator(tweaks['authors_completer_append_separator'])
self.author.update_items_cache(self.db.all_author_names()) self.author.update_items_cache(self.db.all_author_names())
for i in all_authors:
id, name = i
name = authors_to_string([name.strip().replace('|', ',') for n in name.split(',')])
self.author.addItem(name)
au = self.db.authors(self.book_id, True) au = self.db.authors(self.book_id, True)
if not au: if not au:
au = _('Unknown') au = _('Unknown')
au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')]) au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')])
self.author.setEditText(au) self.author.show_initial_value(au)
def initialize_series(self): def initialize_series(self):
all_series = self.db.all_series() all_series = self.db.all_series()
@ -135,22 +128,12 @@ class MetadataWidget(Widget, Ui_Form):
self.series.set_separator(None) self.series.set_separator(None)
self.series.update_items_cache([x[1] for x in all_series]) self.series.update_items_cache([x[1] for x in all_series])
for i in all_series:
id, name = i
self.series.addItem(name)
self.series.setCurrentIndex(-1)
def initialize_publisher(self): def initialize_publisher(self):
all_publishers = self.db.all_publishers() all_publishers = self.db.all_publishers()
all_publishers.sort(key=lambda x : sort_key(x[1])) all_publishers.sort(key=lambda x : sort_key(x[1]))
self.publisher.set_separator(None) self.publisher.set_separator(None)
self.publisher.update_items_cache([x[1] for x in all_publishers]) self.publisher.update_items_cache([x[1] for x in all_publishers])
for i in all_publishers:
id, name = i
self.publisher.addItem(name)
self.publisher.setCurrentIndex(-1)
def get_title_and_authors(self): def get_title_and_authors(self):
title = unicode(self.title.text()).strip() title = unicode(self.title.text()).strip()
if not title: if not title:

View File

@ -314,14 +314,7 @@ class Text(Base):
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
self.setter(val) self.setter(val)
else: else:
idx = None self.widgets[1].show_initial_value(val)
for i, c in enumerate(values):
if c == val:
idx = i
self.widgets[1].addItem(c)
self.widgets[1].setEditText('')
if idx is not None:
self.widgets[1].setCurrentIndex(idx)
def setter(self, val): def setter(self, val):
if self.col_metadata['is_multiple']: if self.col_metadata['is_multiple']:
@ -396,16 +389,8 @@ class Series(Base):
self.initial_index = s_index self.initial_index = s_index
self.initial_val = val self.initial_val = val
val = self.normalize_db_val(val) val = self.normalize_db_val(val)
idx = None
self.name_widget.clear()
for i, c in enumerate(values):
if c == val:
idx = i
self.name_widget.addItem(c)
self.name_widget.update_items_cache(values) self.name_widget.update_items_cache(values)
self.name_widget.setEditText('') self.name_widget.show_initial_value(val)
if idx is not None:
self.widgets[1].setCurrentIndex(idx)
def getter(self): def getter(self):
n = unicode(self.name_widget.currentText()).strip() n = unicode(self.name_widget.currentText()).strip()
@ -860,8 +845,6 @@ class BulkSeries(BulkBase):
self.idx_widget.setChecked(False) self.idx_widget.setChecked(False)
self.main_widget.set_separator(None) self.main_widget.set_separator(None)
self.main_widget.update_items_cache(self.all_values) self.main_widget.update_items_cache(self.all_values)
for c in self.all_values:
self.main_widget.addItem(c)
self.main_widget.setEditText('') self.main_widget.setEditText('')
self.a_c_checkbox.setChecked(False) self.a_c_checkbox.setChecked(False)
@ -1005,15 +988,8 @@ class BulkText(BulkBase):
if not self.col_metadata['is_multiple']: if not self.col_metadata['is_multiple']:
val = self.get_initial_value(book_ids) val = self.get_initial_value(book_ids)
self.initial_val = val = self.normalize_db_val(val) self.initial_val = val = self.normalize_db_val(val)
idx = None
self.main_widget.blockSignals(True) self.main_widget.blockSignals(True)
for i, c in enumerate(self.all_values): self.main_widget.show_initial_value(val)
if c == val:
idx = i
self.main_widget.addItem(c)
self.main_widget.setEditText('')
if idx is not None:
self.main_widget.setCurrentIndex(idx)
self.main_widget.blockSignals(False) self.main_widget.blockSignals(False)
def commit(self, book_ids, notify=False): def commit(self, book_ids, notify=False):

View File

@ -6,8 +6,7 @@ __license__ = 'GPL v3'
from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \ from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \
QApplication, QSpinBox, QToolButton, QIcon QApplication, QSpinBox, QToolButton, QIcon
from calibre.ebooks.metadata import authors_to_string, string_to_authors from calibre.ebooks.metadata import string_to_authors
from calibre.utils.icu import sort_key
from calibre.gui2.complete import MultiCompleteComboBox from calibre.gui2.complete import MultiCompleteComboBox
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
@ -56,17 +55,10 @@ class AddEmptyBookDialog(QDialog):
self.authors_combo.setEditText(_('Unknown')) self.authors_combo.setEditText(_('Unknown'))
def initialize_authors(self, db, author): def initialize_authors(self, db, author):
all_authors = db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1]))
for i in all_authors:
id, name = i
name = [name.strip().replace('|', ',') for n in name.split(',')]
self.authors_combo.addItem(authors_to_string(name))
au = author au = author
if not au: if not au:
au = _('Unknown') au = _('Unknown')
self.authors_combo.setEditText(au.replace('|', ',')) self.authors_combo.show_initial_value(au.replace('|', ','))
self.authors_combo.set_separator('&') self.authors_combo.set_separator('&')
self.authors_combo.set_space_before_sep(True) self.authors_combo.set_space_before_sep(True)

View File

@ -261,8 +261,12 @@ class MyBlockingBusy(QDialog): # {{{
else: else:
next = self.db.get_next_series_num_for(series) next = self.db.get_next_series_num_for(series)
self.db.set_series(id, series, notify=False, commit=False) self.db.set_series(id, series, notify=False, commit=False)
num = next if do_autonumber and series else 1.0 if not series:
self.db.set_series_index(id, num, notify=False, commit=False) self.db.set_series_index(id, 1.0, notify=False, commit=False)
elif do_autonumber: # is True if do_series_restart is True
self.db.set_series_index(id, next, notify=False, commit=False)
elif tweaks['series_index_auto_increment'] != 'no_change':
self.db.set_series_index(id, 1.0, notify=False, commit=False)
if do_remove_conv: if do_remove_conv:
self.db.delete_conversion_options(id, 'PIPE', commit=False) self.db.delete_conversion_options(id, 'PIPE', commit=False)
@ -872,38 +876,25 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
all_authors = self.db.all_authors() all_authors = self.db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1])) all_authors.sort(key=lambda x : sort_key(x[1]))
for i in all_authors:
id, name = i
name = name.strip().replace('|', ',')
self.authors.addItem(name)
self.authors.setEditText('')
self.authors.set_separator('&') self.authors.set_separator('&')
self.authors.set_space_before_sep(True) self.authors.set_space_before_sep(True)
self.authors.set_add_separator(tweaks['authors_completer_append_separator']) self.authors.set_add_separator(tweaks['authors_completer_append_separator'])
self.authors.update_items_cache(self.db.all_author_names()) self.authors.update_items_cache(self.db.all_author_names())
self.authors.show_initial_value('')
def initialize_series(self): def initialize_series(self):
all_series = self.db.all_series() all_series = self.db.all_series()
all_series.sort(key=lambda x : sort_key(x[1])) all_series.sort(key=lambda x : sort_key(x[1]))
self.series.set_separator(None) self.series.set_separator(None)
self.series.update_items_cache([x[1] for x in all_series]) self.series.update_items_cache([x[1] for x in all_series])
self.series.show_initial_value('')
for i in all_series:
id, name = i
self.series.addItem(name)
self.series.setEditText('')
def initialize_publisher(self): def initialize_publisher(self):
all_publishers = self.db.all_publishers() all_publishers = self.db.all_publishers()
all_publishers.sort(key=lambda x : sort_key(x[1])) all_publishers.sort(key=lambda x : sort_key(x[1]))
self.publisher.set_separator(None) self.publisher.set_separator(None)
self.publisher.update_items_cache([x[1] for x in all_publishers]) self.publisher.update_items_cache([x[1] for x in all_publishers])
self.publisher.show_initial_value('')
for i in all_publishers:
id, name = i
self.publisher.addItem(name)
self.publisher.setEditText('')
def tag_editor(self, *args): def tag_editor(self, *args):
d = TagEditor(self, self.db, None) d = TagEditor(self, self.db, None)

View File

@ -25,10 +25,6 @@ class SearchDialog(QDialog, Ui_Dialog):
all_authors = db.all_authors() all_authors = db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1])) all_authors.sort(key=lambda x : sort_key(x[1]))
for i in all_authors:
id, name = i
name = name.strip().replace('|', ',')
self.authors_box.addItem(name)
self.authors_box.setEditText('') self.authors_box.setEditText('')
self.authors_box.set_separator('&') self.authors_box.set_separator('&')
self.authors_box.set_space_before_sep(True) self.authors_box.set_space_before_sep(True)
@ -39,10 +35,7 @@ class SearchDialog(QDialog, Ui_Dialog):
all_series.sort(key=lambda x : sort_key(x[1])) all_series.sort(key=lambda x : sort_key(x[1]))
self.series_box.set_separator(None) self.series_box.set_separator(None)
self.series_box.update_items_cache([x[1] for x in all_series]) self.series_box.update_items_cache([x[1] for x in all_series])
for i in all_series: self.series_box.show_initial_value('')
id, name = i
self.series_box.addItem(name)
self.series_box.setEditText('')
all_tags = db.all_tags() all_tags = db.all_tags()
self.tags_box.update_items_cache(all_tags) self.tags_box.update_items_cache(all_tags)

View File

@ -32,8 +32,6 @@ class LanguagesEdit(MultiCompleteComboBox):
all_items = sorted(self._lang_map.itervalues(), all_items = sorted(self._lang_map.itervalues(),
key=lambda x: (-pmap.get(x, 0), sort_key(x))) key=lambda x: (-pmap.get(x, 0), sort_key(x)))
self.update_items_cache(all_items) self.update_items_cache(all_items)
for item in all_items:
self.addItem(item)
@property @property
def vals(self): def vals(self):

View File

@ -125,8 +125,6 @@ class TextDelegate(QStyledItemDelegate): # {{{
editor.set_separator(None) editor.set_separator(None)
complete_items = [i[1] for i in self.auto_complete_function()] complete_items = [i[1] for i in self.auto_complete_function()]
editor.update_items_cache(complete_items) editor.update_items_cache(complete_items)
for item in sorted(complete_items, key=sort_key):
editor.addItem(item)
ct = index.data(Qt.DisplayRole).toString() ct = index.data(Qt.DisplayRole).toString()
editor.show_initial_value(ct) editor.show_initial_value(ct)
else: else:
@ -166,8 +164,6 @@ class CompleteDelegate(QStyledItemDelegate): # {{{
all_items = list(self.db.all_custom( all_items = list(self.db.all_custom(
label=self.db.field_metadata.key_to_label(col))) label=self.db.field_metadata.key_to_label(col)))
editor.update_items_cache(all_items) editor.update_items_cache(all_items)
for item in sorted(all_items, key=sort_key):
editor.addItem(item)
ct = index.data(Qt.DisplayRole).toString() ct = index.data(Qt.DisplayRole).toString()
editor.show_initial_value(ct) editor.show_initial_value(ct)
else: else:

View File

@ -846,7 +846,9 @@ class BooksModel(QAbstractTableModel): # {{{
s_index = float(match.group(1)) s_index = float(match.group(1))
val = pat.sub('', val).strip() val = pat.sub('', val).strip()
elif val: elif val:
if tweaks['series_index_auto_increment'] != 'const': # it is OK to leave s_index == None when using 'no_change'
if tweaks['series_index_auto_increment'] != 'const' and \
tweaks['series_index_auto_increment'] != 'no_change':
s_index = self.db.get_next_cc_series_num_for(val, s_index = self.db.get_next_cc_series_num_for(val,
label=label, num=None) label=label, num=None)
elif typ == 'composite': elif typ == 'composite':
@ -915,7 +917,8 @@ class BooksModel(QAbstractTableModel): # {{{
self.db.set_series_index(id, float(match.group(1))) self.db.set_series_index(id, float(match.group(1)))
val = pat.sub('', val).strip() val = pat.sub('', val).strip()
elif val: elif val:
if tweaks['series_index_auto_increment'] != 'const': if tweaks['series_index_auto_increment'] != 'const' and \
tweaks['series_index_auto_increment'] != 'no_change':
ni = self.db.get_next_series_num_for(val) ni = self.db.get_next_series_num_for(val)
if ni != 1: if ni != 1:
self.db.set_series_index(id, ni) self.db.set_series_index(id, ni)

View File

@ -246,14 +246,6 @@ class AuthorsEdit(MultiCompleteComboBox):
def initialize(self, db, id_): def initialize(self, db, id_):
self.books_to_refresh = set([]) self.books_to_refresh = set([])
all_authors = db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1]))
self.clear()
for i in all_authors:
id, name = i
name = name.strip().replace('|', ',')
self.addItem(name)
self.set_separator('&') self.set_separator('&')
self.set_space_before_sep(True) self.set_space_before_sep(True)
self.set_add_separator(tweaks['authors_completer_append_separator']) self.set_add_separator(tweaks['authors_completer_append_separator'])
@ -299,7 +291,6 @@ class AuthorsEdit(MultiCompleteComboBox):
self.setEditText(' & '.join([x.strip() for x in val])) self.setEditText(' & '.join([x.strip() for x in val]))
self.lineEdit().setCursorPosition(0) self.lineEdit().setCursorPosition(0)
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def break_cycles(self): def break_cycles(self):
@ -488,19 +479,12 @@ class SeriesEdit(MultiCompleteComboBox):
all_series.sort(key=lambda x : sort_key(x[1])) all_series.sort(key=lambda x : sort_key(x[1]))
self.update_items_cache([x[1] for x in all_series]) self.update_items_cache([x[1] for x in all_series])
series_id = db.series_id(id_, index_is_id=True) series_id = db.series_id(id_, index_is_id=True)
idx, c = None, 0 inval = ''
self.clear()
for i in all_series: for i in all_series:
id, name = i if i[0] == series_id:
if id == series_id: inval = i[1]
idx = c break
self.addItem(name) self.original_val = self.current_val = inval
c += 1
self.lineEdit().setText('')
if idx is not None:
self.setCurrentIndex(idx)
self.original_val = self.current_val
def commit(self, db, id_): def commit(self, db, id_):
series = self.current_val series = self.current_val
@ -560,7 +544,7 @@ class SeriesIndexEdit(QDoubleSpinBox):
return True return True
def increment(self): def increment(self):
if self.db is not None: if tweaks['series_index_auto_increment'] != 'no_change' and self.db is not None:
try: try:
series = self.series_edit.current_val series = self.series_edit.current_val
if series and series != self.original_series_name: if series and series != self.original_series_name:
@ -1373,17 +1357,12 @@ class PublisherEdit(MultiCompleteComboBox): # {{{
all_publishers.sort(key=lambda x : sort_key(x[1])) all_publishers.sort(key=lambda x : sort_key(x[1]))
self.update_items_cache([x[1] for x in all_publishers]) self.update_items_cache([x[1] for x in all_publishers])
publisher_id = db.publisher_id(id_, index_is_id=True) publisher_id = db.publisher_id(id_, index_is_id=True)
idx = None inval = ''
self.clear() for pid, name in all_publishers:
for i, x in enumerate(all_publishers): if pid == publisher_id:
id_, name = x inval = name
if id_ == publisher_id: break
idx = i self.original_val = self.current_val = inval
self.addItem(name)
self.setEditText('')
if idx is not None:
self.setCurrentIndex(idx)
def commit(self, db, id_): def commit(self, db, id_):
self.books_to_refresh |= db.set_publisher(id_, self.current_val, self.books_to_refresh |= db.set_publisher(id_, self.current_val,

View File

@ -64,11 +64,11 @@ class EbookscomStore(BasicStoreConfig, StorePlugin):
continue continue
id = mo.group() id = mo.group()
cover_url = ''.join(data.xpath('.//div[@class="img"]//img/@src')) cover_url = ''.join(data.xpath('.//div[contains(@class, "img")]//img/@src'))
title = ''.join(data.xpath( title = ''.join(data.xpath(
'descendant::span[@class="book-title"]/a/text()')).strip() 'descendant::span[@class="book-title"]/a/text()')).strip()
author = ''.join(data.xpath( author = ', '.join(data.xpath(
'descendant::span[@class="author"]/a/text()')).strip() 'descendant::span[@class="author"]/a/text()')).strip()
if not title or not author: if not title or not author:
continue continue

View File

@ -1,81 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import urllib
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class OReillyStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://oreilly.com/ebooks/'
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
else:
d = WebStoreDialog(self.gui, url, parent, detail_item)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://search.oreilly.com/?t1=Books&t2=Format&t3=Ebook&q=' + urllib.quote_plus(query)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//div[@class="result"]'):
if counter <= 0:
break
ebook = ' '.join(data.xpath('.//p[@class="note"]/text()'))
if 'ebook' not in ebook.lower():
continue
id = ''.join(data.xpath('./div[@class="book_text"]//p[@class="title"]/a/@href'))
cover_url = ''.join(data.xpath('./a/img[1]/@src'))
title = ''.join(data.xpath('./div[@class="book_text"]/p[@class="title"]/a/text()'))
author = ''.join(data.xpath('./div[@class="book_text"]/p[@class="note"][1]/text()'))
author = author.split('By ')[-1].strip()
# Get the detail here because we need to get the ebook id for the detail_item.
with closing(br.open(id, timeout=timeout)) as nf:
idoc = html.fromstring(nf.read())
for td in idoc.xpath('//td[@class="optionsTd"]'):
if 'ebook' in ''.join(td.xpath('.//text()')).lower():
price = ''.join(td.xpath('.//span[@class="price"]/text()')).strip()
formats = ''.join(td.xpath('.//a[@id="availableFormats"]/text()')).strip()
break
counter -= 1
s = SearchResult()
s.cover_url = cover_url.strip()
s.title = title.strip()
s.author = author.strip()
s.detail_item = id.strip()
s.price = price.strip()
s.drm = SearchResult.DRM_UNLOCKED
s.formats = formats.upper()
yield s

View File

@ -46,30 +46,37 @@ class OzonRUStore(BasicStoreConfig, StorePlugin):
d.set_tags(self.config.get('tags', '')) d.set_tags(self.config.get('tags', ''))
d.exec_() d.exec_()
def search(self, query, max_results=15, timeout=60):
def search(self, query, max_results=10, timeout=60):
search_url = self.shop_url + '/webservice/webservice.asmx/SearchWebService?'\ search_url = self.shop_url + '/webservice/webservice.asmx/SearchWebService?'\
'searchText=%s&searchContext=ebook' % urllib2.quote(query) 'searchText=%s&searchContext=ebook' % urllib2.quote(query)
search_urls = [ search_url ]
## add this as the fist try if it looks like ozon ID
if re.match("^\d{6,9}$", query):
ozon_detail = self.shop_url + '/webservices/OzonWebSvc.asmx/ItemDetail?ID=%s' % query
search_urls.insert(0, ozon_detail)
xp_template = 'normalize-space(./*[local-name() = "{0}"]/text())' xp_template = 'normalize-space(./*[local-name() = "{0}"]/text())'
counter = max_results counter = max_results
br = browser() br = browser()
with closing(br.open(search_url, timeout=timeout)) as f:
raw = xml_to_unicode(f.read(), strip_encoding_pats=True, assume_utf8=True)[0] for url in search_urls:
doc = etree.fromstring(raw) with closing(br.open(url, timeout=timeout)) as f:
for data in doc.xpath('//*[local-name() = "SearchItems"]'): raw = xml_to_unicode(f.read(), strip_encoding_pats=True, assume_utf8=True)[0]
if counter <= 0: doc = etree.fromstring(raw)
break for data in doc.xpath('//*[local-name()="SearchItems" or local-name()="ItemDetail"]'):
counter -= 1 if counter <= 0:
break
counter -= 1
s = SearchResult() s = SearchResult()
s.detail_item = data.xpath(xp_template.format('ID')) s.detail_item = data.xpath(xp_template.format('ID'))
s.title = data.xpath(xp_template.format('Name')) s.title = data.xpath(xp_template.format('Name'))
s.author = data.xpath(xp_template.format('Author')) s.author = data.xpath(xp_template.format('Author'))
s.price = data.xpath(xp_template.format('Price')) s.price = data.xpath(xp_template.format('Price'))
s.cover_url = data.xpath(xp_template.format('Picture')) s.cover_url = data.xpath(xp_template.format('Picture'))
s.price = format_price_in_RUR(s.price) s.price = format_price_in_RUR(s.price)
yield s yield s
def get_details(self, search_result, timeout=60): def get_details(self, search_result, timeout=60):
url = self.shop_url + '/context/detail/id/' + urllib2.quote(search_result.detail_item) url = self.shop_url + '/context/detail/id/' + urllib2.quote(search_result.detail_item)
@ -97,6 +104,16 @@ class OzonRUStore(BasicStoreConfig, StorePlugin):
search_result.formats = ', '.join(_parse_ebook_formats(formats)) search_result.formats = ', '.join(_parse_ebook_formats(formats))
# unfortunately no direct links to download books (only buy link) # unfortunately no direct links to download books (only buy link)
# search_result.downloads['BF2'] = self.shop_url + '/order/digitalorder.aspx?id=' + + urllib2.quote(search_result.detail_item) # search_result.downloads['BF2'] = self.shop_url + '/order/digitalorder.aspx?id=' + + urllib2.quote(search_result.detail_item)
#<p class="main-cost"><span class="main">215</span><span class="submain">00</span> руб.</p>
#<span itemprop="price" class="hidden">215.00</span>
#<meta itemprop="priceCurrency" content="RUR " />
# if the price not in the search result (the ID search case)
if not search_result.price:
price = doc.xpath(u'normalize-space(//*[@itemprop="price"]/text())')
search_result.price = format_price_in_RUR(price)
return result return result
def format_price_in_RUR(price): def format_price_in_RUR(price):

View File

@ -41,7 +41,7 @@ class WeightlessBooksStore(BasicStoreConfig, StorePlugin):
counter = max_results counter = max_results
with closing(br.open(url, timeout=timeout)) as f: with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read()) doc = html.fromstring(f.read())
for data in doc.xpath('//li[@id="product"]'): for data in doc.xpath('//li[@class="product"]'):
if counter <= 0: if counter <= 0:
break break

View File

@ -234,21 +234,27 @@ class Document(QWebPage): # {{{
def switch_to_fullscreen_mode(self): def switch_to_fullscreen_mode(self):
self.in_fullscreen_mode = True self.in_fullscreen_mode = True
self.javascript(''' if self.in_paged_mode:
var s = document.body.style; self.javascript('paged_display.max_col_width = %d'%self.max_fs_width)
s.maxWidth = "%dpx"; else:
s.marginLeft = "auto"; self.javascript('''
s.marginRight = "auto"; var s = document.body.style;
'''%self.max_fs_width) s.maxWidth = "%dpx";
s.marginLeft = "auto";
s.marginRight = "auto";
'''%self.max_fs_width)
def switch_to_window_mode(self): def switch_to_window_mode(self):
self.in_fullscreen_mode = False self.in_fullscreen_mode = False
self.javascript(''' if self.in_paged_mode:
var s = document.body.style; self.javascript('paged_display.max_col_width = %d'%-1)
s.maxWidth = "none"; else:
s.marginLeft = "%s"; self.javascript('''
s.marginRight = "%s"; var s = document.body.style;
'''%(self.initial_left_margin, self.initial_right_margin)) s.maxWidth = "none";
s.marginLeft = "%s";
s.marginRight = "%s";
'''%(self.initial_left_margin, self.initial_right_margin))
@pyqtSignature("QString") @pyqtSignature("QString")
def debug(self, msg): def debug(self, msg):

View File

@ -477,6 +477,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
else: else:
self.view.document.switch_to_window_mode() self.view.document.switch_to_window_mode()
self.view.document.page_position.restore() self.view.document.page_position.restore()
self.scrolled(self.view.scroll_fraction)
def goto(self, ref): def goto(self, ref):
if ref: if ref:
@ -754,12 +755,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
# There hasn't been a resize event for some time # There hasn't been a resize event for some time
# restore the current page position. # restore the current page position.
self.resize_in_progress = False self.resize_in_progress = False
self.view.document.after_resize()
if self.window_mode_changed: if self.window_mode_changed:
# This resize is part of a window mode change, special case it # This resize is part of a window mode change, special case it
self.handle_window_mode_toggle() self.handle_window_mode_toggle()
else: else:
self.view.document.page_position.restore() self.view.document.page_position.restore()
self.view.document.after_resize()
def close_progress_indicator(self): def close_progress_indicator(self):
self.pi.stop() self.pi.stop()

View File

@ -829,7 +829,9 @@ def parse_series_string(db, label, value):
val = pat.sub('', val).strip() val = pat.sub('', val).strip()
s_index = float(match.group(1)) s_index = float(match.group(1))
elif val: elif val:
if tweaks['series_index_auto_increment'] != 'const': if tweaks['series_index_auto_increment'] == 'no_change':
pass
elif tweaks['series_index_auto_increment'] != 'const':
s_index = db.get_next_cc_series_num_for(val, label=label) s_index = db.get_next_cc_series_num_for(val, label=label)
else: else:
s_index = 1.0 s_index = 1.0

View File

@ -1203,7 +1203,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if m: if m:
return m['mtime'] return m['mtime']
def format_metadata(self, id_, fmt, allow_cache=True): def format_metadata(self, id_, fmt, allow_cache=True, update_db=False,
commit=False):
if not fmt: if not fmt:
return {} return {}
fmt = fmt.upper() fmt = fmt.upper()
@ -1218,6 +1219,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
ans['size'] = stat.st_size ans['size'] = stat.st_size
ans['mtime'] = utcfromtimestamp(stat.st_mtime) ans['mtime'] = utcfromtimestamp(stat.st_mtime)
self.format_metadata_cache[id_][fmt] = ans self.format_metadata_cache[id_][fmt] = ans
if update_db:
self.conn.execute(
'UPDATE data SET uncompressed_size=? WHERE format=? AND'
' book=?', (stat.st_size, fmt, id_))
if commit:
self.conn.commit()
return ans return ans
def format_hash(self, id_, fmt): def format_hash(self, id_, fmt):
@ -1448,6 +1455,24 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if notify: if notify:
self.notify('metadata', [id]) self.notify('metadata', [id])
def clean_standard_field(self, field, commit=False):
# Don't bother with validity checking. Let the exception fly out so
# we can see what happened
def doit(table, ltable_col):
st = ('DELETE FROM books_%s_link WHERE (SELECT COUNT(id) '
'FROM books WHERE id=book) < 1;')%table
self.conn.execute(st)
st = ('DELETE FROM %(table)s WHERE (SELECT COUNT(id) '
'FROM books_%(table)s_link WHERE '
'%(ltable_col)s=%(table)s.id) < 1;') % dict(
table=table, ltable_col=ltable_col)
self.conn.execute(st)
fm = self.field_metadata[field]
doit(fm['table'], fm['link_column'])
if commit:
self.conn.commit()
def clean(self): def clean(self):
''' '''
Remove orphaned entries. Remove orphaned entries.
@ -2550,6 +2575,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.set_tags(book_id, new_names, append=True, notify=False, self.set_tags(book_id, new_names, append=True, notify=False,
commit=False) commit=False)
self.dirtied(books, commit=False) self.dirtied(books, commit=False)
self.clean_standard_field('tags', commit=False)
self.conn.commit() self.conn.commit()
def delete_tag_using_id(self, id): def delete_tag_using_id(self, id):
@ -2564,7 +2590,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return [] return []
return result return result
def rename_series(self, old_id, new_name): def rename_series(self, old_id, new_name, change_index=True):
new_name = new_name.strip() new_name = new_name.strip()
new_id = self.conn.get( new_id = self.conn.get(
'''SELECT id from series '''SELECT id from series
@ -2577,23 +2603,26 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# New series exists. Must update the link, then assign a # New series exists. Must update the link, then assign a
# new series index to each of the books. # new series index to each of the books.
# Get the list of books where we must update the series index if change_index:
books = self.conn.get('''SELECT books.id # Get the list of books where we must update the series index
FROM books, books_series_link as lt books = self.conn.get('''SELECT books.id
WHERE books.id = lt.book AND lt.series=? FROM books, books_series_link as lt
ORDER BY books.series_index''', (old_id,)) WHERE books.id = lt.book AND lt.series=?
ORDER BY books.series_index''', (old_id,))
# Now update the link table # Now update the link table
self.conn.execute('''UPDATE books_series_link self.conn.execute('''UPDATE books_series_link
SET series=? SET series=?
WHERE series=?''',(new_id, old_id,)) WHERE series=?''',(new_id, old_id,))
# Now set the indices if change_index and tweaks['series_index_auto_increment'] != 'no_change':
for (book_id,) in books: # Now set the indices
# Get the next series index for (book_id,) in books:
index = self.get_next_series_num_for(new_name) # Get the next series index
self.conn.execute('''UPDATE books index = self.get_next_series_num_for(new_name)
SET series_index=? self.conn.execute('''UPDATE books
WHERE id=?''',(index, book_id,)) SET series_index=?
WHERE id=?''',(index, book_id,))
self.dirty_books_referencing('series', new_id, commit=False) self.dirty_books_referencing('series', new_id, commit=False)
self.clean_standard_field('series', commit=False)
self.conn.commit() self.conn.commit()
def delete_series_using_id(self, id): def delete_series_using_id(self, id):
@ -2629,6 +2658,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# Get rid of the no-longer used publisher # Get rid of the no-longer used publisher
self.conn.execute('DELETE FROM publishers WHERE id=?', (old_id,)) self.conn.execute('DELETE FROM publishers WHERE id=?', (old_id,))
self.dirty_books_referencing('publisher', new_id, commit=False) self.dirty_books_referencing('publisher', new_id, commit=False)
self.clean_standard_field('publisher', commit=False)
self.conn.commit() self.conn.commit()
def delete_publisher_using_id(self, old_id): def delete_publisher_using_id(self, old_id):
@ -2727,7 +2757,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# metadata. Ignore it. # metadata. Ignore it.
pass pass
# Now delete the old author from the DB # Now delete the old author from the DB
bks = self.conn.get('SELECT book FROM books_authors_link WHERE author=?', (old_id,))
self.conn.execute('DELETE FROM authors WHERE id=?', (old_id,)) self.conn.execute('DELETE FROM authors WHERE id=?', (old_id,))
self.dirtied(books, commit=False) self.dirtied(books, commit=False)
self.conn.commit() self.conn.commit()
@ -3684,4 +3713,12 @@ books_series_link feeds
s = self.conn.get('''SELECT book FROM books_plugin_data WHERE name=?''', (name,)) s = self.conn.get('''SELECT book FROM books_plugin_data WHERE name=?''', (name,))
return [x[0] for x in s] return [x[0] for x in s]
def get_usage_count_by_id(self, field):
fm = self.field_metadata[field]
if not fm.get('link_column', None):
raise ValueError('%s is not an is_multiple field')
return self.conn.get(
'SELECT {0}, count(*) FROM books_{1}_link GROUP BY {0}'.format(
fm['link_column'], fm['table']))