Pull from Trunk

This commit is contained in:
Timothy Legge 2010-06-05 00:03:32 -03:00
commit 6ee7c8418d
38 changed files with 43701 additions and 37578 deletions

View File

@ -4,6 +4,13 @@
# for important features/bug fixes. # for important features/bug fixes.
# Also, each release can have new and improved recipes. # Also, each release can have new and improved recipes.
- version: 0.7.0
date: 2010-06-04
new features:
- title: "Go to http://calibre-ebook.com/new-in/seven to see what's new in 0.7.0"
type: major
- version: 0.6.55 - version: 0.6.55
date: 2010-05-28 date: 2010-05-28

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 B

After

Width:  |  Height:  |  Size: 820 B

View File

@ -5,7 +5,6 @@ __copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
clarin.com clarin.com
''' '''
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class Clarin(BasicNewsRecipe): class Clarin(BasicNewsRecipe):
@ -18,11 +17,12 @@ class Clarin(BasicNewsRecipe):
max_articles_per_feed = 100 max_articles_per_feed = 100
use_embedded_content = False use_embedded_content = False
no_stylesheets = True no_stylesheets = True
cover_url = strftime('http://www.clarin.com/diario/%Y/%m/%d/portada.jpg') encoding = 'utf8'
encoding = 'cp1252' language = 'es_AR'
language = 'es' publication_type = 'newspaper'
masthead_url = 'http://www.clarin.com/shared/v10/img/Hd/lg_Clarin.gif' INDEX = 'http://www.clarin.com'
extra_css = ' body{font-family: Arial,Helvetica,sans-serif} h2{font-family: Georgia,"Times New Roman",Times,serif; font-size: xx-large} .Volan,.Pie,.Autor{ font-size: x-small} .Copete,.Hora{font-size: large} ' masthead_url = 'http://www.clarin.com/static/CLAClarin/images/logo-clarin-print.jpg'
extra_css = ' body{font-family: Arial,Helvetica,sans-serif} h2{font-family: Georgia,serif; font-size: xx-large} .hora{font-weight:bold} .hd p{font-size: small} .nombre-autor{color: #0F325A} '
conversion_options = { conversion_options = {
'comment' : description 'comment' : description
@ -31,27 +31,32 @@ class Clarin(BasicNewsRecipe):
, 'language' : language , 'language' : language
} }
remove_tags = [ keep_only_tags = [dict(attrs={'class':['hd','mt']})]
dict(name='a' , attrs={'class':'Imp' })
,dict(name='div' , attrs={'class':'Perma' })
,dict(name='h1' , text='Imprimir' )
]
feeds = [ feeds = [
(u'Ultimo Momento', u'http://www.clarin.com/diario/hoy/um/sumariorss.xml') (u'Pagina principal', u'http://www.clarin.com/rss/' )
,(u'El Pais' , u'http://www.clarin.com/diario/hoy/elpais.xml' ) ,(u'Politica' , u'http://www.clarin.com/rss/politica/' )
,(u'Opinion' , u'http://www.clarin.com/diario/hoy/opinion.xml' ) ,(u'Deportes' , u'http://www.clarin.com/rss/deportes/' )
,(u'El Mundo' , u'http://www.clarin.com/diario/hoy/elmundo.xml' ) ,(u'Economia' , u'http://www.clarin.com/economia/' )
,(u'Sociedad' , u'http://www.clarin.com/diario/hoy/sociedad.xml' ) ,(u'Mundo' , u'http://www.clarin.com/rss/mundo/' )
,(u'La Ciudad' , u'http://www.clarin.com/diario/hoy/laciudad.xml' ) ,(u'Espectaculos' , u'http://www.clarin.com/rss/espectaculos/')
,(u'Policiales' , u'http://www.clarin.com/diario/hoy/policiales.xml' ) ,(u'Sociedad' , u'http://www.clarin.com/rss/sociedad/' )
,(u'Deportes' , u'http://www.clarin.com/diario/hoy/deportes.xml' ) ,(u'Ciudades' , u'http://www.clarin.com/rss/ciudades/' )
,(u'Policiales' , u'http://www.clarin.com/rss/policiales/' )
,(u'Internet' , u'http://www.clarin.com/rss/internet/' )
,(u'Ciudades' , u'http://www.clarin.com/rss/ciudades/' )
] ]
def print_version(self, url): def print_version(self, url):
rest = url.partition('-0')[-1] return url + '?print=1'
lmain = rest.partition('.')[0]
lurl = u'http://www.servicios.clarin.com/notas/jsp/clarin/v9/notas/imprimir.jsp?pagid=' + lmain
return lurl
def get_cover_url(self):
cover_url = None
soup = self.index_to_soup(self.INDEX)
cover_item = soup.find('div',attrs={'class':'bb-md bb-md-edicion_papel'})
if cover_item:
ap = cover_item.find('a',attrs={'href':'/edicion-impresa/'})
if ap:
cover_url = self.INDEX + ap.img['src']
return cover_url

View File

@ -21,12 +21,16 @@ class weltDe(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
remove_stylesheets = True remove_stylesheets = True
remove_javascript = True remove_javascript = True
encoding = 'iso-8859-1' encoding = 'utf-8'
BasicNewsRecipe.summary_length = 200 html2epub_options = 'linearize_tables = True\nbase_font_size2=10'
BasicNewsRecipe.summary_length = 100
remove_tags = [dict(id='jumplinks'), remove_tags = [dict(id='jumplinks'),
dict(id='ad1'), dict(id='ad1'),
dict(id='top'),
dict(id='header'),
dict(id='additionalNavWrapper'),
dict(id='fullimage_index'), dict(id='fullimage_index'),
dict(id='additionalNav'), dict(id='additionalNav'),
dict(id='printMenu'), dict(id='printMenu'),
@ -35,6 +39,8 @@ class weltDe(BasicNewsRecipe):
dict(id='servicesBox'), dict(id='servicesBox'),
dict(id='servicesNav'), dict(id='servicesNav'),
dict(id='ad2'), dict(id='ad2'),
dict(id='banner_1'),
dict(id='ssoInfoTop'),
dict(id='brandingWrapper'), dict(id='brandingWrapper'),
dict(id='links-intern'), dict(id='links-intern'),
dict(id='navigation'), dict(id='navigation'),
@ -53,10 +59,22 @@ class weltDe(BasicNewsRecipe):
dict(id='xmsg_comment'), dict(id='xmsg_comment'),
dict(id='additionalNavWrapper'), dict(id='additionalNavWrapper'),
dict(id='imagebox'), dict(id='imagebox'),
dict(id='footerContainer'),
#dict(id=''), #dict(id=''),
dict(name='span'), dict(name='span'),
dict(name='div', attrs={'class':'printURL'}), dict(name='div', attrs={'class':'printURL'}),
dict(name='ul', attrs={'class':'clear mainNavigation inline'}),
dict(name='ul', attrs={'class':'inline'}),
dict(name='ul', attrs={'class':'ubar'}),
dict(name='hr', attrs={'class':'ubar'}),
dict(name='li', attrs={'class':'counter'}),
dict(name='li', attrs={'class':'browseBack'}),
dict(name='li', attrs={'class':'browseNext'}),
dict(name='li', attrs={'class':'selected'}),
dict(name='div', attrs={'class':'floatLeft'}),
dict(name='div', attrs={'class':'ad'}), dict(name='div', attrs={'class':'ad'}),
dict(name='div', attrs={'class':'ftBarLeft'}),
dict(name='div', attrs={'class':'clear additionalNav'}),
dict(name='div', attrs={'class':'inlineBox inlineFurtherLinks'}), dict(name='div', attrs={'class':'inlineBox inlineFurtherLinks'}),
dict(name='div', attrs={'class':'inlineBox videoInlineBox'}), dict(name='div', attrs={'class':'inlineBox videoInlineBox'}),
dict(name='div', attrs={'class':'inlineGallery'}), dict(name='div', attrs={'class':'inlineGallery'}),
@ -65,6 +83,23 @@ class weltDe(BasicNewsRecipe):
dict(name='div', attrs={'class':'articleOptions clear'}), dict(name='div', attrs={'class':'articleOptions clear'}),
dict(name='div', attrs={'class':'noPrint galleryIndex'}), dict(name='div', attrs={'class':'noPrint galleryIndex'}),
dict(name='div', attrs={'class':'inlineBox inlineTagCloud'}), dict(name='div', attrs={'class':'inlineBox inlineTagCloud'}),
dict(name='div', attrs={'class':'clear module writeComment bgColor1'}),
dict(name='div', attrs={'class':'clear module textGallery bgColor1'}),
dict(name='div', attrs={'class':'clear module socialMedia bgColor1'}),
dict(name='div', attrs={'class':'clear module continuativeLinks'}),
dict(name='div', attrs={'class':'moreArtH3'}),
dict(name='div', attrs={'class':'jqmWindow'}),
dict(name='div', attrs={'class':'clear gap4'}),
dict(name='div', attrs={'class':'hidden'}),
dict(name='div', attrs={'class':'advertising'}),
dict(name='div', attrs={'class':'ad adMarginBottom'}),
dict(name='div', attrs={'class':'ad'}),
dict(name='div', attrs={'class':'topLine'}),
dict(name='div', attrs={'class':'toplineH2'}),
dict(name='div', attrs={'class':'headLineH3'}),
dict(name='div', attrs={'class':'print'}),
dict(name='div', attrs={'class':'clear menu'}),
dict(name='div', attrs={'class':'clear galleryContent'}),
dict(name='p', attrs={'class':'jump'}), dict(name='p', attrs={'class':'jump'}),
dict(name='a', attrs={'class':'commentLink'}), dict(name='a', attrs={'class':'commentLink'}),
dict(name='h2', attrs={'class':'jumpHeading'}), dict(name='h2', attrs={'class':'jumpHeading'}),
@ -75,7 +110,7 @@ class weltDe(BasicNewsRecipe):
dict(name='table', attrs={'class':'textGallery'}), 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(name='div', attrs={'class':'clear departmentLine'})]
extra_css = ''' extra_css = '''
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #003399;} h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #003399;}
@ -87,7 +122,6 @@ class weltDe(BasicNewsRecipe):
.photo {font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #666666;} ''' .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'),
('Wirtschaft', 'http://welt.de/wirtschaft/?service=Rss'), ('Wirtschaft', 'http://welt.de/wirtschaft/?service=Rss'),
('Finanzen', 'http://welt.de/finanzen/?service=Rss'), ('Finanzen', 'http://welt.de/finanzen/?service=Rss'),
('Sport', 'http://welt.de/sport/?service=Rss'), ('Sport', 'http://welt.de/sport/?service=Rss'),
@ -101,4 +135,5 @@ class weltDe(BasicNewsRecipe):
def print_version(self, url): def print_version(self, url):
return url.replace ('.html', '.html?print=yes') return url.replace ('.html', '.html?print=true')

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.6.55' __version__ = '0.7.0'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re import re

View File

@ -44,6 +44,12 @@ if iswindows:
] ]
class ITUNES(DevicePlugin): class ITUNES(DevicePlugin):
'''
try:
pythoncom.CoInitialize()
finally:
pythoncom.CoUninitialize()
'''
name = 'Apple device interface' name = 'Apple device interface'
gui_name = 'Apple device' gui_name = 'Apple device'
@ -51,7 +57,8 @@ class ITUNES(DevicePlugin):
description = _('Communicate with iBooks through iTunes.') description = _('Communicate with iBooks through iTunes.')
supported_platforms = ['osx','windows'] supported_platforms = ['osx','windows']
author = 'GRiker' author = 'GRiker'
driver_version = '0.2' #: The version of this plugin as a 3-tuple (major, minor, revision)
version = (1, 0, 0)
OPEN_FEEDBACK_MESSAGE = _( OPEN_FEEDBACK_MESSAGE = _(
'Apple device detected, launching iTunes, please wait ...') 'Apple device detected, launching iTunes, please wait ...')
@ -68,6 +75,7 @@ class ITUNES(DevicePlugin):
# Properties # Properties
cached_books = {} cached_books = {}
cache_dir = os.path.join(config_dir, 'caches', 'itunes') cache_dir = os.path.join(config_dir, 'caches', 'itunes')
ejected = False
iTunes= None iTunes= None
log = Log() log = Log()
path_template = 'iTunes/%s - %s.epub' path_template = 'iTunes/%s - %s.epub'
@ -99,16 +107,19 @@ class ITUNES(DevicePlugin):
if isosx: if isosx:
if DEBUG: if DEBUG:
self.log.info( "ITUNES.add_books_to_metadata()") self.log.info( "ITUNES.add_books_to_metadata()")
self._dump_update_list('add_books_to_metadata()')
for (j,p_book) in enumerate(self.update_list): for (j,p_book) in enumerate(self.update_list):
#self.log.info("ITUNES.add_books_to_metadata(): looking for %s" % p_book['lib_book']) self.log.info("ITUNES.add_books_to_metadata(): looking for %s" %
str(p_book['lib_book'])[-9:])
for i,bl_book in enumerate(booklists[0]): for i,bl_book in enumerate(booklists[0]):
#self.log.info("ITUNES.add_books_to_metadata(): evaluating %s" % bl_book.library_id)
if bl_book.library_id == p_book['lib_book']: if bl_book.library_id == p_book['lib_book']:
booklists[0].pop(i) booklists[0].pop(i)
#self.log.info("ITUNES.add_books_to_metadata(): removing %s" % p_book['title']) self.log.info("ITUNES.add_books_to_metadata(): removing %s %s" %
(p_book['title'], str(p_book['lib_book'])[-9:]))
break break
else: else:
self.log.error(" update_list item '%s' not found in booklists[0]" % p_book['title']) self.log.error(" update_list item '%s' by %s %s not found in booklists[0]" %
(p_book['title'], p_book['author'],str(p_book['lib_book'])[-9:]))
if self.report_progress is not None: if self.report_progress is not None:
self.report_progress(j+1/task_count, _('Updating device metadata listing...')) self.report_progress(j+1/task_count, _('Updating device metadata listing...'))
@ -136,7 +147,12 @@ class ITUNES(DevicePlugin):
# Add new books to booklists[0] # Add new books to booklists[0]
for new_book in locations[0]: for new_book in locations[0]:
if DEBUG:
self.log.info(" adding '%s' by '%s' to booklists[0]" %
(new_book.title, new_book.author))
booklists[0].append(new_book) booklists[0].append(new_book)
if DEBUG:
self._dump_booklist(booklists[0],'after add_books_to_metadata()')
def books(self, oncard=None, end_session=True): def books(self, oncard=None, end_session=True):
""" """
@ -153,10 +169,10 @@ class ITUNES(DevicePlugin):
list of device books. list of device books.
""" """
if DEBUG:
self.log.info("ITUNES:books(oncard=%s)" % oncard)
if not oncard: if not oncard:
if DEBUG:
self.log.info("ITUNES:books(oncard=%s)" % oncard)
# Fetch a list of books from iPod device connected to iTunes # Fetch a list of books from iPod device connected to iTunes
# Fetch Library|Books # Fetch Library|Books
@ -187,7 +203,7 @@ class ITUNES(DevicePlugin):
cached_books[this_book.path] = { cached_books[this_book.path] = {
'title':book.name(), 'title':book.name(),
'author':book.artist(), 'author':[book.artist()],
'lib_book':library_books[this_book.path] if this_book.path in library_books else None 'lib_book':library_books[this_book.path] if this_book.path in library_books else None
} }
@ -232,7 +248,8 @@ class ITUNES(DevicePlugin):
self.report_progress(1.0, _('finished')) self.report_progress(1.0, _('finished'))
self.cached_books = cached_books self.cached_books = cached_books
if DEBUG: if DEBUG:
self._dump_cached_books() self._dump_booklist(booklist, 'returning from books():')
self._dump_cached_books('returning from books():')
return booklist return booklist
else: else:
return [] return []
@ -254,31 +271,49 @@ class ITUNES(DevicePlugin):
if self.iTunes: if self.iTunes:
# Check for connected book-capable device # Check for connected book-capable device
try: self.sources = self._get_sources()
''' if 'iPod' in self.sources:
names = [s.name() for s in self.iTunes.sources()] #if DEBUG:
kinds = [str(s.kind()).rpartition('.')[2] for s in self.iTunes.sources()] #sys.stdout.write('.')
self.sources = sources = dict(zip(kinds,names)) #sys.stdout.flush()
''' return True
self.sources = self._get_sources() else:
if 'iPod' in self.sources: if DEBUG:
if DEBUG: sys.stdout.write('-')
sys.stdout.write('.') sys.stdout.flush()
sys.stdout.flush()
return True
else:
if DEBUG:
self.log.info("ITUNES.can_handle(): device ejected")
return False
except:
# iTunes connection failed, probably not running anymore
self.log.error("ITUNES.can_handle(): lost connection to iTunes")
return False return False
else: else:
# can_handle() is called once before open(), so need to return True # Called at entry
# to keep things going # We need to know if iTunes sees the iPad
# It may have been ejected
if DEBUG: if DEBUG:
self.log.info("ITUNES:can_handle(): iTunes not yet instantiated") self.log.info("ITUNES.can_handle()")
self._launch_iTunes()
self.sources = self._get_sources()
if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''):
attempts = 9
while attempts:
# If iTunes was just launched, device may not be detected yet
self.sources = self._get_sources()
if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''):
attempts -= 1
time.sleep(0.5)
if DEBUG:
self.log.warning(" waiting for identified iPad, attempt #%d" % (10 - attempts))
else:
if DEBUG:
self.log.info(' found connected iPad in iTunes')
break
else:
# iTunes running, but not connected iPad
if DEBUG:
self.log.info(' self.ejected = True')
self.ejected = True
return False
else:
self.log.info(' found connected iPad in sources')
return True return True
def can_handle_windows(self, device_id, debug=False): def can_handle_windows(self, device_id, debug=False):
@ -292,35 +327,74 @@ class ITUNES(DevicePlugin):
:param device_info: On windows a device ID string. On Unix a tuple of :param device_info: On windows a device ID string. On Unix a tuple of
``(vendor_id, product_id, bcd)``. ``(vendor_id, product_id, bcd)``.
iPad implementation notes:
It is necessary to use this method to check for the presence of a connected
iPad, as we have to return True if we can handle device interaction, or False if not.
''' '''
if self.iTunes: if self.iTunes:
# Check for connected book-capable device # We've previously run, so the user probably ejected the device
try: try:
''' pythoncom.CoInitialize()
names = [s.name() for s in self.iTunes.sources()]
kinds = [str(s.kind()).rpartition('.')[2] for s in self.iTunes.sources()]
self.sources = sources = dict(zip(kinds,names))
'''
self.sources = self._get_sources() self.sources = self._get_sources()
if 'iPod' in self.sources: if 'iPod' in self.sources:
if DEBUG: if DEBUG:
sys.stdout.write('.') sys.stdout.write('.')
sys.stdout.flush() sys.stdout.flush()
if DEBUG:
self.log.info('ITUNES.can_handle_windows:\n confirming connected iPad')
self.ejected = False
return True return True
else: else:
if DEBUG: if DEBUG:
self.log.info("ITUNES.can_handle(): device ejected") self.log.info("ITUNES.can_handle_windows():\n device ejected")
self.ejected = True
return False return False
except: except:
# iTunes connection failed, probably not running anymore # iTunes connection failed, probably not running anymore
self.log.error("ITUNES.can_handle(): lost connection to iTunes")
self.log.error("ITUNES.can_handle_windows():\n lost connection to iTunes")
return False return False
finally:
pythoncom.CoUninitialize()
else: else:
# can_handle_windows() is called once before open(), so need to return True # This is called at entry
# to keep things going # We need to know if iTunes sees the iPad
# It may have been ejected
if DEBUG: if DEBUG:
self.log.info("ITUNES:can_handle(): iTunes not yet instantiated") self.log.info("ITUNES:can_handle_windows():\n Launching iTunes")
try:
pythoncom.CoInitialize()
self._launch_iTunes()
self.sources = self._get_sources()
if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''):
attempts = 9
while attempts:
# If iTunes was just launched, device may not be detected yet
self.sources = self._get_sources()
if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''):
attempts -= 1
time.sleep(0.5)
if DEBUG:
self.log.warning(" waiting for identified iPad, attempt #%d" % (10 - attempts))
else:
if DEBUG:
self.log.info(' found connected iPad in iTunes')
break
else:
# iTunes running, but not connected iPad
if DEBUG:
self.log.info(' self.ejected = True')
self.ejected = True
return False
else:
self.log.info(' found connected iPad in sources')
finally:
pythoncom.CoUninitialize()
return True return True
def card_prefix(self, end_session=True): def card_prefix(self, end_session=True):
@ -333,8 +407,6 @@ class ITUNES(DevicePlugin):
('place', None) ('place', None)
(None, None) (None, None)
''' '''
if DEBUG:
self.log.info("ITUNES:card_prefix()")
return (None,None) return (None,None)
def delete_books(self, paths, end_session=True): def delete_books(self, paths, end_session=True):
@ -399,6 +471,8 @@ class ITUNES(DevicePlugin):
@return: A 3 element list with free space in bytes of (1, 2, 3). If a @return: A 3 element list with free space in bytes of (1, 2, 3). If a
particular device doesn't have any of these locations it should return -1. particular device doesn't have any of these locations it should return -1.
In Windows, a sync-in-progress blocks this call until sync is complete
""" """
if DEBUG: if DEBUG:
self.log.info("ITUNES:free_space()") self.log.info("ITUNES:free_space()")
@ -411,13 +485,19 @@ class ITUNES(DevicePlugin):
elif iswindows: elif iswindows:
if 'iPod' in self.sources: if 'iPod' in self.sources:
try:
pythoncom.CoInitialize() while True:
self.iTunes = win32com.client.Dispatch("iTunes.Application") try:
connected_device = self.sources['iPod'] try:
free_space = self.iTunes.sources.ItemByName(connected_device).FreeSpace pythoncom.CoInitialize()
finally: self.iTunes = win32com.client.Dispatch("iTunes.Application")
pythoncom.CoUninitialize() connected_device = self.sources['iPod']
free_space = self.iTunes.sources.ItemByName(connected_device).FreeSpace
finally:
pythoncom.CoUninitialize()
break
except:
self.log.error(' waiting for free_space() call to go through')
return (free_space,-1,-1) return (free_space,-1,-1)
@ -450,61 +530,11 @@ class ITUNES(DevicePlugin):
mounted. The base class within USBMS device.py has a implementation of mounted. The base class within USBMS device.py has a implementation of
this function that should serve as a good example for USB Mass storage this function that should serve as a good example for USB Mass storage
devices. devices.
Note that most of the initialization is necessarily performed in can_handle(), as
we need to talk to iTunes to discover if there's a connected iPod
''' '''
if isosx:
# Launch iTunes if not already running
if DEBUG:
self.log.info("ITUNES:open(): Instantiating iTunes")
# Instantiate iTunes
running_apps = appscript.app('System Events')
if not 'iTunes' in running_apps.processes.name():
if DEBUG:
self.log.info( "ITUNES:open(): Launching iTunes" )
self.iTunes = iTunes= appscript.app('iTunes', hide=True)
iTunes.run()
initial_status = 'launched'
else:
self.iTunes = appscript.app('iTunes')
initial_status = 'already running'
if DEBUG:
self.log.info( " %s - %s (%s), driver version %s" %
(self.iTunes.name(), self.iTunes.version(), initial_status, self.driver_version))
# Init the iTunes source list
'''
names = [s.name() for s in self.iTunes.sources()]
kinds = [str(s.kind()).rpartition('.')[2] for s in self.iTunes.sources()]
self.sources = dict(zip(kinds,names))
'''
self.sources = self._get_sources()
elif iswindows:
# Launch iTunes if not already running
if DEBUG:
self.log.info("ITUNES:open(): Instantiating iTunes")
# Instantiate iTunes
try:
pythoncom.CoInitialize()
self.iTunes = win32com.client.Dispatch("iTunes.Application")
if not DEBUG:
self.iTunes.Windows[0].Minimized = True
initial_status = 'launched'
if DEBUG:
self.log.info( " %s - %s (%s), driver version %s" %
(self.iTunes.Windows[0].name, self.iTunes.Version, initial_status, self.driver_version))
# Init the iTunes source list
self.sources = self._get_sources()
finally:
pythoncom.CoUninitialize()
# Confirm/create thumbs archive # Confirm/create thumbs archive
archive_path = os.path.join(self.cache_dir, "thumbs.zip") archive_path = os.path.join(self.cache_dir, "thumbs.zip")
@ -545,10 +575,10 @@ class ITUNES(DevicePlugin):
self.log.error("ITUNES.remove_books_from_metadata(): '%s' not found in self.cached_book" % path) self.log.error("ITUNES.remove_books_from_metadata(): '%s' not found in self.cached_book" % path)
# Remove from cached_books # Remove from cached_books
self.cached_books.pop(path)
if DEBUG: if DEBUG:
self.log.info("ITUNES.remove_books_from_metadata(): Removing '%s' from self.cached_books" % path) self.log.info("ITUNES.remove_books_from_metadata(): Removing '%s' from self.cached_books" % path)
self.cached_books.pop(path) self._dump_cached_books('remove_books_from_metadata()')
else: else:
self.log.warning("ITUNES.remove_books_from_metadata(): skipping purchased book, can't remove via automation interface") self.log.warning("ITUNES.remove_books_from_metadata(): skipping purchased book, can't remove via automation interface")
@ -573,8 +603,6 @@ class ITUNES(DevicePlugin):
If it is called with -1 that means that the If it is called with -1 that means that the
task does not have any progress information task does not have any progress information
''' '''
if DEBUG:
self.log.info("ITUNES:set_progress_reporter()")
self.report_progress = report_progress self.report_progress = report_progress
def settings(self): def settings(self):
@ -582,8 +610,6 @@ class ITUNES(DevicePlugin):
Should return an opts object. The opts object should have one attribute Should return an opts object. The opts object should have one attribute
`format_map` which is an ordered list of formats for the device. `format_map` which is an ordered list of formats for the device.
''' '''
if DEBUG:
self.log.info("ITUNES.settings()")
klass = self if isinstance(self, type) else self.__class__ klass = self if isinstance(self, type) else self.__class__
c = Config('device_drivers_%s' % klass.__name__, _('settings for device drivers')) c = Config('device_drivers_%s' % klass.__name__, _('settings for device drivers'))
c.add_opt('format_map', default=self.FORMATS, c.add_opt('format_map', default=self.FORMATS,
@ -600,24 +626,23 @@ class ITUNES(DevicePlugin):
if DEBUG: if DEBUG:
self.log.info("ITUNES:sync_booklists():") self.log.info("ITUNES:sync_booklists():")
if self.update_needed: if self.update_needed:
if DEBUG:
self.log.info(' calling _update_device')
self._update_device(msg=self.update_msg) self._update_device(msg=self.update_msg)
self.update_needed = False self.update_needed = False
# Get actual size of updated books on device # Get actual size of updated books on device
if self.update_list: if self.update_list:
if DEBUG: if DEBUG:
self.log.info("ITUNES:sync_booklists()\n update_list:") self._dump_update_list(header='sync_booklists()')
for ub in self.update_list:
self.log.info(" '%s' by %s" % (ub['title'], ub['author']))
if isosx: if isosx:
for updated_book in self.update_list: for updated_book in self.update_list:
size_on_device = self._get_device_book_size(updated_book['title'], updated_book['author']) size_on_device = self._get_device_book_size(updated_book['title'],
updated_book['author'][0])
if size_on_device: if size_on_device:
for book in booklists[0]: for book in booklists[0]:
if book.title == updated_book['title'] and \ if book.title == updated_book['title'] and \
book.author[0] == updated_book['author']: book.author == updated_book['author']:
book.size = size_on_device
break break
else: else:
self.log.error("ITUNES:sync_booklists(): could not update book size for '%s'" % updated_book['title']) self.log.error("ITUNES:sync_booklists(): could not update book size for '%s'" % updated_book['title'])
@ -705,15 +730,24 @@ class ITUNES(DevicePlugin):
"Click 'Show Details' for a list.") "Click 'Show Details' for a list.")
if isosx: if isosx:
if DEBUG:
self.log.info("ITUNES.upload_books():")
self._dump_files(files, header='upload_books()')
self._dump_cached_books('upload_books()')
self._dump_update_list('upload_books()')
for (i,file) in enumerate(files): for (i,file) in enumerate(files):
path = self.path_template % (metadata[i].title, metadata[i].author[0]) path = self.path_template % (metadata[i].title, metadata[i].author[0])
# Delete existing from Library|Books, add to self.update_list # Delete existing from Library|Books, add to self.update_list
# for deletion from booklist[0] during add_books_to_metadata # for deletion from booklist[0] during add_books_to_metadata
if path in self.cached_books: if path in self.cached_books:
if DEBUG:
self.log.info(" adding '%s' by %s to self.update_list" %
(self.cached_books[path]['title'],self.cached_books[path]['author']))
# *** Second time a book is updated the author is a list ***
self.update_list.append(self.cached_books[path]) self.update_list.append(self.cached_books[path])
if DEBUG: if DEBUG:
self.log.info("ITUNES.upload_books():")
self.log.info( " deleting existing '%s'" % (path)) self.log.info( " deleting existing '%s'" % (path))
self._remove_from_iTunes(self.cached_books[path]) self._remove_from_iTunes(self.cached_books[path])
@ -941,26 +975,44 @@ class ITUNES(DevicePlugin):
return (new_booklist, [], []) return (new_booklist, [], [])
# Private methods # Private methods
def _dump_booklist(self,booklist, header="booklists[0]"): def _dump_booklist(self, booklist, header=None):
''' '''
''' '''
self.log.info() if header:
self.log.info(header) msg = '\nbooklist, %s' % header
self.log.info( "%s" % ('-' * len(header))) self.log.info(msg)
for i,book in enumerate(booklist): self.log.info('%s' % ('-' * len(msg)))
self.log.info( "%2d %-25.25s %s" % (i,book.title, book.library_id))
for book in booklist:
if isosx:
self.log.info("%-40.40s %-30.30s %-10.10s" %
(book.title, book.author, str(book.library_id)[-9:]))
elif iswindows:
self.log.info("%-40.40s %-30.30s" %
(book.title, book.author))
def _dump_cached_books(self, header=None):
'''
'''
if header:
msg = '\nself.cached_books, %s' % header
self.log.info(msg)
self.log.info( "%s" % ('-' * len(msg)))
if isosx:
for cb in self.cached_books.keys():
self.log.info("%-40.40s %-30.30s %-10.10s" %
(self.cached_books[cb]['title'],
self.cached_books[cb]['author'],
str(self.cached_books[cb]['lib_book'])[-9:]))
elif iswindows:
for cb in self.cached_books.keys():
self.log.info("%-40.40s %-30.30s" %
(self.cached_books[cb]['title'],
self.cached_books[cb]['author']))
self.log.info() self.log.info()
def _dump_cached_books(self): def _dump_hex(self, src, length=16):
'''
'''
self.log.info("\n%-40.40s %-12.12s" % ('Device Books','In Library'))
self.log.info("%-40.40s %-12.12s" % ('------------','----------'))
for cb in self.cached_books.keys():
self.log.info("%-40.40s %6.6s" % (self.cached_books[cb]['title'], 'yes' if self.cached_books[cb]['lib_book'] else ' no'))
self.log.info("\n")
def _hexdump(self, src, length=16):
''' '''
''' '''
FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])
@ -973,6 +1025,34 @@ class ITUNES(DevicePlugin):
N+=length N+=length
print result print result
def _dump_files(self, files, header=None):
if header:
msg = '\nfiles passed to %s:' % header
self.log.info(msg)
self.log.info( "%s" % ('-' * len(msg)))
for file in files:
self.log.info(file)
self.log.info()
def _dump_update_list(self,header=None):
if header:
msg = '\nself.update_list called from %s' % header
self.log.info(msg)
self.log.info( "%s" % ('-' * len(msg)))
if isosx:
for ub in self.update_list:
self.log.info("%-40.40s %-30.30s %-10.10s" %
(ub['title'],
ub['author'],
str(ub['lib_book'])[-9:]))
elif iswindows:
for ub in self.update_list:
self.log.info("%-40.40s %-30.30s" %
(ub['title'],
ub['author']))
self.log.info()
def _find_device_book(self, cached_book): def _find_device_book(self, cached_book):
''' '''
Windows-only method to get a handle to a device book in the current pythoncom session Windows-only method to get a handle to a device book in the current pythoncom session
@ -1034,11 +1114,11 @@ class ITUNES(DevicePlugin):
except: except:
zfw = zipfile.ZipFile(archive_path, mode='a') zfw = zipfile.ZipFile(archive_path, mode='a')
else: else:
if DEBUG: # if DEBUG:
if isosx: # if isosx:
self.log.info("ITUNES._generate_thumbnail(): cached thumb found for '%s'" % book.name()) # self.log.info("ITUNES._generate_thumbnail(): cached thumb found for '%s'" % book.name())
elif iswindows: # elif iswindows:
self.log.info("ITUNES._generate_thumbnail(): cached thumb found for '%s'" % book.Name) # self.log.info("ITUNES._generate_thumbnail(): cached thumb found for '%s'" % book.Name)
return thumb_data return thumb_data
@ -1046,7 +1126,7 @@ class ITUNES(DevicePlugin):
try: try:
# Resize the cover # Resize the cover
data = book.artworks[1].raw_data().data data = book.artworks[1].raw_data().data
#self._hexdump(data[:256]) #self._dump_hex(data[:256])
im = PILImage.open(cStringIO.StringIO(data)) im = PILImage.open(cStringIO.StringIO(data))
scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80) scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80)
im = im.resize((int(width),int(height)), PILImage.ANTIALIAS) im = im.resize((int(width),int(height)), PILImage.ANTIALIAS)
@ -1097,27 +1177,27 @@ class ITUNES(DevicePlugin):
def _get_device_book_size(self, title, author): def _get_device_book_size(self, title, author):
''' '''
Fetch the size of a book stored on the device Fetch the size of a book stored on the device
Windows: If sync-in-progress, this call blocked until sync completes
''' '''
if DEBUG: if DEBUG:
self.log.info("ITUNES._get_device_book_size():\n looking for title: '%s' author: %s" % (title,author)) self.log.info("ITUNES._get_device_book_size():\n looking for title: '%s' author: '%s'" %
(title,author))
device_books = self._get_device_books() device_books = self._get_device_books()
if isosx: if isosx:
for d_book in device_books: for d_book in device_books:
if DEBUG:
self.log.info(" evaluating title: '%s' author: '%s'" % (d_book.name(), d_book.artist()))
if d_book.name() == title and d_book.artist() == author: if d_book.name() == title and d_book.artist() == author:
if DEBUG:
self.log.info(' found it')
return d_book.size() return d_book.size()
else: else:
self.log.error("ITUNES._get_device_book_size(): could not find '%s' by '%s' in device_books" % (title,author)) self.log.error("ITUNES._get_device_book_size():"
" could not find '%s' by '%s' in device_books" % (title,author))
return None return None
elif iswindows: elif iswindows:
for d_book in device_books: for d_book in device_books:
'''
if DEBUG:
self.log.info(" evaluating title: '%s' author: '%s'" % (d_book.Name, d_book.Artist))
'''
if d_book.Name == title and d_book.Artist == author: if d_book.Name == title and d_book.Artist == author:
self.log.info(" found it") self.log.info(" found it")
return d_book.Size return d_book.Size
@ -1143,6 +1223,10 @@ class ITUNES(DevicePlugin):
dev_playlists = [pl.Name for pl in dev.Playlists] dev_playlists = [pl.Name for pl in dev.Playlists]
if 'Books' in dev_playlists: if 'Books' in dev_playlists:
return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Books').Tracks return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Books').Tracks
else:
return []
if DEBUG:
self.log.warning('ITUNES._get_device_book(): No iPod device connected')
return [] return []
def _get_library_books(self): def _get_library_books(self):
@ -1188,9 +1272,10 @@ class ITUNES(DevicePlugin):
return [] return []
elif iswindows: elif iswindows:
dev = self.iTunes.sources.ItemByName(connected_device) dev = self.iTunes.sources.ItemByName(connected_device)
dev_playlists = [pl.Name for pl in dev.Playlists] if dev.Playlists is not None:
if 'Purchased' in dev_playlists: dev_playlists = [pl.Name for pl in dev.Playlists]
return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Purchased').Tracks if 'Purchased' in dev_playlists:
return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Purchased').Tracks
else: else:
return [] return []
@ -1203,6 +1288,7 @@ class ITUNES(DevicePlugin):
kinds = [str(s.kind()).rpartition('.')[2] for s in self.iTunes.sources()] kinds = [str(s.kind()).rpartition('.')[2] for s in self.iTunes.sources()]
return dict(zip(kinds,names)) return dict(zip(kinds,names))
elif iswindows: elif iswindows:
# Assumes a pythoncom wrapper
it_sources = ['Unknown','Library','iPod','AudioCD','MP3CD','Device','RadioTuner','SharedLibrary'] it_sources = ['Unknown','Library','iPod','AudioCD','MP3CD','Device','RadioTuner','SharedLibrary']
names = [s.name for s in self.iTunes.sources] names = [s.name for s in self.iTunes.sources]
kinds = [it_sources[s.kind] for s in self.iTunes.sources] kinds = [it_sources[s.kind] for s in self.iTunes.sources]
@ -1216,16 +1302,79 @@ class ITUNES(DevicePlugin):
else: else:
return True return True
def _launch_iTunes(self):
'''
'''
if DEBUG:
self.log.info("ITUNES:_launch_iTunes():\n Instantiating iTunes")
if isosx:
'''
Launch iTunes if not already running
'''
# Instantiate iTunes
running_apps = appscript.app('System Events')
if not 'iTunes' in running_apps.processes.name():
if DEBUG:
self.log.info( "ITUNES:open(): Launching iTunes" )
self.iTunes = iTunes= appscript.app('iTunes', hide=True)
iTunes.run()
initial_status = 'launched'
else:
self.iTunes = appscript.app('iTunes')
initial_status = 'already running'
if DEBUG:
self.log.info( " [%s - %s (%s), driver version %d.%d.%d]" %
(self.iTunes.name(), self.iTunes.version(), initial_status,
self.version[0],self.version[1],self.version[2]))
if iswindows:
'''
Launch iTunes if not already running
Assumes pythoncom wrapper
'''
# Instantiate iTunes
self.iTunes = win32com.client.Dispatch("iTunes.Application")
if not DEBUG:
self.iTunes.Windows[0].Minimized = True
initial_status = 'launched'
if DEBUG:
self.log.info( " [%s - %s (%s), driver version %d.%d.%d]" %
(self.iTunes.Windows[0].name, self.iTunes.Version, initial_status,
self.version[0],self.version[1],self.version[2]))
def _remove_from_iTunes(self, cached_book): def _remove_from_iTunes(self, cached_book):
''' '''
iTunes does not delete books from storage when removing from database iTunes does not delete books from storage when removing from database
''' '''
if isosx: if isosx:
storage_path = os.path.split(cached_book['lib_book'].location().path) storage_path = os.path.split(cached_book['lib_book'].location().path)
title_storage_path = storage_path[0]
if DEBUG: if DEBUG:
self.log.info("ITUNES._remove_from_iTunes():") self.log.info("ITUNES._remove_from_iTunes():")
self.log.info(" removing storage_path: %s" % storage_path[0]) self.log.info(" removing title_storage_path: %s" % title_storage_path)
shutil.rmtree(storage_path[0]) try:
shutil.rmtree(title_storage_path)
except:
self.log.info(" '%s' not empty" % title_storage_path)
# Clean up title/author directories
author_storage_path = os.path.split(title_storage_path)[0]
self.log.info(" author_storage_path: %s" % author_storage_path)
author_files = os.listdir(author_storage_path)
if '.DS_Store' in author_files:
author_files.pop(author_files.index('.DS_Store'))
if not author_files:
shutil.rmtree(author_storage_path)
if DEBUG:
self.log.info(" removing empty author_storage_path")
else:
if DEBUG:
self.log.info(" author_storage_path not empty (%d objects):" % len(author_files))
self.log.info(" %s" % '\n'.join(author_files))
self.iTunes.delete(cached_book['lib_book']) self.iTunes.delete(cached_book['lib_book'])
elif iswindows: elif iswindows:
@ -1280,7 +1429,7 @@ class ITUNES(DevicePlugin):
try: try:
pythoncom.CoInitialize() pythoncom.CoInitialize()
self.iTunes = win32com.client.Dispatch("iTunes.Application") self.iTunes = win32com.client.Dispatch("iTunes.Application")
#result = self.iTunes.UpdateIPod() self.iTunes.UpdateIPod()
if wait: if wait:
if DEBUG: if DEBUG:
sys.stdout.write(" waiting for iPad sync to complete ...") sys.stdout.write(" waiting for iPad sync to complete ...")
@ -1291,6 +1440,7 @@ class ITUNES(DevicePlugin):
pb_count = len(self._get_purchased_book_ids()) pb_count = len(self._get_purchased_book_ids())
if db_count != lb_count + pb_count: if db_count != lb_count + pb_count:
if DEBUG: if DEBUG:
#sys.stdout.write(' %d != %d + %d\n' % (db_count,lb_count,pb_count))
sys.stdout.write('.') sys.stdout.write('.')
sys.stdout.flush() sys.stdout.flush()
time.sleep(2) time.sleep(2)
@ -1333,8 +1483,6 @@ class BookList(list):
Add the book to the booklist. Intent is to maintain any device-internal Add the book to the booklist. Intent is to maintain any device-internal
metadata. Return True if booklists must be sync'ed metadata. Return True if booklists must be sync'ed
''' '''
if DEBUG:
self.log.info("BookList.add_book():\n%s" % book)
self.append(book) self.append(book)
def remove_book(self, book): def remove_book(self, book):

View File

@ -44,7 +44,8 @@ def get_metadata_(src, encoding=None):
author = match.group(2).replace(',', ';') author = match.group(2).replace(',', ';')
ent_pat = re.compile(r'&(\S+)?;') ent_pat = re.compile(r'&(\S+)?;')
title = ent_pat.sub(entity_to_unicode, title) if title:
title = ent_pat.sub(entity_to_unicode, title)
if author: if author:
author = ent_pat.sub(entity_to_unicode, author) author = ent_pat.sub(entity_to_unicode, author)
mi = MetaInformation(title, [author] if author else None) mi = MetaInformation(title, [author] if author else None)

View File

@ -1334,7 +1334,7 @@ class MobiWriter(object):
item = self._oeb.manifest.hrefs[href] item = self._oeb.manifest.hrefs[href]
try: try:
data = rescale_image(item.data, self._imagemax) data = rescale_image(item.data, self._imagemax)
except IOError: except:
self._oeb.logger.warn('Bad image file %r' % item.href) self._oeb.logger.warn('Bad image file %r' % item.href)
continue continue
self._records.append(data) self._records.append(data)

View File

@ -201,6 +201,11 @@ class CSSFlattener(object):
tag = barename(node.tag) tag = barename(node.tag)
style = stylizer.style(node) style = stylizer.style(node)
cssdict = style.cssdict() cssdict = style.cssdict()
try:
font_size = style['font-size']
except:
font_size = self.sbase if self.sbase is not None else \
self.context.source.fbase
if 'align' in node.attrib: if 'align' in node.attrib:
cssdict['text-align'] = node.attrib['align'] cssdict['text-align'] = node.attrib['align']
del node.attrib['align'] del node.attrib['align']
@ -219,13 +224,16 @@ class CSSFlattener(object):
esize = 1 esize = 1
if esize > 7: if esize > 7:
esize = 7 esize = 7
cssdict['font-size'] = fnums[esize] font_size = fnums[esize]
else: else:
try: try:
cssdict['font-size'] = fnums[force_int(size)] font_size = fnums[force_int(size)]
except: except:
cssdict['font-size'] = fnums[3] font_size = fnums[3]
cssdict['font-size'] = '%.1fpt'%font_size
del node.attrib['size'] del node.attrib['size']
if 'face' in node.attrib:
del node.attrib['face']
if 'color' in node.attrib: if 'color' in node.attrib:
cssdict['color'] = node.attrib['color'] cssdict['color'] = node.attrib['color']
del node.attrib['color'] del node.attrib['color']
@ -244,7 +252,7 @@ class CSSFlattener(object):
cssdict['font-size'] = '%0.5fem'%(fsize/psize) cssdict['font-size'] = '%0.5fem'%(fsize/psize)
psize = fsize psize = fsize
elif 'font-size' in cssdict or tag == 'body': elif 'font-size' in cssdict or tag == 'body':
fsize = self.fmap[style['font-size']] fsize = self.fmap[font_size]
cssdict['font-size'] = "%0.5fem" % (fsize / psize) cssdict['font-size'] = "%0.5fem" % (fsize / psize)
psize = fsize psize = fsize
if cssdict: if cssdict:

View File

@ -75,6 +75,9 @@ class BooksView(QTableView): # {{{
h.setSectionHidden(idx, True) h.setSectionHidden(idx, True)
elif action == 'show': elif action == 'show':
h.setSectionHidden(idx, False) h.setSectionHidden(idx, False)
if h.sectionSize(idx) < 3:
sz = h.sectionSizeHint(idx)
h.resizeSection(idx, sz)
elif action == 'ascending': elif action == 'ascending':
self.sortByColumn(idx, Qt.AscendingOrder) self.sortByColumn(idx, Qt.AscendingOrder)
elif action == 'descending': elif action == 'descending':
@ -257,6 +260,11 @@ class BooksView(QTableView): # {{{
for col, alignment in state.get('column_alignment', {}).items(): for col, alignment in state.get('column_alignment', {}).items():
self._model.change_alignment(col, alignment) self._model.change_alignment(col, alignment)
for i in range(h.count()):
if not h.isSectionHidden(i) and h.sectionSize(i) < 3:
sz = h.sectionSizeHint(i)
h.resizeSection(i, sz)
def get_default_state(self): def get_default_state(self):
old_state = { old_state = {
'hidden_columns': [], 'hidden_columns': [],

View File

@ -25,7 +25,7 @@ BASE_HREFS = {
1 : '/opds', 1 : '/opds',
} }
STANZA_FORMATS = frozenset(['epub', 'pdb']) STANZA_FORMATS = frozenset(['epub', 'pdb', 'pdf', 'cbr', 'cbz', 'djvu'])
def url_for(name, version, **kwargs): def url_for(name, version, **kwargs):
if not name.endswith('_'): if not name.endswith('_'):
@ -121,7 +121,7 @@ def CATALOG_GROUP_ENTRY(item, category, base_href, version, updated):
TITLE(item.text), TITLE(item.text),
ID(id_), ID(id_),
UPDATED(updated), UPDATED(updated),
E.content(_('%d books')%item.count, type='text'), E.content(_('%d items')%item.count, type='text'),
link link
) )

View File

@ -135,29 +135,18 @@ turned into a collection on the reader. Note that the PRS-500 does not support c
How do I use |app| with my iPad/iPhone/iTouch? How do I use |app| with my iPad/iPhone/iTouch?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can access your calibre library on a iPad/iPhone/iTouch over the air using the calibre content server. The easiest way to browse your |app| collection on your Apple device (iPad/iPhone/iPod) is by using the *free* Stanza app, available from the Apple app store. You need at least Stanza version 3.0. Stanza allows you to access your |app| collection wirelessly, over the air.
First perform the following steps in |app| First perform the following steps in |app|
* Set the Preferred Output Format in |app| to EPUB (The output format can be set under Preferences->General) * Set the Preferred Output Format in |app| to EPUB (The output format can be set under Preferences->General)
* Set the output profile to iPad (this will work for iPhone/iPods as well), under Preferences->Conversion->Page Setup
* Convert the books you want to read on your iPhone to EPUB format by selecting them and clicking the Convert button. * Convert the books you want to read on your iPhone to EPUB format by selecting them and clicking the Convert button.
* Turn on the Content Server in |app|'s preferences and leave |app| running. * Turn on the Content Server in |app|'s preferences and leave |app| running.
For an iPad: Install the free Stanza reader app on your iPad/iPhone/iTouch using iTunes.
Install the ReadMe app on your iPad using iTunes. Open the Readme builtin browser and browse to:: Now you should be able to access your books on your iPhone by opening Stanza. Go to "Get Books" and then click the "Shared" tab. Under Shared you will see an entry "Books in calibre". If you don't, make sure your iPad/iPhone is connected using the WiFi network in your house, not 3G. If the |app| catalog is still not detected in Stanza, you can add it manually in Stanza. To do this, click the "Shared" tab, then click the "Edit" button and then click "Add book source" to add a new book source. In the Add Book Source screen enter whatever name you like and in the URL field, enter the following::
http://192.168.1.2:8080/
Replace ``192.168.1.2`` with the local IP address of the computer running |app|. If you have changed the port the |app| content server is running on, you will have to change ``8080`` as well to the new port. The local IP address is the IP address you computer is assigned on your home network. A quick Google search will tell you how to find out your local IP address.
The books in your |app| library will be presented as a list, 25 entries at a time. Click the right arrow to go to the next 25. You can also type in the search box to find specific books. Just click on the EPUB link of the book you want and it will be downloaded into your ReadMe library.
For an iPhone/iTouch:
Install the free Stanza reader app on your iPhone/iTouch using iTunes.
Now you should be able to access your books on your iPhone by opening Stanza. Go to "Get Books" and then click the "Shared" tab. Under Shared you will see an entry "Books in calibre". If you don't, make sure your iPhone is connected using the WiFi network in your house, not 3G. If the |app| catalog is still not detected in Stanza, you can add it manually in Stanza. To do this, click the "Shared" tab, then click the "Edit" button and then click "Add book source" to add a new book source. In the Add Book Source screen enter whatever name you like and in the URL field, enter the following::
http://192.168.1.2:8080/ http://192.168.1.2:8080/
@ -165,7 +154,10 @@ Replace ``192.168.1.2`` with the local IP address of the computer running |app|.
If you get timeout errors while browsing the calibre catalog in Stanza, try increasing the connection timeout value in the stanza settings. Go to Info->Settings and increase the value of Download Timeout. If you get timeout errors while browsing the calibre catalog in Stanza, try increasing the connection timeout value in the stanza settings. Go to Info->Settings and increase the value of Download Timeout.
Note that neither the Stanza, nor the ReadMe apps are in anyway associated with |app|. Alternative for the iPad
^^^^^^^^^^^^^^^^^^^^^^^^^^^
As of |app| version 0.7.0, on windows and OS X you can plugin your iPad into the computer using its charging cable, and |app| will detect it and show you a list of books on the iPad. You can then use the Send to device button to send books directly to iBooks on the iPad.
How do I use |app| with my Android phone? How do I use |app| with my Android phone?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -9,7 +9,7 @@ The Graphical User Interface *(GUI)* provides access to all
library management and ebook format conversion features. The basic workflow library management and ebook format conversion features. The basic workflow
for using |app| is to first add books to the library from your hard disk. for using |app| is to first add books to the library from your hard disk.
|app| will automatically try to read metadata from the books and add them |app| will automatically try to read metadata from the books and add them
to its internal database. Once they are in the database, you can performa various to its internal database. Once they are in the database, you can perform a various
:ref:`actions` on them that include conversion from one format to another, :ref:`actions` on them that include conversion from one format to another,
transfer to the reading device, viewing on your computer, editing metadata, including covers, etc. transfer to the reading device, viewing on your computer, editing metadata, including covers, etc.
@ -243,7 +243,7 @@ Now, you can access your saved search in the Tag Browser under "Searches". A sin
Preferences Preferences
--------------- ---------------
The Preferences dialog allows you to set some global defaults used by all of |app|. To access it, click the |cbi|. The Preferences dialog allows you to change the way various aspects of |app| work. To access it, click the |cbi|.
.. |cbi| image:: images/configuration.png .. |cbi| image:: images/configuration.png
@ -251,7 +251,7 @@ The Preferences dialog allows you to set some global defaults used by all of |ap
Guessing metadata from file names Guessing metadata from file names
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the :guilabel:`Advanced` section of the configuration dialog, you can specify a regularexpression that |app| will use to try and guess metadata from the names of ebook files In the :guilabel:`Add/Save` section of the configuration dialog, you can specify a regular expression that |app| will use to try and guess metadata from the names of ebook files
that you add to the library. The default regular expression is:: that you add to the library. The default regular expression is::
title - author title - author
@ -265,18 +265,13 @@ will be interpreted to have the title: Foundation and Earth and author: Isaac As
.. tip:: .. tip::
If the filename does not contain the hyphen, the regular expression will fail. If the filename does not contain the hyphen, the regular expression will fail.
.. tip::
If you want to only use metadata guessed from filenames and not metadata read from the file itself, you can tell |app| to do this, via the configuration dialog, accessed by the button to the right
of the search box.
.. _book_details: .. _book_details:
Book Details Book Details
------------- -------------
.. image:: images/book_details.png .. image:: images/book_details.png
The Book Details display shows you extra information and the cover for the currently selected book. THe comments section is truncated if the comments are too long. To see the full comments as well as The Book Details display shows you extra information and the cover for the currently selected book.
a larger image of the cover, click anywhere in the Book Details area.
.. _jobs: .. _jobs:

View File

@ -111,6 +111,8 @@ Pre/post processing of downloaded HTML
.. automember:: BasicNewsRecipe.remove_javascript .. automember:: BasicNewsRecipe.remove_javascript
.. automethod:: BasicNewsRecipe.prepreprocess_html
.. automethod:: BasicNewsRecipe.preprocess_html .. automethod:: BasicNewsRecipe.preprocess_html
.. automethod:: BasicNewsRecipe.postprocess_html .. automethod:: BasicNewsRecipe.postprocess_html

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -161,6 +161,19 @@ def create_text_arc(text, font_size, font=None, bgcolor='white'):
p.MagickTrimImage(canvas, 0) p.MagickTrimImage(canvas, 0)
return canvas return canvas
def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
border_color='white'):
with p.ImageMagick():
img = load_image(path_to_image)
lwidth = p.MagickGetImageWidth(img)
lheight = p.MagickGetImageHeight(img)
canvas = create_canvas(lwidth+left+right, lheight+top+bottom,
border_color)
compose_image(canvas, img, left, top)
p.DestroyMagickWand(img)
with open(path_to_image, 'wb') as f:
p.MagickWriteImage(canvas, f)
p.DestroyMagickWand(canvas)
def create_cover_page(top_lines, logo_path, width=590, height=750, def create_cover_page(top_lines, logo_path, width=590, height=750,
bgcolor='white', output_format='png'): bgcolor='white', output_format='png'):

View File

@ -267,7 +267,7 @@ class BasicNewsRecipe(Recipe):
} }
a.article { a.article {
font-weight: bold; font-weight: bold; text-align:left;
} }
a.feed { a.feed {
@ -403,10 +403,25 @@ class BasicNewsRecipe(Recipe):
return url return url
return article.get('link', None) return article.get('link', None)
def prepreprocess_html(self, soup):
'''
This method is called with the source of each downloaded :term:`HTML` file, before
any of the cleanup attributes like remove_tags, keep_only_tags are
applied. Note that preprocess_regexps will have already been applied.
It can be used to do arbitrarily powerful pre-processing on the :term:`HTML`.
It should return `soup` after processing it.
`soup`: A `BeautifulSoup <http://www.crummy.com/software/BeautifulSoup/documentation.html>`_
instance containing the downloaded :term:`HTML`.
'''
return soup
def preprocess_html(self, soup): def preprocess_html(self, soup):
''' '''
This method is called with the source of each downloaded :term:`HTML` file, before This method is called with the source of each downloaded :term:`HTML` file, before
it is parsed for links and images. it is parsed for links and images. It is called after the cleanup as
specified by remove_tags etc.
It can be used to do arbitrarily powerful pre-processing on the :term:`HTML`. It can be used to do arbitrarily powerful pre-processing on the :term:`HTML`.
It should return `soup` after processing it. It should return `soup` after processing it.
@ -523,8 +538,8 @@ class BasicNewsRecipe(Recipe):
Intended to be used to get article metadata like author/summary/etc. Intended to be used to get article metadata like author/summary/etc.
from the parsed HTML (soup). from the parsed HTML (soup).
:param article: A object of class :class:`calibre.web.feeds.Article`. :param article: A object of class :class:`calibre.web.feeds.Article`.
If you chane the sumamry, remeber to also change the If you change the summary, remember to also change the
text_summary text_summary
:param soup: Parsed HTML belonging to this article :param soup: Parsed HTML belonging to this article
:param first: True iff the parsed HTML is the first page of the article. :param first: True iff the parsed HTML is the first page of the article.
''' '''
@ -603,7 +618,7 @@ class BasicNewsRecipe(Recipe):
self.web2disk_options = web2disk_option_parser().parse_args(web2disk_cmdline)[0] self.web2disk_options = web2disk_option_parser().parse_args(web2disk_cmdline)[0]
for extra in ('keep_only_tags', 'remove_tags', 'preprocess_regexps', for extra in ('keep_only_tags', 'remove_tags', 'preprocess_regexps',
'preprocess_html', 'remove_tags_after', 'prepreprocess_html', 'preprocess_html', 'remove_tags_after',
'remove_tags_before', 'is_link_wanted'): 'remove_tags_before', 'is_link_wanted'):
setattr(self.web2disk_options, extra, getattr(self, extra)) setattr(self.web2disk_options, extra, getattr(self, extra))
self.web2disk_options.postprocess_html = self._postprocess_html self.web2disk_options.postprocess_html = self._postprocess_html
@ -758,15 +773,15 @@ class BasicNewsRecipe(Recipe):
if self.touchscreen: if self.touchscreen:
touchscreen_css = u''' touchscreen_css = u'''
.summary_headline { .summary_headline {
font-size:large; font-weight:bold; margin-top:0px; margin-bottom:0px; font-weight:bold; text-align:left;
} }
.summary_byline { .summary_byline {
font-size:small; margin-top:0px; margin-bottom:0px; font-family:monospace;
} }
.summary_text { .summary_text {
margin-top:0px; margin-bottom:0px; text-align:left;
} }
.feed { .feed {
@ -782,12 +797,6 @@ class BasicNewsRecipe(Recipe):
border-width:thin; border-width:thin;
} }
table.toc {
font-size:large;
}
td.article_count {
text-align:right;
}
''' '''
templ = templates.TouchscreenFeedTemplate() templ = templates.TouchscreenFeedTemplate()
@ -1120,8 +1129,11 @@ class BasicNewsRecipe(Recipe):
mi.publisher = __appname__ mi.publisher = __appname__
mi.author_sort = __appname__ mi.author_sort = __appname__
if self.output_profile.name == 'iPad': if self.output_profile.name == 'iPad':
mi.authors = [strftime('%A, %d %B %Y')] date_as_author = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y'))
mi.author_sort = strftime('%Y-%m-%d') mi = MetaInformation(self.short_title(), [date_as_author])
mi.publisher = __appname__
sort_author = re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', self.title).rstrip()
mi.author_sort = '%s %s' % (sort_author, strftime('%Y-%m-%d'))
mi.publication_type = 'periodical:'+self.publication_type mi.publication_type = 'periodical:'+self.publication_type
mi.timestamp = nowf() mi.timestamp = nowf()
mi.comments = self.description mi.comments = self.description
@ -1245,7 +1257,6 @@ class BasicNewsRecipe(Recipe):
with nested(open(opf_path, 'wb'), open(ncx_path, 'wb')) as (opf_file, ncx_file): with nested(open(opf_path, 'wb'), open(ncx_path, 'wb')) as (opf_file, ncx_file):
opf.render(opf_file, ncx_file) opf.render(opf_file, ncx_file)
def article_downloaded(self, request, result): def article_downloaded(self, request, result):
index = os.path.join(os.path.dirname(result[0]), 'index.html') index = os.path.join(os.path.dirname(result[0]), 'index.html')
if index != result[0]: if index != result[0]:

View File

@ -120,6 +120,7 @@ class TouchscreenNavBarTemplate(Template):
href = '%s%s/%s/index.html'%(prefix, up, next) href = '%s%s/%s/index.html'%(prefix, up, next)
navbar.text = '| ' navbar.text = '| '
navbar.append(A('Next', href=href)) navbar.append(A('Next', href=href))
href = '%s../index.html#article_%d'%(prefix, art) href = '%s../index.html#article_%d'%(prefix, art)
navbar.iterchildren(reversed=True).next().tail = ' | ' navbar.iterchildren(reversed=True).next().tail = ' | '
navbar.append(A('Section Menu', href=href)) navbar.append(A('Section Menu', href=href))
@ -130,6 +131,7 @@ class TouchscreenNavBarTemplate(Template):
href = '%s../article_%d/index.html'%(prefix, art-1) href = '%s../article_%d/index.html'%(prefix, art-1)
navbar.iterchildren(reversed=True).next().tail = ' | ' navbar.iterchildren(reversed=True).next().tail = ' | '
navbar.append(A('Previous', href=href)) navbar.append(A('Previous', href=href))
navbar.iterchildren(reversed=True).next().tail = ' | ' navbar.iterchildren(reversed=True).next().tail = ' | '
if not bottom: if not bottom:
navbar.append(HR()) navbar.append(HR())
@ -165,8 +167,14 @@ class TouchscreenIndexTemplate(Template):
def _generate(self, title, masthead, datefmt, feeds, extra_css=None, style=None): def _generate(self, title, masthead, datefmt, feeds, extra_css=None, style=None):
if isinstance(datefmt, unicode): if isinstance(datefmt, unicode):
datefmt = datefmt.encode(preferred_encoding) datefmt = datefmt.encode(preferred_encoding)
date = strftime(datefmt) date = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y'))
masthead_img = IMG(src=masthead,alt="masthead") masthead_p = etree.Element("p")
masthead_p.set("style","text-align:center")
masthead_img = etree.Element("img")
masthead_img.set("src",masthead)
masthead_img.set("alt","masthead")
masthead_p.append(masthead_img)
head = HEAD(TITLE(title)) head = HEAD(TITLE(title))
if style: if style:
head.append(STYLE(style, type='text/css')) head.append(STYLE(style, type='text/css'))
@ -177,15 +185,13 @@ class TouchscreenIndexTemplate(Template):
for i, feed in enumerate(feeds): for i, feed in enumerate(feeds):
if feed: if feed:
tr = TR() tr = TR()
tr.append(TD( CLASS('toc_item'), A(feed.title, href='feed_%d/index.html'%i))) tr.append(TD( CLASS('calibre_rescale_120'), A(feed.title, href='feed_%d/index.html'%i)))
tr.append(TD( CLASS('article_count'),'%d' % len(feed.articles))) tr.append(TD( '%s' % len(feed.articles), style="text-align:right"))
toc.append(tr) toc.append(tr)
div = DIV( div = DIV(
PT(masthead_img,style='text-align:center'), masthead_p,
PT(date, style='text-align:center'), PT(date, style='text-align:center'),
toc, toc)
CLASS('calibre_rescale_100'))
self.root = HTML(head, BODY(div)) self.root = HTML(head, BODY(div))
class FeedTemplate(Template): class FeedTemplate(Template):
@ -271,12 +277,15 @@ class TouchscreenFeedTemplate(Template):
continue continue
tr = TR() tr = TR()
td = TD( td = TD(
A(article.title, CLASS('article calibre_rescale_100', A(article.title, CLASS('summary_headline','calibre_rescale_120',
href=article.url)) href=article.url))
) )
if article.author:
td.append(DIV(article.author,
CLASS('summary_byline', 'calibre_rescale_100')))
if article.summary: if article.summary:
td.append(DIV(cutoff(article.text_summary), td.append(DIV(cutoff(article.text_summary),
CLASS('article_description', 'calibre_rescale_80'))) CLASS('summary_text', 'calibre_rescale_100')))
tr.append(td) tr.append(td)
toc.append(tr) toc.append(tr)
div.append(toc) div.append(toc)

View File

@ -136,6 +136,7 @@ class RecursiveFetcher(object):
self.remove_tags_before = getattr(options, 'remove_tags_before', None) self.remove_tags_before = getattr(options, 'remove_tags_before', None)
self.keep_only_tags = getattr(options, 'keep_only_tags', []) self.keep_only_tags = getattr(options, 'keep_only_tags', [])
self.preprocess_html_ext = getattr(options, 'preprocess_html', lambda soup: soup) self.preprocess_html_ext = getattr(options, 'preprocess_html', lambda soup: soup)
self.prepreprocess_html_ext = getattr(options, 'prepreprocess_html', lambda soup: soup)
self.postprocess_html_ext= getattr(options, 'postprocess_html', None) self.postprocess_html_ext= getattr(options, 'postprocess_html', None)
self._is_link_wanted = getattr(options, 'is_link_wanted', self._is_link_wanted = getattr(options, 'is_link_wanted',
default_is_link_wanted) default_is_link_wanted)
@ -153,6 +154,8 @@ class RecursiveFetcher(object):
nmassage.append((re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')) nmassage.append((re.compile(r'<!--.*?-->', re.DOTALL), lambda m: ''))
soup = BeautifulSoup(xml_to_unicode(src, self.verbose, strip_encoding_pats=True)[0], markupMassage=nmassage) soup = BeautifulSoup(xml_to_unicode(src, self.verbose, strip_encoding_pats=True)[0], markupMassage=nmassage)
soup = self.prepreprocess_html_ext(soup)
if self.keep_only_tags: if self.keep_only_tags:
body = Tag(soup, 'body') body = Tag(soup, 'body')
try: try: