Sync to trunk.

This commit is contained in:
John Schember 2009-12-18 06:05:43 -05:00
commit d75040c80a
38 changed files with 701 additions and 122 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

BIN
resources/images/notify.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,49 @@
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
www.boston.com
'''
from calibre.web.feeds.recipes import BasicNewsRecipe
class BusinessStandard(BasicNewsRecipe):
title = 'Boston'
__author__ = 'Darko Miletic'
description = 'News from Boston'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
delay = 1
use_embedded_content = False
encoding = 'cp1252'
publisher = 'Boston'
category = 'news, boston, usa, world'
language = 'en'
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
}
keep_only_tags = [dict(name='div', attrs={'class':'story'})]
remove_tags = [dict(name=['object','link','script','iframe'])]
feeds = [
(u'Top Stories' , u'http://feeds.boston.com/boston/topstories' )
,(u'Patriots news', u'http://feeds.boston.com/boston/sports/football/patriots')
,(u'National news', u'http://feeds.boston.com/boston/news/nation' )
,(u'World news' , u'http://feeds.boston.com/boston/news/world' )
]
def print_version(self, url):
return url + '?mode=PF'
def get_article_url(self, article):
rawarticle = article.get('pheedo_origlink', None)
artls, sep, rsep = rawarticle.rpartition('/?')
if artls == '':
artls = rawarticle.rpartition('?')[0]
return artls

View File

@ -0,0 +1,20 @@
from calibre.web.feeds.news import BasicNewsRecipe
class ClarionLedger(BasicNewsRecipe):
title = u'Clarion Ledger'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
language = 'en'
__author__ = 'cr4zyd'
feeds = [(u'Local News', u'http://www.clarionledger.com/apps/pbcs.dll/oversikt?Category=RSS01'), (u'Breaking News', u'http://www.clarionledger.com/apps/pbcs.dll/section?Category=RSS'), (u'Sports', u'http://www.clarionledger.com/apps/pbcs.dll/oversikt?Category=RSS02'), (u'Business', u'http://www.clarionledger.com/apps/pbcs.dll/oversikt?Category=RSS03')]
keep_only_tags = [dict(name='div', attrs={'class':'article-headline'}),
dict(name='div', attrs={'class':'article-bodytext'})]
remove_tags = [dict(name=['img','script','li']),
dict(name='p', attrs={'class':'ratingbyline'}),
dict(name='div', attrs={'class':'article-tools'}),
dict(name='div', attrs={'class':'article-pagination article-pagination-top'}),
dict(name='div', attrs={'class':'article-pagination article-pagination-bottom'}),
dict(name='div', attrs={'class':'articleflex-container'})]

View File

@ -0,0 +1,31 @@
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
http://www.dosisdiarias.com
'''
from calibre.web.feeds.recipes import BasicNewsRecipe
class DosisDiarias(BasicNewsRecipe):
title = 'Alberto Montt en dosis diarias'
__author__ = 'Darko Miletic'
description = 'Mire sin compromiso y si le gusta vuelva'
oldest_article = 5
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = True
encoding = 'utf-8'
publisher = 'Alberto Montt'
category = 'comic, blog, spanish'
language = 'es'
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
}
remove_tags = [dict(name='div',attrs={'class':'feedflare'})]
feeds = [(u'Dosis diaria', u'http://feeds.feedburner.com/montt' )]

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
@ -19,22 +18,19 @@ class ElMundo(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
encoding = 'iso8859_15' encoding = 'iso8859_15'
cover_url = 'http://estaticos02.cache.el-mundo.net/papel/imagenes/v2.0/logoverde.gif' language = 'es'
remove_javascript = True
html2lrf_options = [ conversion_options = {
'--comment', description 'comments' : description
, '--category', category ,'tags' : category
, '--publisher', publisher ,'language' : language
] ,'publisher' : publisher
}
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' keep_only_tags = [dict(name='div', attrs={'class':'noticia'})]
remove_tags_before = dict(attrs={'class':['titular','antetitulo'] })
remove_tags_after = dict(name='div' , attrs={'id':['desarrollo_noticia','tamano']})
keep_only_tags = [
dict(name='div', attrs={'id':['bloqueprincipal','noticia']})
,dict(name='div', attrs={'class':['contenido_noticia_01']})
]
remove_tags = [ remove_tags = [
dict(name='div', attrs={'class':['herramientas','publicidad_google']}) dict(name='div', attrs={'class':['herramientas','publicidad_google']})
,dict(name='div', attrs={'id':'modulo_multimedia' }) ,dict(name='div', attrs={'id':'modulo_multimedia' })
@ -44,6 +40,8 @@ class ElMundo(BasicNewsRecipe):
feeds = [ feeds = [
(u'Portada' , u'http://rss.elmundo.es/rss/descarga.htm?data2=4' ) (u'Portada' , u'http://rss.elmundo.es/rss/descarga.htm?data2=4' )
,(u'Deportes' , u'http://rss.elmundo.es/rss/descarga.htm?data2=14')
,(u'Economia' , u'http://rss.elmundo.es/rss/descarga.htm?data2=7' )
,(u'Espana' , u'http://rss.elmundo.es/rss/descarga.htm?data2=8' ) ,(u'Espana' , u'http://rss.elmundo.es/rss/descarga.htm?data2=8' )
,(u'Internacional' , u'http://rss.elmundo.es/rss/descarga.htm?data2=9' ) ,(u'Internacional' , u'http://rss.elmundo.es/rss/descarga.htm?data2=9' )
,(u'Cultura' , u'http://rss.elmundo.es/rss/descarga.htm?data2=6' ) ,(u'Cultura' , u'http://rss.elmundo.es/rss/descarga.htm?data2=6' )
@ -51,10 +49,3 @@ class ElMundo(BasicNewsRecipe):
,(u'Comunicacion' , u'http://rss.elmundo.es/rss/descarga.htm?data2=26') ,(u'Comunicacion' , u'http://rss.elmundo.es/rss/descarga.htm?data2=26')
,(u'Television' , u'http://rss.elmundo.es/rss/descarga.htm?data2=76') ,(u'Television' , u'http://rss.elmundo.es/rss/descarga.htm?data2=76')
] ]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup
language = 'es'

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
@ -12,30 +11,29 @@ class LondonReviewOfBooks(BasicNewsRecipe):
title = u'London Review of Books' title = u'London Review of Books'
__author__ = u'Darko Miletic' __author__ = u'Darko Miletic'
description = u'Literary review publishing essay-length book reviews and topical articles on politics, literature, history, philosophy, science and the arts by leading writers and thinkers' description = u'Literary review publishing essay-length book reviews and topical articles on politics, literature, history, philosophy, science and the arts by leading writers and thinkers'
category = 'news, literature, England'
publisher = 'London Review of Books'
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100
language = 'en_GB' language = 'en_GB'
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
encoding = 'cp1252' encoding = 'utf-8'
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
}
keep_only_tags = [dict(name='div' , attrs={'id' :'main'})]
remove_tags = [ remove_tags = [
dict(name='div' , attrs={'id' :'otherarticles'}) dict(name='div' , attrs={'class':['pagetools','issue-nav-controls','nocss']})
,dict(name='div' , attrs={'class':'pagetools' }) ,dict(name='div' , attrs={'id' :['mainmenu','precontent','otherarticles'] })
,dict(name='div' , attrs={'id' :'mainmenu' }) ,dict(name='span', attrs={'class':['inlineright','article-icons']})
,dict(name='div' , attrs={'id' :'precontent' }) ,dict(name='ul' , attrs={'class':'article-controls'})
,dict(name='div' , attrs={'class':'nocss' }) ,dict(name='p' , attrs={'class':'meta-info' })
,dict(name='span', attrs={'class':'inlineright' })
] ]
feeds = [(u'London Review of Books', u'http://www.lrb.co.uk/lrbrss.xml')] feeds = [(u'London Review of Books', u'http://www.lrb.co.uk/lrbrss.xml')]
def print_version(self, url):
main, split, rest = url.rpartition('/')
return main + '/print/' + rest
def postprocess_html(self, soup, first_fetch):
for t in soup.findAll(['table', 'tr', 'td']):
t.name = 'div'
return soup

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: cp1252 -*-
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
@ -13,7 +14,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class Politico(BasicNewsRecipe): class Politico(BasicNewsRecipe):
title = 'Politico' title = 'Politico'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic and Sujata Raman'
description = 'Political news from USA' description = 'Political news from USA'
publisher = 'Capitol News Company, LLC' publisher = 'Capitol News Company, LLC'
category = 'news, politics, USA' category = 'news, politics, USA'
@ -22,10 +23,9 @@ class Politico(BasicNewsRecipe):
use_embedded_content = False use_embedded_content = False
no_stylesheets = True no_stylesheets = True
remove_javascript = True remove_javascript = True
encoding = 'cp1252' encoding = 'UTF-8'
language = 'en' language = 'en'
html2lrf_options = [ html2lrf_options = [
'--comment', description '--comment', description
, '--category', category , '--category', category
@ -35,7 +35,19 @@ class Politico(BasicNewsRecipe):
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True' html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
remove_tags = [dict(name=['notags','embed','object','link','img'])] remove_tags = [
dict(name=['notags','embed','object','link','img']),
]
extra_css = '''
body{font-family:Arial,Sans-serif;}
element.style{color:#FF0000;font-family:Arial,Sans-serif;}
.author{color:#808080;font-size:x-small;}
a{ color:#003399;}
.byline{color:#696969 ; font-size:x-small;}
.story{color:#000000;}
td{color:#000000;}
'''
feeds = [ feeds = [
(u'Top Stories' , u'http://www.politico.com/rss/politicopicks.xml' ) (u'Top Stories' , u'http://www.politico.com/rss/politicopicks.xml' )
@ -48,17 +60,23 @@ class Politico(BasicNewsRecipe):
,(u'Roger Simon' , u'http://www.politico.com/rss/rogersimon.xml' ) ,(u'Roger Simon' , u'http://www.politico.com/rss/rogersimon.xml' )
,(u'Suite Talk' , u'http://www.politico.com/rss/suitetalk.xml' ) ,(u'Suite Talk' , u'http://www.politico.com/rss/suitetalk.xml' )
,(u'Playbook' , u'http://www.politico.com/rss/playbook.xml' ) ,(u'Playbook' , u'http://www.politico.com/rss/playbook.xml' )
,(u'The Huddle' , u'http://www.politico.com/rss/huddle.xml' ) #(u'The Huddle' , u'http://www.politico.com/rss/huddle.xml' )
] ]
def preprocess_html(self, soup): def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Language" content="en-US"/>' mtag = '<meta http-equiv="Content-Language" content="en"/>'
soup.head.insert(0,mtag) soup.head.insert(0,mtag)
for item in soup.findAll(style=True): for item in soup.findAll(style=True):
del item['style'] del item['style']
return soup return soup
url_pat = re.compile(r'<a href="([^"]+printstory\.cfm[^"]+)"') url_pat = re.compile(r'<a href="([^"]+print.*\.cfm[^"]+)"')
def postprocess_html(self, soup, first):
for tag in soup.findAll(name=['table', 'tr', 'td']):
tag.name = 'div'
return soup
def print_version(self, url): def print_version(self, url):
raw = self.index_to_soup(url, raw=True) raw = self.index_to_soup(url, raw=True)

View File

@ -0,0 +1,32 @@
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
praguemonitor.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class PragueDailyMonitor(BasicNewsRecipe):
title = u'Prague Daily Monitor'
__author__ = u'Darko Miletic'
description = u'Czech news in English'
category = 'news, politics, Czech republic'
publisher = 'Prague Daily Monitor'
oldest_article = 2
max_articles_per_feed = 100
language = 'en'
no_stylesheets = True
use_embedded_content = False
encoding = 'utf-8'
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
}
keep_only_tags = [dict(name='div' , attrs={'id':['content-header','content-area']})]
feeds = [(u'All Articles', u'http://feeds.feedburner.com/PragueDailyMonitor?format=xml')]

View File

@ -26,9 +26,12 @@ class Sueddeutsche(BasicNewsRecipe):
dict(name='div', attrs={'id':["artikel","contentTable"]}) , dict(name='div', attrs={'id':["artikel","contentTable"]}) ,
] ]
remove_tags = [ dict(name='link'), dict(name='iframe'), remove_tags = [ dict(name='link'), dict(name='iframe'),
dict(name='div', attrs={'id':["themenbox","artikelfoot","CAD_AD","rechteSpalte"]}), dict(name='div', attrs={'id':["themenbox","artikelfoot","CAD_AD","SKY_AD","NT1_AD","rechteSpalte"]}),
dict(name='div', attrs={'class':["similar-article-box","artikelliste","nteaser301bg","pages closed"]}), dict(name='div', attrs={'class':["similar-article-box","artikelliste","nteaser301bg","pages closed"]}),
dict(name='div', attrs={'class':["listHeader","listHeader2","hr2","item","videoBigButton"]}),
dict(name='p', attrs={'class':["ressortartikeln",]}), dict(name='p', attrs={'class':["ressortartikeln",]}),
dict(name='div', attrs={'style':["position:relative;"]}),
dict(name='span', attrs={'class':["nlinkheaderteaserschwarz",]}),
dict(name='table', attrs={'class':["kommentare","footer","pageBoxBot","pageAktiv","bgcontent"]}), dict(name='table', attrs={'class':["kommentare","footer","pageBoxBot","pageAktiv","bgcontent"]}),
dict(name='ul', attrs={'class':["breadcrumb","articles","activities"]}), dict(name='ul', attrs={'class':["breadcrumb","articles","activities"]}),
dict(name='p', text = "ANZEIGE") dict(name='p', text = "ANZEIGE")
@ -66,3 +69,4 @@ class Sueddeutsche(BasicNewsRecipe):

View File

@ -15,12 +15,13 @@ class weltDe(BasicNewsRecipe):
__author__ = 'Oliver Niesner' __author__ = 'Oliver Niesner'
use_embedded_content = False use_embedded_content = False
timefmt = ' [%d %b %Y]' timefmt = ' [%d %b %Y]'
max_articles_per_feed = 15 # reduced to this value to prevent too many articles (suggested by Gregory Riker max_articles_per_feed = 15
linearize_tables = True
no_stylesheets = True no_stylesheets = True
remove_stylesheets = True remove_stylesheets = True
remove_javascript = True remove_javascript = True
language = 'de'
encoding = 'iso-8859-1' encoding = 'iso-8859-1'
BasicNewsRecipe.summary_length = 200
remove_tags = [dict(id='jumplinks'), remove_tags = [dict(id='jumplinks'),
@ -43,10 +44,14 @@ class weltDe(BasicNewsRecipe):
dict(id='servicesBox'), dict(id='servicesBox'),
dict(id='toggleAdvancedSearch'), dict(id='toggleAdvancedSearch'),
dict(id='mainNav'), dict(id='mainNav'),
dict(id='ratingBox5136466_1'),
dict(id='ratingBox5136466_2'),
dict(id='articleInlineMediaBox0'), dict(id='articleInlineMediaBox0'),
dict(id='sectionSponsor'), dict(id='sectionSponsor'),
dict(id='sprucharea'),
dict(id='xmsg_recommendEmail'),
dict(id='xmsg_recommendSms'),
dict(id='xmsg_comment'),
dict(id='additionalNavWrapper'),
dict(id='imagebox'),
#dict(id=''), #dict(id=''),
dict(name='span'), dict(name='span'),
dict(name='div', attrs={'class':'printURL'}), dict(name='div', attrs={'class':'printURL'}),
@ -65,10 +70,21 @@ class weltDe(BasicNewsRecipe):
dict(name='ul', attrs={'class':'optionsSubNav clear'}), dict(name='ul', attrs={'class':'optionsSubNav clear'}),
dict(name='li', attrs={'class':'next'}), dict(name='li', attrs={'class':'next'}),
dict(name='li', attrs={'class':'prev'}), dict(name='li', attrs={'class':'prev'}),
dict(name='li', attrs={'class':'last'}),
dict(name='table', attrs={'class':'textGallery'}),
dict(name='li', attrs={'class':'active'})] dict(name='li', attrs={'class':'active'})]
remove_tags_after = [dict(id='tw_link_widget')] remove_tags_after = [dict(id='tw_link_widget')]
extra_css = '''
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #003399;}
a{font-family:Arial,Helvetica,sans-serif; font-size: x-small; font-style:italic;}
.dachzeile p{font-family:Arial,Helvetica,sans-serif; font-size: x-small; }
h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;}
.artikelTeaser{font-family:Arial,Helvetica,sans-serif; font-size: x-small; font-weight:bold; }
body{font-family:Arial,Helvetica,sans-serif; }
.photo {font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #666666;} '''
feeds = [ ('Politik', 'http://welt.de/politik/?service=Rss'), feeds = [ ('Politik', 'http://welt.de/politik/?service=Rss'),
('Deutsche Dinge', 'http://www.welt.de/deutsche-dinge/?service=Rss'), ('Deutsche Dinge', 'http://www.welt.de/deutsche-dinge/?service=Rss'),
('Wirtschaft', 'http://welt.de/wirtschaft/?service=Rss'), ('Wirtschaft', 'http://welt.de/wirtschaft/?service=Rss'),

View File

@ -187,7 +187,7 @@
<!-- section/title --> <!-- section/title -->
<xsl:template match="fb:body/fb:title"> <xsl:template match="fb:body/fb:title">
<xsl:element name="h1"> <xsl:element name="h1">
<xsl:apply-templates mode="title"/> <xsl:apply-templates />
</xsl:element> </xsl:element>
</xsl:template> </xsl:template>

View File

@ -108,7 +108,7 @@ class LinuxFreeze(Command):
'glib', 'gobject'] 'glib', 'gobject']
packages = ['calibre', 'encodings', 'cherrypy', 'cssutils', 'xdg', packages = ['calibre', 'encodings', 'cherrypy', 'cssutils', 'xdg',
'dateutil', 'dns', 'email'] 'dateutil', 'dns', 'email', 'dbus']
includes += ['calibre.gui2.convert.'+x.split('/')[-1].rpartition('.')[0] for x in \ includes += ['calibre.gui2.convert.'+x.split('/')[-1].rpartition('.')[0] for x in \
glob.glob('src/calibre/gui2/convert/*.py')] glob.glob('src/calibre/gui2/convert/*.py')]

View File

@ -5,7 +5,9 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Device drivers. Device drivers.
''' '''
import time import sys, os, time, pprint
from functools import partial
from StringIO import StringIO
DAY_MAP = dict(Sun=0, Mon=1, Tue=2, Wed=3, Thu=4, Fri=5, Sat=6) DAY_MAP = dict(Sun=0, Mon=1, Tue=2, Wed=3, Thu=4, Fri=5, Sat=6)
MONTH_MAP = dict(Jan=1, Feb=2, Mar=3, Apr=4, May=5, Jun=6, Jul=7, Aug=8, Sep=9, Oct=10, Nov=11, Dec=12) MONTH_MAP = dict(Jan=1, Feb=2, Mar=3, Apr=4, May=5, Jun=6, Jul=7, Aug=8, Sep=9, Oct=10, Nov=11, Dec=12)
@ -24,3 +26,96 @@ def strftime(epoch, zone=time.gmtime):
src[0] = INVERSE_DAY_MAP[int(src[0][:-1])]+',' src[0] = INVERSE_DAY_MAP[int(src[0][:-1])]+','
src[2] = INVERSE_MONTH_MAP[int(src[2])] src[2] = INVERSE_MONTH_MAP[int(src[2])]
return ' '.join(src) return ' '.join(src)
def debug():
from calibre.customize.ui import device_plugins
from calibre.devices.scanner import DeviceScanner
from calibre.constants import iswindows, isosx, __version__
from calibre import prints
oldo, olde = sys.stdout, sys.stderr
buf = StringIO()
sys.stdout = sys.stderr = buf
try:
out = partial(prints, file=buf)
out('Version:', __version__)
s = DeviceScanner()
s.scan()
devices = (s.devices)
if not iswindows:
devices = [list(x) for x in devices]
for d in devices:
for i in range(3):
d[i] = hex(d[i])
out('USB devices on system:')
out(pprint.pformat(devices))
if iswindows:
if iswindows:
import pythoncom
pythoncom.CoInitialize()
try:
wmi = __import__('wmi', globals(), locals(), [], -1)
drives = []
out('Drives detected:')
out('\t', '(ID, Partitions, Drive letter)')
for drive in wmi.WMI(find_classes=False).Win32_DiskDrive():
if drive.Partitions == 0:
continue
try:
partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0]
prefix = logical_disk.DeviceID+os.sep
drives.append((str(drive.PNPDeviceID), drive.Index, prefix))
except IndexError:
drives.append((str(drive.PNPDeviceID), 'No mount points found'))
for drive in drives:
out('\t', drive)
finally:
pythoncom.CoUninitialize()
ioreg = None
if isosx:
from calibre.devices.usbms.device import Device
ioreg = Device.run_ioreg()
connected_devices = []
for dev in device_plugins():
out('Looking for', dev.__class__.__name__)
connected = s.is_device_connected(dev, debug=True)
if connected:
connected_devices.append(dev)
errors = {}
success = False
for dev in connected_devices:
out('Device possibly connected:', dev.__class__.name)
out('Trying to open device...', end=' ')
try:
dev.open()
out('OK')
except:
import traceback
errors[dev] = traceback.format_exc()
out('failed')
continue
success = True
if hasattr(dev, '_main_prefix'):
out('Main memory:', repr(dev._main_prefix))
out('Total space:', dev.total_space())
break
if not success and errors:
out('Opening of the following devices failed')
for dev,msg in errors.items():
out(dev)
out(msg)
out(' ')
if ioreg is not None:
out(' ')
out('IOREG Output')
out(ioreg)
return buf.getvalue().decode('utf-8')
finally:
sys.stdout = oldo
sys.stderr = olde

View File

@ -117,9 +117,15 @@ class ITALICA(EB600):
name = 'Italica Device Interface' name = 'Italica Device Interface'
gui_name = 'Italica' gui_name = 'Italica'
FORMATS = ['epub', 'pdf', 'txt'] FORMATS = ['epub', 'rtf', 'fb2', 'html', 'prc', 'mobi', 'pdf', 'txt']
VENDOR_NAME = 'ITALICA' VENDOR_NAME = 'ITALICA'
WINDOWS_MAIN_MEM = 'EREADER' WINDOWS_MAIN_MEM = 'EREADER'
WINDOWS_CARD_A_MEM = WINDOWS_MAIN_MEM
OSX_MAIN_MEM = 'Italica eReader Media' OSX_MAIN_MEM = 'Italica eReader Media'
OSX_CARD_A_MEM = OSX_MAIN_MEM
MAIN_MEMORY_VOLUME_LABEL = 'Italica Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'Italica Storage Card'

View File

@ -166,7 +166,7 @@ class PRS500(DeviceConfig, DevicePlugin):
try: try:
if not dev.handle: if not dev.handle:
dev.open() dev.open()
if not dev.in_session: if not getattr(dev, 'in_session', False):
dev.send_validated_command(BeginEndSession(end=False)) dev.send_validated_command(BeginEndSession(end=False))
dev.in_session = True dev.in_session = True
res = func(*args, **kwargs) res = func(*args, **kwargs)

View File

@ -711,7 +711,9 @@ class Device(DeviceConfig, DevicePlugin):
candidates = self.get_main_ebook_dir() candidates = self.get_main_ebook_dir()
if isinstance(candidates, basestring): if isinstance(candidates, basestring):
candidates = [candidates] candidates = [candidates]
candidates = [os.path.join(self._main_prefix, *(x.split('/'))) for x candidates = [
((os.path.join(self._main_prefix, *(x.split('/')))) if x else
self._main_prefix) for x
in candidates] in candidates]
existing = [x for x in candidates if os.path.exists(x)] existing = [x for x in candidates if os.path.exists(x)]
if not existing: if not existing:

View File

@ -63,7 +63,7 @@ class USBMS(CLI, Device):
if isinstance(ebook_dirs, basestring): if isinstance(ebook_dirs, basestring):
ebook_dirs = [ebook_dirs] ebook_dirs = [ebook_dirs]
for ebook_dir in ebook_dirs: for ebook_dir in ebook_dirs:
ebook_dir = os.path.join(prefix, *(ebook_dir.split('/'))) ebook_dir = os.path.join(prefix, *(ebook_dir.split('/'))) if ebook_dir else prefix
if not os.path.exists(ebook_dir): continue if not os.path.exists(ebook_dir): continue
# Get all books in the ebook_dir directory # Get all books in the ebook_dir directory
if self.SUPPORTS_SUB_DIRS: if self.SUPPORTS_SUB_DIRS:

View File

@ -25,7 +25,7 @@ class DRMError(ValueError):
BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'htm', 'xhtm', BOOK_EXTENSIONS = ['lrf', 'rar', 'zip', 'rtf', 'lit', 'txt', 'htm', 'xhtm',
'html', 'xhtml', 'pdf', 'pdb', 'prc', 'mobi', 'azw', 'doc', 'html', 'xhtml', 'pdf', 'pdb', 'prc', 'mobi', 'azw', 'doc',
'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'oebzip', 'epub', 'fb2', 'djvu', 'lrx', 'cbr', 'cbz', 'oebzip',
'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1'] 'rb', 'imp', 'odt', 'chm', 'tpz', 'azw1', 'pml']
class HTMLRenderer(object): class HTMLRenderer(object):

View File

@ -804,6 +804,11 @@ OptionRecommendation(name='language',
if line_height < 1e-4: if line_height < 1e-4:
line_height = None line_height = None
if self.opts.linearize_tables and \
self.output_plugin.file_type not in ('mobi', 'lrf'):
from calibre.ebooks.oeb.transforms.linearize_tables import LinearizeTables
LinearizeTables()(self.oeb, self.opts)
flattener = CSSFlattener(fbase=fbase, fkey=fkey, flattener = CSSFlattener(fbase=fbase, fkey=fkey,
lineh=line_height, lineh=line_height,
untable=self.output_plugin.file_type in ('mobi','lit'), untable=self.output_plugin.file_type in ('mobi','lit'),
@ -812,10 +817,6 @@ OptionRecommendation(name='language',
self.opts.insert_blank_line = oibl self.opts.insert_blank_line = oibl
self.opts.remove_paragraph_spacing = orps self.opts.remove_paragraph_spacing = orps
if self.opts.linearize_tables and \
self.output_plugin.file_type not in ('mobi', 'lrf'):
from calibre.ebooks.oeb.transforms.linearize_tables import LinearizeTables
LinearizeTables()(self.oeb, self.opts)
pr(0.9) pr(0.9)
self.flush() self.flush()

View File

@ -16,10 +16,16 @@ class MOBIInput(InputFormatPlugin):
accelerators): accelerators):
from calibre.ebooks.mobi.reader import MobiReader from calibre.ebooks.mobi.reader import MobiReader
from lxml import html from lxml import html
parse_cache = {}
try:
mr = MobiReader(stream, log, options.input_encoding, mr = MobiReader(stream, log, options.input_encoding,
options.debug_pipeline) options.debug_pipeline)
parse_cache = {}
mr.extract_content('.', parse_cache) mr.extract_content('.', parse_cache)
except:
mr = MobiReader(stream, log, options.input_encoding,
options.debug_pipeline, try_extra_data_fix=True)
mr.extract_content('.', parse_cache)
raw = parse_cache.pop('calibre_raw_mobi_markup', False) raw = parse_cache.pop('calibre_raw_mobi_markup', False)
if raw: if raw:
if isinstance(raw, unicode): if isinstance(raw, unicode):

View File

@ -108,7 +108,7 @@ class EXTHHeader(object):
class BookHeader(object): class BookHeader(object):
def __init__(self, raw, ident, user_encoding, log): def __init__(self, raw, ident, user_encoding, log, try_extra_data_fix=False):
self.log = log self.log = log
self.compression_type = raw[:2] self.compression_type = raw[:2]
self.records, self.records_size = struct.unpack('>HH', raw[8:12]) self.records, self.records_size = struct.unpack('>HH', raw[8:12])
@ -141,7 +141,8 @@ class BookHeader(object):
self.codec = 'cp1252' if user_encoding is None else user_encoding self.codec = 'cp1252' if user_encoding is None else user_encoding
log.warn('Unknown codepage %d. Assuming %s' % (self.codepage, log.warn('Unknown codepage %d. Assuming %s' % (self.codepage,
self.codec)) self.codec))
if ident == 'TEXTREAD' or self.length < 0xE4 or 0xE8 < self.length: if ident == 'TEXTREAD' or self.length < 0xE4 or 0xE8 < self.length \
or (try_extra_data_fix and self.length == 0xE4):
self.extra_flags = 0 self.extra_flags = 0
else: else:
self.extra_flags, = struct.unpack('>H', raw[0xF2:0xF4]) self.extra_flags, = struct.unpack('>H', raw[0xF2:0xF4])
@ -229,7 +230,8 @@ class MobiReader(object):
PAGE_BREAK_PAT = re.compile(r'(<[/]{0,1}mbp:pagebreak\s*[/]{0,1}>)+', re.IGNORECASE) PAGE_BREAK_PAT = re.compile(r'(<[/]{0,1}mbp:pagebreak\s*[/]{0,1}>)+', re.IGNORECASE)
IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex') IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex')
def __init__(self, filename_or_stream, log, user_encoding=None, debug=None): def __init__(self, filename_or_stream, log, user_encoding=None, debug=None,
try_extra_data_fix=False):
self.log = log self.log = log
self.debug = debug self.debug = debug
self.embedded_mi = None self.embedded_mi = None
@ -284,7 +286,7 @@ class MobiReader(object):
self.book_header = BookHeader(self.sections[0][0], self.ident, self.book_header = BookHeader(self.sections[0][0], self.ident,
user_encoding, self.log) user_encoding, self.log, try_extra_data_fix=try_extra_data_fix)
self.name = self.name.decode(self.book_header.codec, 'replace') self.name = self.name.decode(self.book_header.codec, 'replace')
def extract_content(self, output_dir, parse_cache): def extract_content(self, output_dir, parse_cache):
@ -701,7 +703,9 @@ class MobiReader(object):
if self.book_header.ancient and '<html' not in self.mobi_html[:300].lower(): if self.book_header.ancient and '<html' not in self.mobi_html[:300].lower():
self.mobi_html = self.mobi_html.replace('\r ', '\n\n ') self.mobi_html = self.mobi_html.replace('\r ', '\n\n ')
self.mobi_html = self.mobi_html.replace('\0', '') self.mobi_html = self.mobi_html.replace('\0', '')
if self.book_header.codec == 'cp1252':
self.mobi_html = self.mobi_html.replace('\x1e', '') # record separator self.mobi_html = self.mobi_html.replace('\x1e', '') # record separator
self.mobi_html = self.mobi_html.replace('\x02', '') # start of text
return processed_records return processed_records

View File

@ -91,7 +91,6 @@ class Split(object):
False)) False))
except: except:
pass pass
page_breaks = set([]) page_breaks = set([])
for selector, before in self.page_break_selectors: for selector, before in self.page_break_selectors:
body = item.data.xpath('//h:body', namespaces=NAMESPACES) body = item.data.xpath('//h:body', namespaces=NAMESPACES)
@ -382,6 +381,7 @@ class FlowSplitter(object):
p[i:i+1] = new_pres p[i:i+1] = new_pres
split_point, before = self.find_split_point(root) split_point, before = self.find_split_point(root)
self.log.debug('\t\t\tSplit point:', split_point.tag, tree.getpath(split_point))
if split_point is None: if split_point is None:
raise SplitError(self.item.href, root) raise SplitError(self.item.href, root)
@ -396,6 +396,9 @@ class FlowSplitter(object):
'\t\t\tCommitted sub-tree #%d (%d KB)'%( '\t\t\tCommitted sub-tree #%d (%d KB)'%(
len(self.split_trees), size/1024.)) len(self.split_trees), size/1024.))
else: else:
self.log.debug(
'\t\t\tSplit tree still too large: %d KB' % \
(size/1024.))
self.split_to_size(t) self.split_to_size(t)
def find_split_point(self, root): def find_split_point(self, root):

View File

@ -78,7 +78,7 @@ class HorizontalBox(object):
def append(self, t): def append(self, t):
self.texts.append(t) self.texts.append(t)
def sort(self): def sort(self, left_margin, right_margin):
self.texts.sort(cmp=lambda x,y: cmp(x.left, y.left)) self.texts.sort(cmp=lambda x,y: cmp(x.left, y.left))
self.top, self.bottom = sys.maxint, 0 self.top, self.bottom = sys.maxint, 0
for t in self.texts: for t in self.texts:
@ -86,6 +86,27 @@ class HorizontalBox(object):
self.bottom = max(self.bottom, t.bottom) self.bottom = max(self.bottom, t.bottom)
self.left = self.texts[0].left self.left = self.texts[0].left
self.right = self.texts[-1].right self.right = self.texts[-1].right
self.gaps = []
for i, t in enumerate(self.texts[1:]):
gap = Interval(self.texts[i].right, t.left)
if gap.width > 3:
self.gaps.append(gap)
left = Interval(left_margin, self.texts[0].left)
if left.width > 3:
self.gaps.insert(0, left)
right = Interval(self.texts[-1].right, right_margin)
if right.width > 3:
self.gaps.append(right)
def has_intersection_with(self, gap):
for g in self.gaps:
if g.intersection(gap):
return True
return False
def identify_columns(self, column_gaps):
self.number_of_columns = len(column_gaps) + 1
class Page(object): class Page(object):
@ -138,19 +159,24 @@ class Page(object):
for hb in self.horizontal_boxes: for hb in self.horizontal_boxes:
hb.sort() hb.sort(self.left_margin, self.right_margin)
self.horizontal_boxes.sort(cmp=lambda x,y: cmp(x.bottom, y.bottom)) self.horizontal_boxes.sort(cmp=lambda x,y: cmp(x.bottom, y.bottom))
def identify_columns(self): def identify_columns(self):
def neighborhood(i): def neighborhood(i):
if i == 0: if i == len(self.horizontal_boxes)-1:
return self.horizontal_boxes[1:3] return self.horizontal_boxes[i-2:i]
if i == len(self.horizontal_boxes)-2:
return (self.horizontal_boxes[i-1], self.horizontal_boxes[i+1]) return (self.horizontal_boxes[i-1], self.horizontal_boxes[i+1])
return self.horizontal_boxes[i+1], self.horizontal_boxes[i+2]
for i, hbox in enumerate(self.horizontal_boxes): for i, hbox in enumerate(self.horizontal_boxes):
pass n1, n2 = neighborhood(i)
for gap in hbox.gaps:
gap.is_column_gap = n1.has_intersection_with(gap) and \
n2.has_intersection_with(gap)

View File

@ -52,7 +52,7 @@ def _config():
help=_('Columns to be displayed in the book list')) help=_('Columns to be displayed in the book list'))
c.add_opt('autolaunch_server', default=False, help=_('Automatically launch content server on application startup')) c.add_opt('autolaunch_server', default=False, help=_('Automatically launch content server on application startup'))
c.add_opt('oldest_news', default=60, help=_('Oldest news kept in database')) c.add_opt('oldest_news', default=60, help=_('Oldest news kept in database'))
c.add_opt('systray_icon', default=True, help=_('Show system tray icon')) c.add_opt('systray_icon', default=False, help=_('Show system tray icon'))
c.add_opt('upload_news_to_device', default=True, c.add_opt('upload_news_to_device', default=True,
help=_('Upload downloaded news to device')) help=_('Upload downloaded news to device'))
c.add_opt('delete_news_from_library_on_upload', default=False, c.add_opt('delete_news_from_library_on_upload', default=False,

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>422</width>
<height>64</height> <height>64</height>
</rect> </rect>
</property> </property>
@ -30,7 +30,20 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLineEdit" name="edit"/> <widget class="HistoryLineEdit" name="edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>100</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>0</height>
</size>
</property>
</widget>
</item> </item>
</layout> </layout>
</item> </item>
@ -54,8 +67,28 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>HistoryLineEdit</class>
<extends>QComboBox</extends>
<header>calibre/gui2/widgets.h</header>
</customwidget>
</customwidgets>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>
</resources> </resources>

View File

@ -70,6 +70,11 @@ class XPathEdit(QWidget, Ui_Edit):
if wiz.exec_() == wiz.Accepted: if wiz.exec_() == wiz.Accepted:
self.edit.setText(wiz.xpath) self.edit.setText(wiz.xpath)
def setObjectName(self, *args):
QWidget.setObjectName(self, *args)
if hasattr(self, 'edit'):
self.edit.initialize('xpath_edit_'+unicode(self.objectName()))
def set_msg(self, msg): def set_msg(self, msg):
self.msg.setText(msg) self.msg.setText(msg)
@ -95,4 +100,11 @@ class XPathEdit(QWidget, Ui_Edit):
return True return True
if __name__ == '__main__':
from PyQt4.Qt import QApplication
app = QApplication([])
w = XPathEdit()
w.setObjectName('test')
w.show()
app.exec_()
print w.xpath

View File

@ -457,6 +457,12 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
self.open_config_dir) self.open_config_dir)
self.opt_get_social_metadata.setChecked(config['get_social_metadata']) self.opt_get_social_metadata.setChecked(config['get_social_metadata'])
self.opt_enforce_cpu_limit.setChecked(config['enforce_cpu_limit']) self.opt_enforce_cpu_limit.setChecked(config['enforce_cpu_limit'])
self.device_detection_button.clicked.connect(self.debug_device_detection)
def debug_device_detection(self):
from calibre.gui2.dialogs.config.device_debug import DebugDevice
d = DebugDevice(self)
d.exec_()
def open_config_dir(self): def open_config_dir(self):
from calibre.utils.config import config_dir from calibre.utils.config import config_dir

View File

@ -15,7 +15,7 @@
<string>Preferences</string> <string>Preferences</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../../../work/calibre/resources/images.qrc"> <iconset>
<normaloff>:/images/config.svg</normaloff>:/images/config.svg</iconset> <normaloff>:/images/config.svg</normaloff>:/images/config.svg</iconset>
</property> </property>
<layout class="QGridLayout"> <layout class="QGridLayout">
@ -148,7 +148,7 @@
<string>...</string> <string>...</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../../../work/calibre/resources/images.qrc"> <iconset>
<normaloff>:/images/mimetypes/dir.svg</normaloff>:/images/mimetypes/dir.svg</iconset> <normaloff>:/images/mimetypes/dir.svg</normaloff>:/images/mimetypes/dir.svg</iconset>
</property> </property>
</widget> </widget>
@ -285,7 +285,7 @@
<string>...</string> <string>...</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../../../work/calibre/resources/images.qrc"> <iconset>
<normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset> <normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
</property> </property>
</widget> </widget>
@ -309,7 +309,7 @@
<string>...</string> <string>...</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../../../work/calibre/resources/images.qrc"> <iconset>
<normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset> <normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset>
</property> </property>
</widget> </widget>
@ -473,7 +473,7 @@
<string>...</string> <string>...</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../../../work/calibre/resources/images.qrc"> <iconset>
<normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset> <normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
</property> </property>
</widget> </widget>
@ -497,7 +497,7 @@
<string>...</string> <string>...</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../../../work/calibre/resources/images.qrc"> <iconset>
<normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset> <normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset>
</property> </property>
</widget> </widget>
@ -557,7 +557,7 @@
<string>&amp;Add email</string> <string>&amp;Add email</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../../../work/calibre/resources/images.qrc"> <iconset>
<normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset> <normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
@ -584,7 +584,7 @@
<string>&amp;Remove email</string> <string>&amp;Remove email</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../../../work/calibre/resources/images.qrc"> <iconset>
<normaloff>:/images/minus.svg</normaloff>:/images/minus.svg</iconset> <normaloff>:/images/minus.svg</normaloff>:/images/minus.svg</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
@ -649,21 +649,21 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="3" column="0" colspan="2">
<widget class="QPushButton" name="compact_button"> <widget class="QPushButton" name="compact_button">
<property name="text"> <property name="text">
<string>&amp;Check database integrity</string> <string>&amp;Check database integrity</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0" colspan="2"> <item row="5" column="0" colspan="2">
<widget class="QPushButton" name="button_osx_symlinks"> <widget class="QPushButton" name="button_osx_symlinks">
<property name="text"> <property name="text">
<string>&amp;Install command line tools</string> <string>&amp;Install command line tools</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2"> <item row="4" column="0" colspan="2">
<widget class="QPushButton" name="button_open_config_dir"> <widget class="QPushButton" name="button_open_config_dir">
<property name="text"> <property name="text">
<string>Open calibre &amp;configuration directory</string> <string>Open calibre &amp;configuration directory</string>
@ -677,6 +677,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2">
<widget class="QPushButton" name="device_detection_button">
<property name="text">
<string>Debug &amp;device detection</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="page_4"> <widget class="QWidget" name="page_4">
@ -966,7 +973,7 @@
<string>...</string> <string>...</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../../../work/calibre/resources/images.qrc"> <iconset>
<normaloff>:/images/document_open.svg</normaloff>:/images/document_open.svg</iconset> <normaloff>:/images/document_open.svg</normaloff>:/images/document_open.svg</iconset>
</property> </property>
</widget> </widget>

View File

@ -0,0 +1,52 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QDialog, QVBoxLayout, QPlainTextEdit, QTimer, \
QDialogButtonBox, QPushButton, QApplication, QIcon
class DebugDevice(QDialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent)
self._layout = QVBoxLayout(self)
self.setLayout(self._layout)
self.log = QPlainTextEdit(self)
self._layout.addWidget(self.log)
self.log.setPlainText(_('Getting debug information')+'...')
self.copy = QPushButton(_('Copy to &clipboard'))
self.copy.setDefault(True)
self.setWindowTitle(_('Debug device detection'))
self.setWindowIcon(QIcon(I('debug.svg')))
self.copy.clicked.connect(self.copy_to_clipboard)
self.ok = QPushButton('&OK')
self.ok.setAutoDefault(False)
self.ok.clicked.connect(self.accept)
self.bbox = QDialogButtonBox(self)
self.bbox.addButton(self.copy, QDialogButtonBox.ActionRole)
self.bbox.addButton(self.ok, QDialogButtonBox.AcceptRole)
self._layout.addWidget(self.bbox)
self.resize(750, 500)
self.bbox.setEnabled(False)
QTimer.singleShot(1000, self.debug)
def debug(self):
try:
from calibre.devices import debug
raw = debug()
self.log.setPlainText(raw)
finally:
self.bbox.setEnabled(True)
def copy_to_clipboard(self):
QApplication.clipboard().setText(self.log.toPlainText())
if __name__ == '__main__':
app = QApplication([])
d = DebugDevice()
d.exec_()

111
src/calibre/gui2/notify.py Normal file
View File

@ -0,0 +1,111 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from calibre.constants import islinux
class Notifier(object):
DEFAULT_TIMEOUT = 5000
def get_msg_parms(self, timeout, body, summary):
if summary is None:
summary = 'calibre'
if timeout == 0:
timeout = self.DEFAULT_TIMEOUT
return timeout, body, summary
def __call__(self, body, summary=None, replaces_id=None, timeout=0):
raise NotImplementedError
class DBUSNotifier(Notifier):
ICON = I('notify.png')
def __init__(self, server, path):
self.ok, self.err = True, None
try:
import dbus
self.dbus = dbus
self._notify = dbus.SessionBus().get_object(server, path)
except Exception, err:
self.ok = False
self.err = str(err)
class KDENotifier(DBUSNotifier):
def __init__(self):
DBUSNotifier.__init__(self, 'org.kde.VisualNotifications',
'/VisualNotifications')
def __call__(self, body, summary=None, replaces_id=None, timeout=0):
if replaces_id is None:
replaces_id = self.dbus.UInt32()
event_id = ''
timeout, body, summary = self.get_msg_parms(timeout, body, summary)
self._notify.Notify('calibre', replaces_id, event_id, self.ICON, summary, body,
self.dbus.Array(signature='s'), self.dbus.Dictionary(signature='sv'),
timeout)
class FDONotifier(DBUSNotifier):
def __init__(self):
DBUSNotifier.__init__(self, 'org.freedesktop.Notifications',
'/org/freedesktop/Notifications')
def __call__(self, body, summary=None, replaces_id=None, timeout=0):
if replaces_id is None:
replaces_id = self.dbus.UInt32()
timeout, body, summary = self.get_msg_parms(timeout, body, summary)
self._notify.Notify('calibre', replaces_id, self.ICON, summary, body,
self.dbus.Array(signature='s'), self.dbus.Dictionary(signature='sv'),
timeout)
class QtNotifier(Notifier):
def __init__(self, systray=None):
self.systray = systray
self.ok = self.systray is not None and self.systray.supportsMessages()
def __call__(self, body, summary=None, replaces_id=None, timeout=0):
timeout, body, summary = self.get_msg_parms(timeout, body, summary)
if self.systray is not None:
self.systray.showMessage(summary, body, self.systray.Information,
timeout)
def get_notifier(systray=None):
ans = None
if islinux:
ans = KDENotifier()
if not ans.ok:
ans = FDONotifier()
if not ans.ok:
ans = None
if ans is None:
ans = QtNotifier(systray)
if not ans.ok:
ans = None
return ans
if __name__ == '__main__':
n = KDENotifier()
n('hello')
n = FDONotifier()
n('hello')
'''
from PyQt4.Qt import QApplication, QSystemTrayIcon, QIcon
app = QApplication([])
ic = QIcon(I('notify.png'))
tray = QSystemTrayIcon(ic)
tray.setVisible(True)
n = QtNotifier(tray)
n('hello')
'''

View File

@ -9,6 +9,7 @@ from calibre import fit_image, preferred_encoding, isosx
from calibre.gui2 import qstring_to_unicode, config from calibre.gui2 import qstring_to_unicode, config
from calibre.gui2.widgets import IMAGE_EXTENSIONS from calibre.gui2.widgets import IMAGE_EXTENSIONS
from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.gui2.notify import get_notifier
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
class BookInfoDisplay(QWidget): class BookInfoDisplay(QWidget):
@ -218,6 +219,7 @@ class StatusBar(QStatusBar):
def __init__(self, jobs_dialog, systray=None): def __init__(self, jobs_dialog, systray=None):
QStatusBar.__init__(self) QStatusBar.__init__(self)
self.systray = systray self.systray = systray
self.notifier = get_notifier(systray)
self.movie_button = MovieButton(jobs_dialog) self.movie_button = MovieButton(jobs_dialog)
self.cover_flow_button = CoverFlowButton() self.cover_flow_button = CoverFlowButton()
self.tag_view_button = TagViewButton() self.tag_view_button = TagViewButton()
@ -247,13 +249,13 @@ class StatusBar(QStatusBar):
def showMessage(self, msg, timeout=0): def showMessage(self, msg, timeout=0):
ret = QStatusBar.showMessage(self, msg, timeout) ret = QStatusBar.showMessage(self, msg, timeout)
if self.systray is not None and not config['disable_tray_notification']: if self.notifier is not None and not config['disable_tray_notification']:
if isosx and isinstance(msg, unicode): if isosx and isinstance(msg, unicode):
try: try:
msg = msg.encode(preferred_encoding) msg = msg.encode(preferred_encoding)
except UnicodeEncodeError: except UnicodeEncodeError:
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
self.systray.showMessage('calibre', msg, self.systray.Information, 10000) self.notifier(msg)
return ret return ret
def jobs(self): def jobs(self):

View File

@ -587,20 +587,32 @@ class DocumentView(QWebView):
if self.manager is not None: if self.manager is not None:
self.manager.next_document() self.manager.next_document()
else: else:
oopos = self.document.ypos
#print '\nOriginal position:', oopos
self.document.set_bottom_padding(0) self.document.set_bottom_padding(0)
opos = self.document.ypos opos = self.document.ypos
#print 'After set padding=0:', self.document.ypos
if opos < oopos:
if self.manager is not None:
self.manager.next_document()
return
lower_limit = opos + delta_y # Max value of top y co-ord after scrolling lower_limit = opos + delta_y # Max value of top y co-ord after scrolling
max_y = self.document.height - window_height # The maximum possible top y co-ord max_y = self.document.height - window_height # The maximum possible top y co-ord
if max_y < lower_limit: if max_y < lower_limit:
#print 'Setting padding to:', lower_limit - max_y
self.document.set_bottom_padding(lower_limit - max_y) self.document.set_bottom_padding(lower_limit - max_y)
max_y = self.document.height - window_height max_y = self.document.height - window_height
lower_limit = min(max_y, lower_limit) lower_limit = min(max_y, lower_limit)
#print 'Scroll to:', lower_limit
if lower_limit > opos: if lower_limit > opos:
self.document.scroll_to(self.document.xpos, lower_limit) self.document.scroll_to(self.document.xpos, lower_limit)
actually_scrolled = self.document.ypos - opos actually_scrolled = self.document.ypos - opos
#print 'After scroll pos:', self.document.ypos
self.find_next_blank_line(window_height - actually_scrolled) self.find_next_blank_line(window_height - actually_scrolled)
#print 'After blank line pos:', self.document.ypos
if self.manager is not None: if self.manager is not None:
self.manager.scrolled(self.scroll_fraction) self.manager.scrolled(self.scroll_fraction)
#print 'After all:', self.document.ypos
def scroll_by(self, x=0, y=0, notify=True): def scroll_by(self, x=0, y=0, notify=True):
old_pos = self.document.ypos old_pos = self.document.ypos

View File

@ -11,7 +11,7 @@ from PyQt4.Qt import QListView, QIcon, QFont, QLabel, QListWidget, \
QAbstractListModel, QVariant, Qt, SIGNAL, \ QAbstractListModel, QVariant, Qt, SIGNAL, \
QRegExp, QSettings, QSize, QModelIndex, \ QRegExp, QSettings, QSize, QModelIndex, \
QAbstractButton, QPainter, QLineEdit, QComboBox, \ QAbstractButton, QPainter, QLineEdit, QComboBox, \
QMenu, QStringListModel, QCompleter QMenu, QStringListModel, QCompleter, QStringList
from calibre.gui2 import human_readable, NONE, TableView, \ from calibre.gui2 import human_readable, NONE, TableView, \
qstring_to_unicode, error_dialog qstring_to_unicode, error_dialog
@ -21,9 +21,11 @@ from calibre import fit_image
from calibre.utils.fonts import fontconfig from calibre.utils.fonts import fontconfig
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.metadata.meta import metadata_from_filename from calibre.ebooks.metadata.meta import metadata_from_filename
from calibre.utils.config import prefs from calibre.utils.config import prefs, XMLConfig
from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
history = XMLConfig('history')
class ProgressIndicator(QWidget): class ProgressIndicator(QWidget):
def __init__(self, *args): def __init__(self, *args):
@ -506,16 +508,16 @@ class LineEditECM(object):
menu.exec_(event.globalPos()) menu.exec_(event.globalPos())
def upper_case(self): def upper_case(self):
self.setText(qstring_to_unicode(self.text()).upper()) self.setText(unicode(self.text()).upper())
def lower_case(self): def lower_case(self):
self.setText(qstring_to_unicode(self.text()).lower()) self.setText(unicode(self.text()).lower())
def swap_case(self): def swap_case(self):
self.setText(qstring_to_unicode(self.text()).swapcase()) self.setText(unicode(self.text()).swapcase())
def title_case(self): def title_case(self):
self.setText(qstring_to_unicode(self.text()).title()) self.setText(unicode(self.text()).title())
class EnLineEdit(LineEditECM, QLineEdit): class EnLineEdit(LineEditECM, QLineEdit):
@ -620,7 +622,7 @@ class EnComboBox(QComboBox):
self.setLineEdit(EnLineEdit(self)) self.setLineEdit(EnLineEdit(self))
def text(self): def text(self):
return qstring_to_unicode(self.currentText()) return unicode(self.currentText())
def setText(self, text): def setText(self, text):
idx = self.findText(text, Qt.MatchFixedString) idx = self.findText(text, Qt.MatchFixedString)
@ -629,6 +631,43 @@ class EnComboBox(QComboBox):
idx = 0 idx = 0
self.setCurrentIndex(idx) self.setCurrentIndex(idx)
class HistoryLineEdit(QComboBox):
def __init__(self, *args):
QComboBox.__init__(self, *args)
self.setEditable(True)
self.setInsertPolicy(self.NoInsert)
self.setMaxCount(10)
@property
def store_name(self):
return 'lineedit_history_'+self._name
def initialize(self, name):
self._name = name
self.addItems(QStringList(history.get(self.store_name, [])))
self.setEditText('')
self.lineEdit().editingFinished.connect(self.save_history)
def save_history(self):
items = []
ct = unicode(self.currentText())
if ct:
items.append(ct)
for i in range(self.count()):
item = unicode(self.itemText(i))
if item not in items:
items.append(item)
history.set(self.store_name, items)
def setText(self, t):
self.setEditText(t)
self.lineEdit().setCursorPosition(0)
def text(self):
return self.currentText()
class PythonHighlighter(QSyntaxHighlighter): class PythonHighlighter(QSyntaxHighlighter):
Rules = [] Rules = []

View File

@ -81,7 +81,7 @@ Device Integration
What devices does |app| support? What devices does |app| support?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
At the moment |app| has full support for the SONY PRS 300/500/505/600/700, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle 1/2/DX, Netronix EB600, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Android phones and the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk. At the moment |app| has full support for the SONY PRS 300/500/505/600/700, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle 1/2/DX, Netronix EB600, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, various Android phones and the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
How can I help get my device supported in |app|? How can I help get my device supported in |app|?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -91,17 +91,14 @@ We just need some information from you:
* What e-book formats does your device support? * What e-book formats does your device support?
* Is there a special directory on the device in which all e-book files should be placed? * Is there a special directory on the device in which all e-book files should be placed?
* We also need the output from running the following command in a terminal, both with the device * We also need information about your device that |app| will collect automatically. First, if your
connected and without:: device supports SD cards, insert them. Then connect your device. In calibre go to Preferences->Advanced
and click the "Debug device detection" button. This will create some debug output. Copy it to a file
and repeat the process, this time with your device disconnected.
* Send both the above outputs to us with the other information and we will write a device driver for your
device.
calibre-debug -d Once you send us the output for a particular operating system, support for the device in that operating system
* If your device supports SD cards, run the above command with the cards inserted.
To run the above command, on Windows you should use the full path to calibre-debug.exe
On OSX, you should go to Preferences->Advanced and click "Install command line tools".
Once you send us the output for a particular operating system, support for the device
will appear in the next release of |app|. will appear in the next release of |app|.
@ -124,6 +121,11 @@ If you do need to reset your metadata due to problems caused by using both
at the same time, then just delete the media.xml file on the Reader using at the same time, then just delete the media.xml file on the Reader using
your PC's file explorer and it will be recreated after disconnection. your PC's file explorer and it will be recreated after disconnection.
With recent reader iterations, SONY, in all its wisdom has decided to try to force you to
use their software. If you install it, it auto-launches whenever you connect the reader.
If you don't want to uninstall it altogether, there are a couple of tricks you can use. The
simplest is to simply re-name the executable file that launches the library program. More detail
`here http://www.mobileread.com/forums/showthread.php?t=65809`_.
Can I use the collections feature of the SONY reader? Can I use the collections feature of the SONY reader?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -194,7 +194,8 @@ class RecursiveFetcher(object):
purl[i] = quote(purl[i]) purl[i] = quote(purl[i])
url = urlparse.urlunparse(purl) url = urlparse.urlunparse(purl)
try: try:
with closing(self.browser.open_novisit(url, timeout=self.timeout)) as f: open_func = getattr(self.browser, 'open_novisit', self.browser.open)
with closing(open_func(url, timeout=self.timeout)) as f:
data = response(f.read()+f.read()) data = response(f.read()+f.read())
data.newurl = f.geturl() data.newurl = f.geturl()
except urllib2.URLError, err: except urllib2.URLError, err: