GRiker's changes

This commit is contained in:
Charles Haley 2010-06-25 17:37:55 +01:00
commit b2e934a28a
4 changed files with 303 additions and 46 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -277,7 +277,6 @@ class ITUNES(DriverBase):
# Fetch a list of books from iPod device connected to iTunes
if 'iPod' in self.sources:
booklist = BookList(self.log)
cached_books = {}
@ -366,7 +365,7 @@ class ITUNES(DriverBase):
self._dump_cached_books('returning from books()',indent=2)
return booklist
else:
return []
return BookList(self.log)
def can_handle(self, device_info, debug=False):
'''
@ -377,7 +376,7 @@ class ITUNES(DriverBase):
Confirm that:
- iTunes is running
- there is an iPod-type device connected
- there is an iDevice connected
This gets called first when the device fingerprint is read, so it needs to
instantiate iTunes if necessary
This gets called ~1x/second while device fingerprint is sensed
@ -2049,8 +2048,13 @@ class ITUNES(DriverBase):
running_apps = appscript.app('System Events')
if not 'iTunes' in running_apps.processes.name():
if DEBUG:
self.log.info( "ITUNES:open(): Launching iTunes" )
self.log.info( "ITUNES:_launch_iTunes(): Launching iTunes" )
try:
self.iTunes = iTunes= appscript.app('iTunes', hide=True)
except:
self.iTunes = None
raise UserFeedback(' ITUNES._launch_iTunes(): unable to find installed iTunes', details=None, level=UserFeedback.WARN)
iTunes.run()
self.initial_status = 'launched'
else:
@ -2091,7 +2095,12 @@ class ITUNES(DriverBase):
os.path.normpath("//server/share") returns "\\\\server\\share"
'''
# Instantiate iTunes
try:
self.iTunes = win32com.client.Dispatch("iTunes.Application")
except:
self.iTunes = None
raise UserFeedback(' ITUNES._launch_iTunes(): unable to find installed iTunes', details=None, level=UserFeedback.WARN)
if not DEBUG:
self.iTunes.Windows[0].Minimized = True
self.initial_status = 'launched'
@ -2564,6 +2573,225 @@ class ITUNES(DriverBase):
db_added.Genre = tag
break
class ITUNES_ASYNC(ITUNES):
'''
This subclass allows the user to interact directly with iTunes via a menu option
'Connect to iTunes' in Send to device.
'''
name = 'iTunes interface'
gui_name = 'Apple iTunes'
icon = I('devices/itunes.png')
description = _('Communicate with iTunes.')
connected = False
def __init__(self,path):
if DEBUG:
self.log.info("ITUNES_ASYNC:__init__()")
if isosx and appscript is None:
self.connected = False
raise UserFeedback('OSX 10.5 or later required', details=None, level=UserFeedback.WARN)
return
else:
self.connected = True
if isosx:
self._launch_iTunes()
if iswindows:
try:
pythoncom.CoInitialize()
self._launch_iTunes()
except:
raise UserFeedback('unable to launch iTunes', details=None, level=UserFeedback.WARN)
finally:
pythoncom.CoUninitialize()
self.manual_sync_mode = False
def books(self, oncard=None, end_session=True):
"""
Return a list of ebooks on the device.
@param oncard: If 'carda' or 'cardb' return a list of ebooks on the
specific storage card, otherwise return list of ebooks
in main memory of device. If a card is specified and no
books are on the card return empty list.
@return: A BookList.
Implementation notes:
iTunes does not sync purchased books, they are only on the device. They are visible, but
they are not backed up to iTunes. Since calibre can't manage them, don't show them in the
list of device books.
"""
if not oncard:
if DEBUG:
self.log.info("ITUNES_ASYNC:books(oncard=%s)" % oncard)
# Fetch a list of books from iTunes
booklist = BookList(self.log)
cached_books = {}
if isosx:
library_books = self._get_library_books()
book_count = float(len(library_books))
for (i,book) in enumerate(library_books):
this_book = Book(library_books[book].name(), library_books[book].artist())
this_book.path = self.path_template % (library_books[book].name(),
library_books[book].artist())
try:
this_book.datetime = parse_date(str(library_books[book].date_added())).timetuple()
except:
pass
this_book.db_id = None
this_book.device_collections = []
#this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None
this_book.library_id = library_books[book]
this_book.size = library_books[book].size()
this_book.uuid = library_books[book].album()
# Hack to discover if we're running in GUI environment
if self.report_progress is not None:
this_book.thumbnail = self._generate_thumbnail(this_book.path, library_books[book])
else:
this_book.thumbnail = None
booklist.add_book(this_book, False)
cached_books[this_book.path] = {
'title':library_books[book].name(),
'author':[library_books[book].artist()],
'lib_book':library_books[book],
'dev_book':None,
'uuid': library_books[book].composer(),
#'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub'
}
if self.report_progress is not None:
self.report_progress(i+1/book_count, _('%d of %d') % (i+1, book_count))
elif iswindows:
try:
pythoncom.CoInitialize()
self.iTunes = win32com.client.Dispatch("iTunes.Application")
library_books = self._get_library_books()
book_count = float(len(library_books))
for (i,book) in enumerate(library_books):
this_book = Book(library_books[book].Name, library_books[book].Artist)
this_book.path = self.path_template % (library_books[book].Name,
library_books[book].Artist)
try:
this_book.datetime = parse_date(str(library_books[book].DateAdded)).timetuple()
except:
pass
this_book.db_id = None
this_book.device_collections = []
this_book.library_id = library_books[book]
this_book.size = library_books[book].Size
# Hack to discover if we're running in GUI environment
if self.report_progress is not None:
this_book.thumbnail = self._generate_thumbnail(this_book.path, library_books[book])
else:
this_book.thumbnail = None
booklist.add_book(this_book, False)
cached_books[this_book.path] = {
'title':library_books[book].Name,
'author':library_books[book].Artist,
'lib_book':library_books[book],
'uuid': library_books[book].Composer,
'format': 'pdf' if library_books[book].KindAsString.startswith('PDF') else 'epub'
}
if self.report_progress is not None:
self.report_progress(i+1/book_count,
_('%d of %d') % (i+1, book_count))
finally:
pythoncom.CoUninitialize()
if self.report_progress is not None:
self.report_progress(1.0, _('finished'))
self.cached_books = cached_books
if DEBUG:
self._dump_booklist(booklist, 'returning from books()', indent=2)
self._dump_cached_books('returning from books()',indent=2)
return booklist
else:
return BookList(self.log)
def disconnect_from_folder(self):
'''
'''
if DEBUG:
self.log.info("ITUNES_ASYNC:disconnect_from_folder()")
self.connected = False
def eject(self):
'''
Un-mount / eject the device from the OS. This does not check if there
are pending GUI jobs that need to communicate with the device.
'''
if DEBUG:
self.log.info("ITUNES_ASYNC:eject()")
self.iTunes = None
self.connected = False
def free_space(self, end_session=True):
"""
Get free space available on the mountpoints:
1. Main memory
2. Card A
3. Card B
@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.
"""
if DEBUG:
self.log.info("ITUNES_ASYNC:free_space()")
free_space = 0
if isosx:
s = os.statvfs(os.sep)
free_space = s.f_bavail * s.f_frsize
elif iswindows:
free_bytes = ctypes.c_ulonglong(0)
ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(os.sep), None, None, ctypes.pointer(free_bytes))
free_space = free_bytes.value
return (free_space,-1,-1)
def get_device_information(self, end_session=True):
"""
Ask device for device information. See L{DeviceInfoQuery}.
@return: (device name, device version, software version on device, mime type)
"""
if DEBUG:
self.log.info("ITUNES_ASYNC:get_device_information()")
return ('iTunes','hw v1.0','sw v1.0', 'mime type normally goes here')
def is_usb_connected(self, devices_on_system, debug=False,
only_presence=False):
return self.connected, self
def sync_booklists(self, booklists, end_session=True):
'''
Update metadata on device.
@param booklists: A tuple containing the result of calls to
(L{books}(oncard=None), L{books}(oncard='carda'),
L{books}(oncard='cardb')).
'''
if DEBUG:
self.log.info("ITUNES_ASYNC.sync_booklists()")
# Inform user of any problem books
if self.problem_titles:
raise UserFeedback(self.problem_msg,
details='\n'.join(self.problem_titles), level=UserFeedback.WARN)
self.problem_titles = []
self.problem_msg = None
self.update_list = []
class BookList(list):
'''

View File

@ -29,6 +29,7 @@ from calibre.utils.filenames import ascii_filename
from calibre.devices.errors import FreeSpaceError
from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \
config as email_config
from calibre.devices.apple.driver import ITUNES_ASYNC
from calibre.devices.folder_device.driver import FOLDER_DEVICE
# }}}
@ -105,10 +106,11 @@ class DeviceManager(Thread): # {{{
self.scanner = DeviceScanner()
self.connected_device = None
self.ejected_devices = set([])
self.connected_device_is_folder = False
self.folder_connection_requests = Queue.Queue(0)
self.mount_connection_requests = Queue.Queue(0)
self.open_feedback_slot = open_feedback_slot
ITUNES_STRING = '#itunes#'
def report_progress(self, *args):
pass
@ -120,7 +122,7 @@ class DeviceManager(Thread): # {{{
def device(self):
return self.connected_device
def do_connect(self, connected_devices, is_folder_device):
def do_connect(self, connected_devices, device_kind):
for dev, detected_device in connected_devices:
if dev.OPEN_FEEDBACK_MESSAGE is not None:
self.open_feedback_slot(dev.OPEN_FEEDBACK_MESSAGE)
@ -133,8 +135,8 @@ class DeviceManager(Thread): # {{{
traceback.print_exc()
continue
self.connected_device = dev
self.connected_device_is_folder = is_folder_device
self.connected_slot(True, is_folder_device)
self.connected_device_kind = device_kind
self.connected_slot(True, device_kind)
return True
return False
@ -152,7 +154,7 @@ class DeviceManager(Thread): # {{{
if self.connected_device in self.ejected_devices:
self.ejected_devices.remove(self.connected_device)
else:
self.connected_slot(False, self.connected_device_is_folder)
self.connected_slot(False, self.connected_device_kind)
self.connected_device = None
def detect_device(self):
@ -174,18 +176,18 @@ class DeviceManager(Thread): # {{{
possibly_connected_devices.append((device, detected_device))
if possibly_connected_devices:
if not self.do_connect(possibly_connected_devices,
is_folder_device=False):
device_kind='device'):
prints('Connect to device failed, retrying in 5 seconds...')
time.sleep(5)
if not self.do_connect(possibly_connected_devices,
is_folder_device=False):
device_kind='usb'):
prints('Device connect failed again, giving up')
def umount_device(self, *args):
if self.is_device_connected and not self.job_manager.has_device_jobs():
self.connected_device.eject()
self.ejected_devices.add(self.connected_device)
self.connected_slot(False, self.connected_device_is_folder)
self.connected_slot(False, self.connected_device_kind)
def next(self):
if not self.jobs.empty():
@ -196,20 +198,19 @@ class DeviceManager(Thread): # {{{
def run(self):
while self.keep_going:
folder_path = None
kls = None
while True:
try:
folder_path = self.folder_connection_requests.get_nowait()
(kls,device_kind, folder_path) = \
self.mount_connection_requests.get_nowait()
except Queue.Empty:
break
if not folder_path or not os.access(folder_path, os.R_OK):
folder_path = None
if not self.is_device_connected and folder_path is not None:
if kls is not None:
try:
dev = FOLDER_DEVICE(folder_path)
self.do_connect([[dev, None],], is_folder_device=True)
dev = kls(folder_path)
self.do_connect([[dev, None],], device_kind=device_kind)
except:
prints('Unable to open folder as device', folder_path)
prints('Unable to open %s as device (%s)'%(device_kind, folder_path))
traceback.print_exc()
else:
self.detect_device()
@ -251,13 +252,14 @@ class DeviceManager(Thread): # {{{
# This will be called on the GUI thread. Because of this, we must store
# information that the scanner thread will use to do the real work.
def connect_to_folder(self, path):
self.folder_connection_requests.put(path)
# Note: this is used for iTunes
def mount_device(self, kls, kind, path):
self.mount_connection_requests.put((kls, kind, path))
# This is called on the GUI thread. No problem here, because it calls the
# device driver, telling it to tell the scanner when it passes by that the
# folder has disconnected.
def disconnect_folder(self):
# folder has disconnected. Note: this is also used for iTunes
def unmount_device(self):
if self.connected_device is not None:
if hasattr(self.connected_device, 'disconnect_from_folder'):
# As we are on the wrong thread, this call must *not* do
@ -375,7 +377,8 @@ class DeviceMenu(QMenu): # {{{
fetch_annotations = pyqtSignal()
connect_to_folder = pyqtSignal()
disconnect_from_folder = pyqtSignal()
connect_to_itunes = pyqtSignal()
disconnect_mounted_device = pyqtSignal()
def __init__(self, parent=None):
QMenu.__init__(self, parent)
@ -492,8 +495,18 @@ class DeviceMenu(QMenu): # {{{
mitem = self.addAction(QIcon(I('eject.svg')), _('Disconnect from folder'))
mitem.setEnabled(False)
mitem.triggered.connect(lambda x : self.disconnect_from_folder.emit())
self.disconnect_from_folder_action = mitem
mitem.triggered.connect(lambda x : self.disconnect_mounted_device.emit())
self.disconnect_mounted_device_action = mitem
mitem = self.addAction(QIcon(I('document_open.svg')), _('Connect to iTunes (BETA TEST)'))
mitem.setEnabled(True)
mitem.triggered.connect(lambda x : self.connect_to_itunes.emit())
self.connect_to_itunes_action = mitem
mitem = self.addAction(QIcon(I('eject.svg')), _('Disconnect from iTunes (BETA TEST)'))
mitem.setEnabled(False)
mitem.triggered.connect(lambda x : self.disconnect_mounted_device.emit())
self.disconnect_from_itunes_action = mitem
self.addSeparator()
self.addMenu(self.set_default_menu)
@ -630,11 +643,16 @@ class DeviceMixin(object): # {{{
def connect_to_folder(self):
dir = choose_dir(self, 'Select Device Folder',
_('Select folder to open as device'))
if dir is not None:
self.device_manager.connect_to_folder(dir)
kls = FOLDER_DEVICE
self.device_manager.mount_device(kls=kls, kind='folder', path=dir)
def disconnect_from_folder(self):
self.device_manager.disconnect_folder()
def connect_to_itunes(self):
kls = ITUNES_ASYNC
self.device_manager.mount_device(kls=kls, kind='itunes', path=None)
# disconnect from both folder and itunes devices
def disconnect_mounted_device(self):
self.device_manager.unmount_device()
def _sync_action_triggered(self, *args):
m = getattr(self, '_sync_menu', None)
@ -649,16 +667,22 @@ class DeviceMixin(object): # {{{
self.dispatch_sync_event)
self._sync_menu.fetch_annotations.connect(self.fetch_annotations)
self._sync_menu.connect_to_folder.connect(self.connect_to_folder)
self._sync_menu.disconnect_from_folder.connect(self.disconnect_from_folder)
self._sync_menu.connect_to_itunes.connect(self.connect_to_itunes)
self._sync_menu.disconnect_mounted_device.connect(self.disconnect_mounted_device)
if self.device_connected:
self._sync_menu.connect_to_folder_action.setEnabled(False)
self._sync_menu.connect_to_itunes_action.setEnabled(False)
if self.device_connected == 'folder':
self._sync_menu.disconnect_from_folder_action.setEnabled(True)
self._sync_menu.disconnect_mounted_device_action.setEnabled(True)
if self.device_connected == 'itunes':
self._sync_menu.disconnect_from_itunes_action.setEnabled(True)
else:
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
self._sync_menu.disconnect_mounted_device_action.setEnabled(False)
else:
self._sync_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
self._sync_menu.disconnect_mounted_device_action.setEnabled(False)
self._sync_menu.connect_to_itunes_action.setEnabled(True)
self._sync_menu.disconnect_from_itunes_action.setEnabled(False)
@ -694,26 +718,31 @@ class DeviceMixin(object): # {{{
# Device connected {{{
def set_device_menu_items_state(self, connected, is_folder_device):
def set_device_menu_items_state(self, connected, device_kind):
if connected:
self._sync_menu.connect_to_folder_action.setEnabled(False)
if is_folder_device:
self._sync_menu.disconnect_from_folder_action.setEnabled(True)
self._sync_menu.connect_to_itunes_action.setEnabled(False)
if device_kind == 'folder':
self._sync_menu.disconnect_mounted_device_action.setEnabled(True)
elif device_kind == 'itunes':
self._sync_menu.disconnect_from_itunes_action.setEnabled(True)
self._sync_menu.enable_device_actions(True,
self.device_manager.device.card_prefix(),
self.device_manager.device)
self.eject_action.setEnabled(True)
else:
self._sync_menu.connect_to_folder_action.setEnabled(True)
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
self._sync_menu.connect_to_itunes_action.setEnabled(True)
self._sync_menu.disconnect_mounted_device_action.setEnabled(False)
self._sync_menu.disconnect_from_itunes_action.setEnabled(False)
self._sync_menu.enable_device_actions(False)
self.eject_action.setEnabled(False)
def device_detected(self, connected, is_folder_device):
def device_detected(self, connected, device_kind):
'''
Called when a device is connected to the computer.
'''
self.set_device_menu_items_state(connected, is_folder_device)
self.set_device_menu_items_state(connected, device_kind)
if connected:
self.device_manager.get_device_information(\
Dispatcher(self.info_read))
@ -722,7 +751,7 @@ class DeviceMixin(object): # {{{
self.status_bar.show_message(_('Device: ')+\
self.device_manager.device.__class__.get_gui_name()+\
_(' detected.'), 3000)
self.device_connected = 'device' if not is_folder_device else 'folder'
self.device_connected = device_kind
self.location_view.model().device_connected(self.device_manager.device)
self.refresh_ondevice_info (device_connected = True, reset_only = True)
else: