mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
GwR wip 0.5.0
This commit is contained in:
parent
eb97c9d963
commit
5c316d4d61
@ -5,7 +5,7 @@ __copyright__ = '2010, Gregory Riker'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
import cStringIO, os, re, shutil, subprocess, sys, tempfile, time, zipfile
|
import cStringIO, ctypes, os, re, shutil, subprocess, sys, tempfile, time, zipfile
|
||||||
|
|
||||||
from calibre.constants import DEBUG
|
from calibre.constants import DEBUG
|
||||||
from calibre import fit_image
|
from calibre import fit_image
|
||||||
@ -14,6 +14,7 @@ from calibre.devices.interface import DevicePlugin
|
|||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.library.server.utils import strftime
|
from calibre.library.server.utils import strftime
|
||||||
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.utils.config import Config, config_dir
|
from calibre.utils.config import Config, config_dir
|
||||||
from calibre.utils.date import parse_date
|
from calibre.utils.date import parse_date
|
||||||
from calibre.utils.logging import Log
|
from calibre.utils.logging import Log
|
||||||
@ -47,7 +48,7 @@ class ITUNES(DevicePlugin):
|
|||||||
supported_platforms = ['osx','windows']
|
supported_platforms = ['osx','windows']
|
||||||
author = 'GRiker'
|
author = 'GRiker'
|
||||||
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
||||||
version = (0, 4, 11)
|
version = (0, 5, 0)
|
||||||
|
|
||||||
OPEN_FEEDBACK_MESSAGE = _(
|
OPEN_FEEDBACK_MESSAGE = _(
|
||||||
'Apple device detected, launching iTunes, please wait ...')
|
'Apple device detected, launching iTunes, please wait ...')
|
||||||
@ -101,6 +102,15 @@ class ITUNES(DevicePlugin):
|
|||||||
'Books',
|
'Books',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SearchField = [
|
||||||
|
'All',
|
||||||
|
'Visible',
|
||||||
|
'Artists',
|
||||||
|
'Albums',
|
||||||
|
'Composers',
|
||||||
|
'SongNames',
|
||||||
|
]
|
||||||
|
|
||||||
# Properties
|
# Properties
|
||||||
cached_books = {}
|
cached_books = {}
|
||||||
cache_dir = os.path.join(config_dir, 'caches', 'itunes')
|
cache_dir = os.path.join(config_dir, 'caches', 'itunes')
|
||||||
@ -108,6 +118,7 @@ class ITUNES(DevicePlugin):
|
|||||||
iTunes= None
|
iTunes= None
|
||||||
iTunes_media = None
|
iTunes_media = None
|
||||||
log = Log()
|
log = Log()
|
||||||
|
manual_sync_mode = False
|
||||||
path_template = 'iTunes/%s - %s.epub'
|
path_template = 'iTunes/%s - %s.epub'
|
||||||
problem_titles = []
|
problem_titles = []
|
||||||
problem_msg = None
|
problem_msg = None
|
||||||
@ -182,9 +193,6 @@ class ITUNES(DevicePlugin):
|
|||||||
(new_book.title, new_book.author))
|
(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):
|
||||||
"""
|
"""
|
||||||
Return a list of ebooks on the device.
|
Return a list of ebooks on the device.
|
||||||
@ -210,7 +218,6 @@ class ITUNES(DevicePlugin):
|
|||||||
library_books = self._get_library_books()
|
library_books = self._get_library_books()
|
||||||
|
|
||||||
if 'iPod' in self.sources:
|
if 'iPod' in self.sources:
|
||||||
#device = self.sources['iPod']
|
|
||||||
booklist = BookList(self.log)
|
booklist = BookList(self.log)
|
||||||
cached_books = {}
|
cached_books = {}
|
||||||
|
|
||||||
@ -238,7 +245,8 @@ 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,
|
||||||
|
'dev_book':book
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.report_progress is not None:
|
if self.report_progress is not None:
|
||||||
@ -342,7 +350,7 @@ class ITUNES(DevicePlugin):
|
|||||||
self.log.warning(" waiting for identified iPad, attempt #%d" % (10 - attempts))
|
self.log.warning(" waiting for identified iPad, attempt #%d" % (10 - attempts))
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(' found connected iPad in iTunes')
|
self.log.info(' found connected iPad')
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# iTunes running, but not connected iPad
|
# iTunes running, but not connected iPad
|
||||||
@ -350,9 +358,8 @@ class ITUNES(DevicePlugin):
|
|||||||
self.log.info(' self.ejected = True')
|
self.log.info(' self.ejected = True')
|
||||||
self.ejected = True
|
self.ejected = True
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
self.log.info(' found connected iPad in sources')
|
|
||||||
|
|
||||||
|
self._discover_manual_sync_mode()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def can_handle_windows(self, device_id, debug=False):
|
def can_handle_windows(self, device_id, debug=False):
|
||||||
@ -384,6 +391,7 @@ class ITUNES(DevicePlugin):
|
|||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info('ITUNES.can_handle_windows:\n confirming connected iPad')
|
self.log.info('ITUNES.can_handle_windows:\n confirming connected iPad')
|
||||||
self.ejected = False
|
self.ejected = False
|
||||||
|
self._discover_manual_sync_mode()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -399,9 +407,6 @@ class ITUNES(DevicePlugin):
|
|||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# This is called at entry
|
|
||||||
# We need to know if iTunes sees the iPad
|
|
||||||
# It may have been ejected
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info("ITUNES:can_handle_windows():\n Launching iTunes")
|
self.log.info("ITUNES:can_handle_windows():\n Launching iTunes")
|
||||||
|
|
||||||
@ -429,8 +434,10 @@ class ITUNES(DevicePlugin):
|
|||||||
self.log.info(' self.ejected = True')
|
self.log.info(' self.ejected = True')
|
||||||
self.ejected = True
|
self.ejected = True
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
self.log.info(' found connected iPad in sources')
|
self.log.info(' found connected iPad in sources')
|
||||||
|
self._discover_manual_sync_mode(wait=1.0)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
@ -460,23 +467,31 @@ class ITUNES(DevicePlugin):
|
|||||||
self.problem_msg = _("Some books not found in iTunes database.\n"
|
self.problem_msg = _("Some books not found in iTunes database.\n"
|
||||||
"Delete using the iBooks app.\n"
|
"Delete using the iBooks app.\n"
|
||||||
"Click 'Show Details' for a list.")
|
"Click 'Show Details' for a list.")
|
||||||
|
self.log.info("ITUNES:delete_books()")
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if self.cached_books[path]['lib_book']:
|
if self.cached_books[path]['lib_book']:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info("ITUNES:delete_books(): Deleting '%s' from iTunes library" % (path))
|
self.log.info(" Deleting '%s' from iTunes library" % (path))
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
self._remove_from_iTunes(self.cached_books[path])
|
self._remove_from_iTunes(self.cached_books[path])
|
||||||
|
if self.manual_sync_mode:
|
||||||
|
self._remove_device_book(self.cached_books[path])
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
try:
|
try:
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||||
self._remove_from_iTunes(self.cached_books[path])
|
self._remove_from_iTunes(self.cached_books[path])
|
||||||
|
if self.manual_sync_mode:
|
||||||
|
self._remove_device_book(self.cached_books[path])
|
||||||
finally:
|
finally:
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
self.update_needed = True
|
if not self.manual_sync_mode:
|
||||||
self.update_msg = "Deleted books from device"
|
self.update_needed = True
|
||||||
|
self.update_msg = "Deleted books from device"
|
||||||
|
else:
|
||||||
|
self.log.info(" skipping sync phase, manual_sync_mode: True")
|
||||||
else:
|
else:
|
||||||
self.problem_titles.append("'%s' by %s" %
|
self.problem_titles.append("'%s' by %s" %
|
||||||
(self.cached_books[path]['title'],self.cached_books[path]['author']))
|
(self.cached_books[path]['title'],self.cached_books[path]['author']))
|
||||||
@ -618,7 +633,7 @@ class ITUNES(DevicePlugin):
|
|||||||
# Remove from cached_books
|
# Remove from cached_books
|
||||||
self.cached_books.pop(path)
|
self.cached_books.pop(path)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" Removing '%s' from self.cached_books" % path)
|
self.log.info(" removing '%s' from self.cached_books" % path)
|
||||||
# self._dump_cached_books('remove_books_from_metadata()')
|
# self._dump_cached_books('remove_books_from_metadata()')
|
||||||
else:
|
else:
|
||||||
self.log.warning(" skipping purchased book, can't remove via automation interface")
|
self.log.warning(" skipping purchased book, can't remove via automation interface")
|
||||||
@ -740,34 +755,6 @@ class ITUNES(DevicePlugin):
|
|||||||
# Delete existing from Library|Books
|
# Delete existing from Library|Books
|
||||||
# Add to self.update_list for deletion from booklist[0] during add_books_to_metadata
|
# Add to self.update_list for deletion from booklist[0] during add_books_to_metadata
|
||||||
|
|
||||||
'''
|
|
||||||
# ---------------------------
|
|
||||||
# PROVISIONAL
|
|
||||||
# Use the cover to find the database storage point of the epub
|
|
||||||
# Pass database copy to iTunes instead of the temporary file
|
|
||||||
|
|
||||||
if False:
|
|
||||||
if DEBUG:
|
|
||||||
self.log.info(" processing '%s'" % metadata[i].title)
|
|
||||||
self.log.info(" file: %s" % (file._name if isinstance(file,PersistentTemporaryFile) else file))
|
|
||||||
self.log.info(" cover: %s" % metadata[i].cover)
|
|
||||||
|
|
||||||
calibre_database_item = False
|
|
||||||
if metadata[i].cover:
|
|
||||||
passed_file = file
|
|
||||||
storage_path = os.path.split(metadata[i].cover)[0]
|
|
||||||
try:
|
|
||||||
database_epub = filter(lambda x: x.endswith('.epub'), os.listdir(storage_path))[0]
|
|
||||||
file = os.path.join(storage_path,database_epub)
|
|
||||||
calibre_database_item = True
|
|
||||||
self.log.info(" using database file: %s" % file)
|
|
||||||
except:
|
|
||||||
self.log.info(" could not find epub in %s" % storage_path)
|
|
||||||
else:
|
|
||||||
self.log.info(" no cover available, using temp file")
|
|
||||||
# ---------------------------
|
|
||||||
'''
|
|
||||||
|
|
||||||
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
||||||
if path in self.cached_books:
|
if path in self.cached_books:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -778,6 +765,21 @@ class ITUNES(DevicePlugin):
|
|||||||
if DEBUG:
|
if DEBUG:
|
||||||
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])
|
||||||
|
if self.manual_sync_mode:
|
||||||
|
dev_book_added = self._remove_device_book(self.cached_books[path])
|
||||||
|
|
||||||
|
'''
|
||||||
|
Old code testing for PTO
|
||||||
|
Use this with manuals_sync_mode to decide whether to add to Library|Books
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" file: %s" % (file._name if isinstance(file,PersistentTemporaryFile) else file))
|
||||||
|
# Add to iTunes Library|Books
|
||||||
|
if isinstance(file,PersistentTemporaryFile):
|
||||||
|
added = self.iTunes.add(appscript.mactypes.File(file._name))
|
||||||
|
else:
|
||||||
|
added = self.iTunes.add(appscript.mactypes.File(file))
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
# Add to iTunes Library|Books
|
# Add to iTunes Library|Books
|
||||||
fpath = file
|
fpath = file
|
||||||
@ -785,7 +787,18 @@ class ITUNES(DevicePlugin):
|
|||||||
fpath = file.orig_file_path
|
fpath = file.orig_file_path
|
||||||
elif getattr(file, 'name', None) is not None:
|
elif getattr(file, 'name', None) is not None:
|
||||||
fpath = file.name
|
fpath = file.name
|
||||||
added = self.iTunes.add(appscript.mactypes.File(fpath))
|
|
||||||
|
if isinstance(file,PersistentTemporaryFile) and self.manual_sync_mode:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" PTF not added to Library|Books")
|
||||||
|
else:
|
||||||
|
added = self.iTunes.add(appscript.mactypes.File(fpath))
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" file added to Library|Books")
|
||||||
|
|
||||||
|
dev_book_added = None
|
||||||
|
if self.manual_sync_mode:
|
||||||
|
dev_book_added = self._add_device_book(fpath)
|
||||||
|
|
||||||
thumb = None
|
thumb = None
|
||||||
if metadata[i].cover:
|
if metadata[i].cover:
|
||||||
@ -853,7 +866,8 @@ class ITUNES(DevicePlugin):
|
|||||||
self.cached_books[this_book.path] = {
|
self.cached_books[this_book.path] = {
|
||||||
'title': this_book.title,
|
'title': this_book.title,
|
||||||
'author': this_book.author,
|
'author': this_book.author,
|
||||||
'lib_book': added
|
'lib_book': added,
|
||||||
|
'dev_book': dev_book_added
|
||||||
}
|
}
|
||||||
|
|
||||||
# Report progress
|
# Report progress
|
||||||
@ -864,62 +878,12 @@ class ITUNES(DevicePlugin):
|
|||||||
try:
|
try:
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||||
|
lib = self.iTunes.LibraryPlaylist
|
||||||
for source in self.iTunes.sources:
|
|
||||||
if source.Kind == self.Sources.index('Library'):
|
|
||||||
lib = source
|
|
||||||
if DEBUG:
|
|
||||||
self.log.info(" Library source: '%s' kind: %s" % (lib.Name, self.Sources[lib.Kind]))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if DEBUG:
|
|
||||||
self.log.info(" Library source not found")
|
|
||||||
|
|
||||||
if lib is not None:
|
|
||||||
lib_books = None
|
|
||||||
for pl in lib.Playlists:
|
|
||||||
if pl.Kind == self.PlaylistKind.index('User') and \
|
|
||||||
pl.SpecialKind == self.PlaylistSpecialKind.index('Books'):
|
|
||||||
if DEBUG:
|
|
||||||
self.log.info(" Books playlist: '%s'" % (pl.Name))
|
|
||||||
lib_books = pl
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if DEBUG:
|
|
||||||
self.log.error(" no Books playlist found")
|
|
||||||
|
|
||||||
for (i,file) in enumerate(files):
|
for (i,file) in enumerate(files):
|
||||||
# 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
|
||||||
|
|
||||||
'''
|
|
||||||
# ---------------------------
|
|
||||||
# PROVISIONAL
|
|
||||||
# Use the cover to find the database storage point of the epub
|
|
||||||
# Pass database copy to iTunes instead of the temporary file
|
|
||||||
|
|
||||||
if False:
|
|
||||||
if DEBUG:
|
|
||||||
self.log.info(" processing '%s'" % metadata[i].title)
|
|
||||||
self.log.info(" file: %s" % (file._name if isinstance(file,PersistentTemporaryFile) else file))
|
|
||||||
self.log.info(" cover: %s" % metadata[i].cover)
|
|
||||||
|
|
||||||
calibre_database_item = False
|
|
||||||
if metadata[i].cover:
|
|
||||||
passed_file = file
|
|
||||||
storage_path = os.path.split(metadata[i].cover)[0]
|
|
||||||
try:
|
|
||||||
database_epub = filter(lambda x: x.endswith('.epub'), os.listdir(storage_path))[0]
|
|
||||||
file = os.path.join(storage_path,database_epub)
|
|
||||||
calibre_database_item = True
|
|
||||||
self.log.info(" using database file: %s" % file)
|
|
||||||
except:
|
|
||||||
self.log.info(" could not find epub in %s" % storage_path)
|
|
||||||
else:
|
|
||||||
self.log.info(" no cover available, using temp file")
|
|
||||||
# ---------------------------
|
|
||||||
'''
|
|
||||||
|
|
||||||
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
||||||
if path in self.cached_books:
|
if path in self.cached_books:
|
||||||
self.update_list.append(self.cached_books[path])
|
self.update_list.append(self.cached_books[path])
|
||||||
@ -928,6 +892,9 @@ class ITUNES(DevicePlugin):
|
|||||||
self.log.info("ITUNES.upload_books():")
|
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])
|
||||||
|
if self.manual_sync_mode:
|
||||||
|
dev_book_added = self._remove_device_book(self.cached_books[path])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" '%s' not in cached_books" % metadata[i].title)
|
self.log.info(" '%s' not in cached_books" % metadata[i].title)
|
||||||
@ -939,41 +906,58 @@ class ITUNES(DevicePlugin):
|
|||||||
elif getattr(file, 'name', None) is not None:
|
elif getattr(file, 'name', None) is not None:
|
||||||
fpath = file.name
|
fpath = file.name
|
||||||
|
|
||||||
op_status = lib_books.AddFile(fpath)
|
# If this file is to be deleted after xfer to device, don't add it to the
|
||||||
self.log.info("ITUNES.upload_books():\n iTunes adding '%s'"
|
# iTunes database, as the file path will be invalid when calibre exits.
|
||||||
% fpath)
|
# Only possible in manual_sync_mode
|
||||||
|
if getattr(file, 'deleted_after_upload', False) and self.manual_sync_mode:
|
||||||
if DEBUG:
|
|
||||||
sys.stdout.write(" iTunes copying '%s' ..." % metadata[i].title)
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
while op_status.InProgress:
|
|
||||||
time.sleep(0.5)
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
sys.stdout.write('.')
|
self.log.info(" PTF not added to Library|Books")
|
||||||
sys.stdout.flush()
|
else:
|
||||||
if DEBUG:
|
# Add fpath to Library|Books
|
||||||
sys.stdout.write("\n")
|
file_s = ctypes.c_char_p(fpath)
|
||||||
sys.stdout.flush()
|
FileArray = ctypes.c_char_p * 1
|
||||||
|
fa = FileArray(file_s)
|
||||||
if False:
|
op_status = lib.AddFiles(fa)
|
||||||
# According to the Apple API, .Tracks should be populated once the xfer
|
|
||||||
# is complete, but I can't seem to make that work.
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
sys.stdout.write(" waiting for handle to '%s' ..." % metadata[i].title)
|
self.log.info(" file added to Library|Books")
|
||||||
|
|
||||||
|
self.log.info("ITUNES.upload_books():\n iTunes adding '%s'"
|
||||||
|
% fpath)
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
sys.stdout.write(" iTunes copying '%s' ..." % metadata[i].title)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
while op_status.Tracks is None:
|
|
||||||
|
while op_status.InProgress:
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
sys.stdout.write('.')
|
sys.stdout.write('.')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print
|
sys.stdout.write("\n")
|
||||||
added = op_status.Tracks.Item[1]
|
sys.stdout.flush()
|
||||||
else:
|
|
||||||
# This approach simply scans Library|Books for the book we just added
|
if True:
|
||||||
added = self._find_library_book(
|
if DEBUG:
|
||||||
{'title': metadata[i].title,'author': metadata[i].author[0]})
|
sys.stdout.write(" waiting for handle to added '%s' ..." % metadata[i].title)
|
||||||
|
sys.stdout.flush()
|
||||||
|
while op_status.Tracks is None:
|
||||||
|
time.sleep(0.5)
|
||||||
|
if DEBUG:
|
||||||
|
sys.stdout.write('.')
|
||||||
|
sys.stdout.flush()
|
||||||
|
if DEBUG:
|
||||||
|
print
|
||||||
|
added = op_status.Tracks[0]
|
||||||
|
else:
|
||||||
|
# This approach simply scans Library|Books for the book we just added
|
||||||
|
added = self._find_library_book(
|
||||||
|
{'title': metadata[i].title,
|
||||||
|
'author': metadata[i].author[0]})
|
||||||
|
|
||||||
|
dev_book_added = None
|
||||||
|
if self.manual_sync_mode:
|
||||||
|
dev_book_added = self._add_device_book(fpath)
|
||||||
|
|
||||||
if added:
|
if added:
|
||||||
thumb = None
|
thumb = None
|
||||||
@ -1044,7 +1028,8 @@ class ITUNES(DevicePlugin):
|
|||||||
self.cached_books[this_book.path] = {
|
self.cached_books[this_book.path] = {
|
||||||
'title': metadata[i].title,
|
'title': metadata[i].title,
|
||||||
'author': metadata[i].author[0],
|
'author': metadata[i].author[0],
|
||||||
'lib_book': added
|
'lib_book': added,
|
||||||
|
'dev_book': dev_book_added
|
||||||
}
|
}
|
||||||
|
|
||||||
# Report progress
|
# Report progress
|
||||||
@ -1059,12 +1044,115 @@ class ITUNES(DevicePlugin):
|
|||||||
self.report_progress(1.0, _('finished'))
|
self.report_progress(1.0, _('finished'))
|
||||||
|
|
||||||
# Tell sync_booklists we need a re-sync
|
# Tell sync_booklists we need a re-sync
|
||||||
self.update_needed = True
|
if not self.manual_sync_mode:
|
||||||
self.update_msg = "Added books to device"
|
self.update_needed = True
|
||||||
|
self.update_msg = "Added books to device"
|
||||||
|
|
||||||
return (new_booklist, [], [])
|
return (new_booklist, [], [])
|
||||||
|
|
||||||
# Private methods
|
# Private methods
|
||||||
|
def _add_device_book(self,fpath):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
self.log.info("ITUNES._add_device_book()")
|
||||||
|
if isosx:
|
||||||
|
if 'iPod' in self.sources:
|
||||||
|
connected_device = self.sources['iPod']
|
||||||
|
device = self.iTunes.sources[connected_device]
|
||||||
|
for pl in device.playlists():
|
||||||
|
if pl.special_kind() == appscript.k.Books:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.error(" Device|Books playlist not found")
|
||||||
|
|
||||||
|
# Add the passed book to the Device|Books playlist
|
||||||
|
added = pl.add(appscript.mactypes.File(fpath),to=pl)
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" adding '%s' to device" % fpath)
|
||||||
|
return added
|
||||||
|
|
||||||
|
elif iswindows:
|
||||||
|
if 'iPod' in self.sources:
|
||||||
|
try:
|
||||||
|
pythoncom.CoInitialize()
|
||||||
|
connected_device = self.sources['iPod']
|
||||||
|
device = self.iTunes.sources.ItemByName(connected_device)
|
||||||
|
|
||||||
|
dev_books = None
|
||||||
|
added = None
|
||||||
|
for pl in device.Playlists:
|
||||||
|
if pl.Kind == self.PlaylistKind.index('User') and \
|
||||||
|
pl.SpecialKind == self.PlaylistSpecialKind.index('Books'):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" no Books playlist found")
|
||||||
|
|
||||||
|
# Add the passed book to the Device|Books playlist
|
||||||
|
if pl:
|
||||||
|
added = pl.AddFile(fpath)
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" adding '%s' to device" % fpath)
|
||||||
|
finally:
|
||||||
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
|
return added
|
||||||
|
|
||||||
|
def _discover_manual_sync_mode(self, wait=0):
|
||||||
|
'''
|
||||||
|
Assumes pythoncom for windows
|
||||||
|
wait is passed when launching iTunes, as it seems to need a moment to come to its senses
|
||||||
|
|
||||||
|
'''
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info("ITUNES._discover_manual_sync_mode()")
|
||||||
|
if isosx:
|
||||||
|
connected_device = self.sources['iPod']
|
||||||
|
dev_books = None
|
||||||
|
device = self.iTunes.sources[connected_device]
|
||||||
|
for pl in device.playlists():
|
||||||
|
if pl.special_kind() == appscript.k.Books:
|
||||||
|
dev_books = pl.file_tracks()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.log.error(" book_playlist not found")
|
||||||
|
|
||||||
|
if len(dev_books):
|
||||||
|
first_book = dev_books[0]
|
||||||
|
#if DEBUG:
|
||||||
|
#self.log.info(" determing manual mode by modifying '%s' by %s" % (first_book.name(), first_book.artist()))
|
||||||
|
try:
|
||||||
|
first_book.bpm.set(0)
|
||||||
|
self.manual_sync_mode = True
|
||||||
|
except:
|
||||||
|
self.manual_sync_mode = False
|
||||||
|
self.log.info(" iTunes.manual_sync_mode: %s" % self.manual_sync_mode)
|
||||||
|
|
||||||
|
elif iswindows:
|
||||||
|
if wait:
|
||||||
|
time.sleep(wait)
|
||||||
|
connected_device = self.sources['iPod']
|
||||||
|
device = self.iTunes.sources.ItemByName(connected_device)
|
||||||
|
|
||||||
|
dev_books = None
|
||||||
|
for pl in device.Playlists:
|
||||||
|
if pl.Kind == self.PlaylistKind.index('User') and \
|
||||||
|
pl.SpecialKind == self.PlaylistSpecialKind.index('Books'):
|
||||||
|
dev_books = pl.Tracks
|
||||||
|
break
|
||||||
|
|
||||||
|
if dev_books.Count:
|
||||||
|
first_book = dev_books.Item(1)
|
||||||
|
#if DEBUG:
|
||||||
|
#self.log.info(" determing manual mode by modifying '%s' by %s" % (first_book.Name, first_book.Artist))
|
||||||
|
try:
|
||||||
|
first_book.BPM = 0
|
||||||
|
self.manual_sync_mode = True
|
||||||
|
except:
|
||||||
|
self.manual_sync_mode = False
|
||||||
|
self.log.info(" iTunes.manual_sync_mode: %s" % self.manual_sync_mode)
|
||||||
|
|
||||||
def _dump_booklist(self, booklist, header=None):
|
def _dump_booklist(self, booklist, header=None):
|
||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
@ -1090,10 +1178,11 @@ class ITUNES(DevicePlugin):
|
|||||||
self.log.info( "%s" % ('-' * len(msg)))
|
self.log.info( "%s" % ('-' * len(msg)))
|
||||||
if isosx:
|
if isosx:
|
||||||
for cb in self.cached_books.keys():
|
for cb in self.cached_books.keys():
|
||||||
self.log.info("%-40.40s %-30.30s %-10.10s" %
|
self.log.info("%-40.40s %-30.30s %-10.10s %-10.10s" %
|
||||||
(self.cached_books[cb]['title'],
|
(self.cached_books[cb]['title'],
|
||||||
self.cached_books[cb]['author'],
|
self.cached_books[cb]['author'],
|
||||||
str(self.cached_books[cb]['lib_book'])[-9:]))
|
str(self.cached_books[cb]['lib_book'])[-9:],
|
||||||
|
str(self.cached_books[cb]['dev_book'])[-9:]))
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
for cb in self.cached_books.keys():
|
for cb in self.cached_books.keys():
|
||||||
self.log.info("%-40.40s %-30.30s" %
|
self.log.info("%-40.40s %-30.30s" %
|
||||||
@ -1121,7 +1210,10 @@ class ITUNES(DevicePlugin):
|
|||||||
self.log.info(msg)
|
self.log.info(msg)
|
||||||
self.log.info( "%s" % ('-' * len(msg)))
|
self.log.info( "%s" % ('-' * len(msg)))
|
||||||
for file in files:
|
for file in files:
|
||||||
self.log.info(file)
|
if getattr(file, 'orig_file_path', None) is not None:
|
||||||
|
self.log.info(" %s" % file.orig_file_path)
|
||||||
|
elif getattr(file, 'name', None) is not None:
|
||||||
|
self.log.info(" %s" % file.name)
|
||||||
self.log.info()
|
self.log.info()
|
||||||
|
|
||||||
def _dump_update_list(self,header=None):
|
def _dump_update_list(self,header=None):
|
||||||
@ -1147,7 +1239,6 @@ class ITUNES(DevicePlugin):
|
|||||||
'''
|
'''
|
||||||
Windows-only method to get a handle to a library book in the current pythoncom session
|
Windows-only method to get a handle to a library book in the current pythoncom session
|
||||||
'''
|
'''
|
||||||
SearchField = ['All','Visible','Artists','Titles','Composers','SongNames']
|
|
||||||
if iswindows:
|
if iswindows:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info("ITUNES._find_library_book()")
|
self.log.info("ITUNES._find_library_book()")
|
||||||
@ -1178,8 +1269,8 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
attempts = 9
|
attempts = 9
|
||||||
while attempts:
|
while attempts:
|
||||||
# Find all books by this author, then match title
|
# Find book whose Artist field = cached_book['author']
|
||||||
hits = lib_books.Search(cached_book['author'],SearchField.index('Artists'))
|
hits = lib_books.Search(cached_book['author'],self.SearchField.index('Artists'))
|
||||||
if hits:
|
if hits:
|
||||||
for hit in hits:
|
for hit in hits:
|
||||||
self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist))
|
self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist))
|
||||||
@ -1346,6 +1437,30 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
return device_books
|
return device_books
|
||||||
|
|
||||||
|
def _get_device_playlist(self):
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
if iswindows:
|
||||||
|
if 'iPod' in self.sources:
|
||||||
|
pl = None
|
||||||
|
try:
|
||||||
|
pythoncom.CoInitialize()
|
||||||
|
connected_device = self.sources['iPod']
|
||||||
|
device = self.iTunes.sources.ItemByName(connected_device)
|
||||||
|
|
||||||
|
dev_books = None
|
||||||
|
for pl in device.Playlists:
|
||||||
|
if pl.Kind == self.PlaylistKind.index('User') and \
|
||||||
|
pl.SpecialKind == self.PlaylistSpecialKind.index('Books'):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.error(" no iPad|Books playlist found")
|
||||||
|
finally:
|
||||||
|
pythoncom.CoUninitialize()
|
||||||
|
return pl
|
||||||
|
|
||||||
def _get_library_books(self):
|
def _get_library_books(self):
|
||||||
'''
|
'''
|
||||||
Populate a dict of paths from iTunes Library|Books
|
Populate a dict of paths from iTunes Library|Books
|
||||||
@ -1427,17 +1542,20 @@ class ITUNES(DevicePlugin):
|
|||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.error(" no Library playlists found")
|
self.log.error(" no Library playlists found")
|
||||||
|
|
||||||
for book in lib_books:
|
try:
|
||||||
# This may need additional entries for international iTunes users
|
for book in lib_books:
|
||||||
if book.KindAsString in ['MPEG audio file']:
|
# This may need additional entries for international iTunes users
|
||||||
if DEBUG:
|
if book.KindAsString in ['MPEG audio file']:
|
||||||
self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString))
|
if DEBUG:
|
||||||
else:
|
self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString))
|
||||||
if DEBUG:
|
else:
|
||||||
self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString))
|
if DEBUG:
|
||||||
path = self.path_template % (book.Name, book.Artist)
|
self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString))
|
||||||
library_books[path] = book
|
path = self.path_template % (book.Name, book.Artist)
|
||||||
|
library_books[path] = book
|
||||||
|
except:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" no books in library")
|
||||||
finally:
|
finally:
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
@ -1553,6 +1671,30 @@ class ITUNES(DevicePlugin):
|
|||||||
self.version[0],self.version[1],self.version[2]))
|
self.version[0],self.version[1],self.version[2]))
|
||||||
self.log.info(" iTunes_media: %s" % self.iTunes_media)
|
self.log.info(" iTunes_media: %s" % self.iTunes_media)
|
||||||
|
|
||||||
|
def _remove_device_book(self, cached_book):
|
||||||
|
'''
|
||||||
|
Windows assumes pythoncom wrapper
|
||||||
|
'''
|
||||||
|
self.log.info("ITUNES._remove_device_book()")
|
||||||
|
if isosx:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" deleting %s" % cached_book['dev_book'])
|
||||||
|
result = cached_book['dev_book'].delete()
|
||||||
|
print "result: %s" % result
|
||||||
|
|
||||||
|
elif iswindows:
|
||||||
|
dev_pl = self._get_device_playlist()
|
||||||
|
hits = dev_pl.Search(cached_book['author'],self.SearchField.index('Artists'))
|
||||||
|
if hits:
|
||||||
|
for hit in hits:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist))
|
||||||
|
if hit.Name == cached_book['title']:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" deleting '%s' by %s" % (hit.Name, hit.Artist))
|
||||||
|
results = hit.Delete()
|
||||||
|
break
|
||||||
|
|
||||||
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user