diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index d1f5ea050c..e2e4b549c8 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -430,7 +430,7 @@ from calibre.ebooks.txt.output import TXTOutput from calibre.customize.profiles import input_profiles, output_profiles - +from calibre.devices.apple.driver import ITUNES from calibre.devices.hanlin.driver import HANLINV3, HANLINV5, BOOX from calibre.devices.blackberry.driver import BLACKBERRY from calibre.devices.cybook.driver import CYBOOK @@ -495,6 +495,7 @@ plugins += [ ] # Order here matters. The first matched device is the one used. plugins += [ + ITUNES, HANLINV3, HANLINV5, BLACKBERRY, diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index cebc20f732..154412d220 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -5,22 +5,83 @@ 22 May 2010 ''' +import datetime +from calibre.constants import isosx, iswindows from calibre.devices.interface import DevicePlugin +#from calibre.ebooks.metadata import MetaInformation +from calibre.utils.config import Config -class iDevice(DevicePlugin): +if isosx: + print "running in OSX" + import appscript + +if iswindows: + print "running in Windows" + import win32com.client + +class ITUNES(DevicePlugin): name = 'Apple device interface' gui_name = 'Apple device' + icon = I('devices/iPad.png') + description = _('Communicate with iBooks through iTunes.') supported_platforms = ['windows','osx'] author = 'GRiker' FORMATS = ['epub'] - VENDOR_ID = [0x0830] - PRODUCT_ID = [0x8004, 0x8002, 0x0101] - BCD = [0x0316] + VENDOR_ID = [0x05ac] + # 0x129a:iPad 0x1292:iPhone 3G + PRODUCT_ID = [0x129a,0x1292] + BCD = [0x01] - def is_usb_connected(self, device_on_system): + app = None + is_connected = False + + + # Public methods + + def add_books_to_metadata(cls, locations, metadata, booklists): + ''' + Add locations to the booklists. This function must not communicate with + the device. + @param locations: Result of a call to L{upload_books} + @param metadata: List of MetaInformation objects, same as for + :method:`upload_books`. + @param booklists: A tuple containing the result of calls to + (L{books}(oncard=None), L{books}(oncard='carda'), + L{books}(oncard='cardb')). + ''' + raise NotImplementedError + + 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. + """ + print "ITUNES:books(oncard=%s)" % oncard + if not oncard: + myBooks = BookList() + book = Book() + + myBooks.add_book(book, False) + print "len(myBooks): %d" % len(myBooks) + return myBooks + else: + return [] + + def can_handle(self, device_info, debug=False): + ''' + Unix version of :method:`can_handle_windows` + + :param device_info: Is a tupe of (vid, pid, bcd, manufacturer, product, + serial number) + ''' + print "ITUNES:can_handle()" return True def can_handle_windows(self, device_id, debug=False): @@ -35,17 +96,68 @@ class iDevice(DevicePlugin): :param device_info: On windows a device ID string. On Unix a tuple of ``(vendor_id, product_id, bcd)``. ''' + print "ITUNES:can_handle_windows()" return True - def can_handle(self, device_info, debug=False): + def card_prefix(self, end_session=True): ''' - Unix version of :method:`can_handle_windows` - - :param device_info: Is a tupe of (vid, pid, bcd, manufacturer, product, - serial number) + Return a 2 element list of the prefix to paths on the cards. + If no card is present None is set for the card's prefix. + E.G. + ('/place', '/place2') + (None, 'place2') + ('place', None) + (None, None) ''' + print "ITUNES:card_prefix()" + return (None,None) - return True + def config_widget(cls): + ''' + Should return a QWidget. The QWidget contains the settings for the device interface + ''' + raise NotImplementedError() + + def delete_books(self, paths, end_session=True): + ''' + Delete books at paths on device. + ''' + raise NotImplementedError() + + 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. + ''' + print "ITUNES:eject()" + + 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. + """ + print "ITUNES:free_space()" + return (0,-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) + """ + print "ITUNES:get_device_information()" + return ('iPad','hw v1.0','sw v1.0', 'mime type') + + def get_file(self, path, outfile, end_session=True): + ''' + Read the file at C{path} on the device and write it to outfile. + @param outfile: file object like C{sys.stdout} or the result of an C{open} call + ''' + raise NotImplementedError() def open(self): ''' @@ -58,14 +170,16 @@ class iDevice(DevicePlugin): this function that should serve as a good example for USB Mass storage devices. ''' - print "iDevice(): I am here!" - - 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. - ''' - raise NotImplementedError() + print "ITUNES.open()" + if isosx: + # Launch iTunes if not already running + running_apps = appscript.app('System Events') + if not 'iTunes' in running_apps.processes.name(): + print " launching iTunes" + app = appscript.app('iTunes', hide=True) + app.run() + self.app = app + # May need to set focus back to calibre here? def post_yank_cleanup(self): ''' @@ -73,6 +187,37 @@ class iDevice(DevicePlugin): ''' raise NotImplementedError() + def remove_books_from_metadata(cls, paths, booklists): + ''' + Remove books from the metadata list. This function must not communicate + with the device. + @param paths: paths to books on the device. + @param booklists: A tuple containing the result of calls to + (L{books}(oncard=None), L{books}(oncard='carda'), + L{books}(oncard='cardb')). + ''' + raise NotImplementedError() + + def reset(self, key='-1', log_packets=False, report_progress=None, + detected_device=None) : + """ + :key: The key to unlock the device + :log_packets: If true the packet stream to/from the device is logged + :report_progress: Function that is called with a % progress + (number between 0 and 100) for various tasks + If it is called with -1 that means that the + task does not have any progress information + :detected_device: Device information from the device scanner + """ + print "ITUNE.reset()" + + def save_settings(cls, settings_widget): + ''' + Should save settings to disk. Takes the widget created in config_widget + and saves all settings to disk. + ''' + raise NotImplementedError() + def set_progress_reporter(self, report_progress): ''' @param report_progress: Function that is called with a % progress @@ -80,26 +225,28 @@ class iDevice(DevicePlugin): If it is called with -1 that means that the task does not have any progress information ''' - raise NotImplementedError() + print "ITUNES:set_progress_reporter()" - 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) - """ - raise NotImplementedError() + def settings(cls): + ''' + Should return an opts object. The opts object should have one attribute + `format_map` which is an ordered list of formats for the device. + ''' + print "ITUNES.settings()" + klass = cls if isinstance(cls, type) else cls.__class__ + c = Config('device_drivers_%s' % klass.__name__, _('settings for device drivers')) + c.add_opt('format_map', default=cls.FORMATS, + help=_('Ordered list of formats the device will accept')) + return c.parse() - def card_prefix(self, end_session=True): + def sync_booklists(self, booklists, end_session=True): ''' - Return a 2 element list of the prefix to paths on the cards. - If no card is present None is set for the card's prefix. - E.G. - ('/place', '/place2') - (None, 'place2') - ('place', None) - (None, None) + 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')). ''' - raise NotImplementedError() + print "ITUNES:sync_booklists():" def total_space(self, end_session=True): """ @@ -111,30 +258,7 @@ class iDevice(DevicePlugin): @return: A 3 element list with total space in bytes of (1, 2, 3). If a particular device doesn't have any of these locations it should return 0. """ - raise NotImplementedError() - - 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. - """ - raise NotImplementedError() - - 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. - """ - raise NotImplementedError() + print "ITUNES:total_space()" def upload_books(self, files, names, on_card=None, end_session=True, metadata=None): @@ -158,79 +282,16 @@ class iDevice(DevicePlugin): ''' raise NotImplementedError() - @classmethod - def add_books_to_metadata(cls, locations, metadata, booklists): - ''' - Add locations to the booklists. This function must not communicate with - the device. - @param locations: Result of a call to L{upload_books} - @param metadata: List of MetaInformation objects, same as for - :method:`upload_books`. - @param booklists: A tuple containing the result of calls to - (L{books}(oncard=None), L{books}(oncard='carda'), - L{books}(oncard='cardb')). - ''' - raise NotImplementedError + # Private methods - def delete_books(self, paths, end_session=True): + def _get_source(self): ''' - Delete books at paths on device. + Get iTunes sources (Library, iPod, Radio ...) ''' - raise NotImplementedError() - - @classmethod - def remove_books_from_metadata(cls, paths, booklists): - ''' - Remove books from the metadata list. This function must not communicate - with the device. - @param paths: paths to books on the device. - @param booklists: A tuple containing the result of calls to - (L{books}(oncard=None), L{books}(oncard='carda'), - L{books}(oncard='cardb')). - ''' - raise NotImplementedError() - - 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')). - ''' - raise NotImplementedError() - - def get_file(self, path, outfile, end_session=True): - ''' - Read the file at C{path} on the device and write it to outfile. - @param outfile: file object like C{sys.stdout} or the result of an C{open} call - ''' - raise NotImplementedError() - - @classmethod - def config_widget(cls): - ''' - Should return a QWidget. The QWidget contains the settings for the device interface - ''' - raise NotImplementedError() - - @classmethod - def save_settings(cls, settings_widget): - ''' - Should save settings to disk. Takes the widget created in config_widget - and saves all settings to disk. - ''' - raise NotImplementedError() - - @classmethod - def settings(cls): - ''' - Should return an opts object. The opts object should have one attribute - `format_map` which is an ordered list of formats for the device. - ''' - raise NotImplementedError() - - - + sources = self._app.sources() + names = [s.name() for s in sources] + kinds = [s.kind() for s in sources] + return dict(zip(kinds,names)) class BookList(list): ''' @@ -249,19 +310,20 @@ class BookList(list): __getslice__ = None __setslice__ = None - def __init__(self, oncard, prefix, settings): + def __init__(self): pass def supports_collections(self): ''' Return True if the the device supports collections for this book list. ''' - raise NotImplementedError() + return False def add_book(self, book, replace_metadata): ''' Add the book to the booklist. Intent is to maintain any device-internal metadata. Return True if booklists must be sync'ed ''' - raise NotImplementedError() + print "adding %s" % book + self.append(book) def remove_book(self, book): ''' @@ -281,4 +343,22 @@ class BookList(list): :param collection_attributes: A list of attributes of the Book object ''' - raise NotImplementedError() \ No newline at end of file + return {} + +class Book(object): + ''' + A simple class describing a book in the iTunes Books Library. + These seem to be the minimum Book attributes needed. + ''' + def __init__(self): + setattr(self,'title','A Book Title') + setattr(self,'authors',['John Doe']) + setattr(self,'path','some/path.epub') + setattr(self,'size',1234567) + setattr(self,'datetime',datetime.datetime.now().timetuple()) + setattr(self,'thumbnail',None) + setattr(self,'db_id',0) + setattr(self,'device_collections',[]) + setattr(self,'tags',['Genre']) + +