mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Improvements to the iTunes driver
This commit is contained in:
commit
3f759827f7
@ -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 not oncard:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info("ITUNES:books(oncard=%s)" % oncard)
|
self.log.info("ITUNES:books(oncard=%s)" % oncard)
|
||||||
|
|
||||||
if not 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:
|
|
||||||
'''
|
|
||||||
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()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info("ITUNES.can_handle(): device ejected")
|
sys.stdout.write('-')
|
||||||
return False
|
sys.stdout.flush()
|
||||||
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,6 +485,9 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
if 'iPod' in self.sources:
|
if 'iPod' in self.sources:
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
try:
|
try:
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||||
@ -418,6 +495,9 @@ class ITUNES(DevicePlugin):
|
|||||||
free_space = self.iTunes.sources.ItemByName(connected_device).FreeSpace
|
free_space = self.iTunes.sources.ItemByName(connected_device).FreeSpace
|
||||||
finally:
|
finally:
|
||||||
pythoncom.CoUninitialize()
|
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))
|
|
||||||
self.log.info()
|
|
||||||
|
|
||||||
def _dump_cached_books(self):
|
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):
|
||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
self.log.info("\n%-40.40s %-12.12s" % ('Device Books','In Library'))
|
if header:
|
||||||
self.log.info("%-40.40s %-12.12s" % ('------------','----------'))
|
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():
|
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("%-40.40s %-30.30s %-10.10s" %
|
||||||
self.log.info("\n")
|
(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']))
|
||||||
|
|
||||||
def _hexdump(self, src, length=16):
|
self.log.info()
|
||||||
|
|
||||||
|
def _dump_hex(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,6 +1272,7 @@ class ITUNES(DevicePlugin):
|
|||||||
return []
|
return []
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
dev = self.iTunes.sources.ItemByName(connected_device)
|
dev = self.iTunes.sources.ItemByName(connected_device)
|
||||||
|
if dev.Playlists is not None:
|
||||||
dev_playlists = [pl.Name for pl in dev.Playlists]
|
dev_playlists = [pl.Name for pl in dev.Playlists]
|
||||||
if 'Purchased' in dev_playlists:
|
if 'Purchased' in dev_playlists:
|
||||||
return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Purchased').Tracks
|
return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Purchased').Tracks
|
||||||
@ -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()
|
result = 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):
|
||||||
|
@ -267,7 +267,7 @@ class BasicNewsRecipe(Recipe):
|
|||||||
}
|
}
|
||||||
|
|
||||||
a.article {
|
a.article {
|
||||||
font-weight: bold;
|
font-weight: bold; text-align:left;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.feed {
|
a.feed {
|
||||||
@ -773,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 {
|
||||||
@ -797,9 +797,6 @@ class BasicNewsRecipe(Recipe):
|
|||||||
border-width:thin;
|
border-width:thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.toc {
|
|
||||||
font-size:large;
|
|
||||||
}
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
templ = templates.TouchscreenFeedTemplate()
|
templ = templates.TouchscreenFeedTemplate()
|
||||||
@ -1133,8 +1130,10 @@ class BasicNewsRecipe(Recipe):
|
|||||||
mi.author_sort = __appname__
|
mi.author_sort = __appname__
|
||||||
if self.output_profile.name == 'iPad':
|
if self.output_profile.name == 'iPad':
|
||||||
date_as_author = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y'))
|
date_as_author = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y'))
|
||||||
mi.authors = [date_as_author]
|
mi = MetaInformation(self.short_title(), [date_as_author])
|
||||||
mi.author_sort = strftime('%Y-%m-%d')
|
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
|
||||||
@ -1258,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]:
|
||||||
|
@ -185,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( '%s' % len(feed.articles), style="text-align:right"))
|
tr.append(TD( '%s' % len(feed.articles), style="text-align:right"))
|
||||||
toc.append(tr)
|
toc.append(tr)
|
||||||
|
|
||||||
div = DIV(
|
div = DIV(
|
||||||
masthead_p,
|
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):
|
||||||
@ -279,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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user