diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 81810455ad..993a1180a6 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' import cStringIO, ctypes, datetime, os, re, shutil, sys, tempfile, time -from calibre import fit_image, confirm_config_name, strftime as _strftime +from calibre import fit_image, confirm_config_name, osx_version, strftime as _strftime from calibre.constants import ( __appname__, __version__, isosx, iswindows, cache_dir as _cache_dir) from calibre.devices.errors import OpenFeedback, UserFeedback @@ -191,6 +191,12 @@ class ITUNES(DriverBase): sync_booklists() card_prefix() free_space() + + self.manual_sync_mode is True when we're talking directly to iBooks through iTunes. + Determined in _discover_manual_sync_mode() + Special handling in: + _add_new_copy() + ''' name = 'Apple iTunes interface' @@ -208,6 +214,7 @@ class ITUNES(DriverBase): USE_SERIES_AS_CATEGORY = 0 CACHE_COVERS = 1 USE_ITUNES_STORAGE = 2 + DEBUG_LOGGING = 3 OPEN_FEEDBACK_MESSAGE = _( 'Apple iDevice detected, launching iTunes, please wait ...') @@ -310,7 +317,7 @@ class ITUNES(DriverBase): verbose = False def __init__(self, path): - self.verbose = self.settings().extra_customization[3] + self.verbose = self.settings().extra_customization[self.DEBUG_LOGGING] if self.verbose: logger().info("%s.__init__():" % self.__class__.__name__) @@ -341,13 +348,13 @@ class ITUNES(DriverBase): # Delete any obsolete copies of the book from the booklist if self.update_list: - if False: + if False and self.verbose: self._dump_booklist(booklists[0], header='before', indent=2) self._dump_update_list(header='before', indent=2) self._dump_cached_books(header='before', indent=2) for (j, p_book) in enumerate(self.update_list): - if False: + if False and self.verbose: if isosx: logger().info(" looking for '%s' by %s uuid:%s" % (p_book['title'], p_book['author'], p_book['uuid'])) @@ -656,7 +663,7 @@ class ITUNES(DriverBase): attempts -= 1 time.sleep(1.0) if self.verbose: - logger().warning(" waiting for connected iDevice, attempt #%d" % (10 - attempts)) + logger().info(" waiting for connected iDevice, attempt #%d" % (10 - attempts)) else: if self.verbose: logger().info(' found connected iPad in iTunes') @@ -746,9 +753,13 @@ class ITUNES(DriverBase): if not metadata.uuid: metadata.uuid = "unknown" + if self.verbose: + logger().info(" Deleting '%s' from iBooks" % (path)) + if isosx: self._remove_existing_copy(self.cached_books[path], metadata) elif iswindows: + import pythoncom, win32com.client try: pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") @@ -940,59 +951,76 @@ class ITUNES(DriverBase): ''' if self.verbose: logger().info("%s.remove_books_from_metadata()" % self.__class__.__name__) + for path in paths: if self.verbose: self._dump_cached_book(self.cached_books[path], indent=2) - logger().info(" looking for '%s' by '%s' uuid:%s" % + if False and self.verbose: + logger().info(" looking for '%s' by '%s' uuid:%s" % (self.cached_books[path]['title'], self.cached_books[path]['author'], repr(self.cached_books[path]['uuid']))) # Purge the booklist, self.cached_books, thumb cache for i, bl_book in enumerate(booklists[0]): - if False: + if False and self.verbose: logger().info(" evaluating '%s' by '%s' uuid:%s" % (bl_book.title, bl_book.author, bl_book.uuid)) found = False if bl_book.uuid and bl_book.uuid == self.cached_books[path]['uuid']: - if True: + if True and self.verbose: logger().info(" --matched uuid") - booklists[0].pop(i) found = True elif bl_book.title == self.cached_books[path]['title'] and \ bl_book.author == self.cached_books[path]['author']: - if True: + if True and self.verbose: logger().info(" --matched title + author") - booklists[0].pop(i) found = True if found: + # Remove from booklist[0] + popped = booklists[0].pop(i) + if False and self.verbose: + logger().info(" '%s' removed from booklists[0]" % popped.title) + # Remove from self.cached_books + if False and self.verbose: + logger().info("path: %s" % path) for cb in self.cached_books: + if False and self.verbose: + logger().info(" evaluating '%s' by '%s' uuid:%s" % + (self.cached_books[cb]['title'], + self.cached_books[cb]['author'], + self.cached_books[cb]['uuid'])) if (self.cached_books[cb]['uuid'] == self.cached_books[path]['uuid'] and self.cached_books[cb]['author'] == self.cached_books[path]['author'] and self.cached_books[cb]['title'] == self.cached_books[path]['title']): - self.cached_books.pop(cb) + popped = self.cached_books.pop(cb) + if False and self.verbose: + logger().info(" '%s' removed from self.cached_books" % popped['title']) break else: - logger().error(" '%s' not found in self.cached_books" % self.cached_books[path]['title']) + if self.verbose: + logger().info(" '%s' not found in self.cached_books" % self.cached_books[path]['title']) # Remove from thumb from thumb cache + from calibre.utils.zipfile import ZipFile thumb_path = path.rpartition('.')[0] + '.jpg' zf = ZipFile(self.archive_path, 'a') + fnames = zf.namelist() try: thumb = [x for x in fnames if thumb_path in x][0] except: thumb = None + if thumb: if self.verbose: logger().info(" deleting '%s' from cover cache" % (thumb_path)) - zf.delete(thumb_path) - else: - if self.verbose: - logger().info(" '%s' not found in cover cache" % thumb_path) + zf.delete(thumb_path) + elif self.verbose: + logger().info(" '%s' not found in cover cache" % thumb_path) zf.close() break @@ -1003,7 +1031,7 @@ class ITUNES(DriverBase): self.cached_books[path]['author'], self.cached_books[path]['uuid'])) - if False: + if False and self.verbose: self._dump_booklist(booklists[0], indent=2) self._dump_cached_books(indent=2) @@ -1045,7 +1073,7 @@ class ITUNES(DriverBase): self.plugboard_func = pb_func def shutdown(self): - if False and DEBUG: + if False and self.verbose: logger().info("%s.shutdown()\n" % self.__class__.__name__) def sync_booklists(self, booklists, end_session=True): @@ -1225,7 +1253,8 @@ class ITUNES(DriverBase): ''' assumes pythoncom wrapper for windows ''' - logger().info(" %s._add_device_book()" % self.__class__.__name__) + if self.verbose: + logger().info(" %s._add_device_book()" % self.__class__.__name__) if isosx: import appscript if 'iPod' in self.sources: @@ -1483,15 +1512,47 @@ class ITUNES(DriverBase): Could also be a problem with the integrity of the cover data? ''' if lb_added: - try: - lb_added.artworks[1].data_.set(cover_data) - except: + delay = 2.0 + self._wait_for_writable_metadata(db_added, delay=delay) + + # Wait for updatable artwork + attempts = 9 + while attempts: + try: + lb_added.artworks[1].data_.set(cover_data) + except: + attempts -= 1 + time.sleep(delay) + if self.verbose: +# logger().warning(" iTunes automation interface reported an error" +# " adding artwork to '%s' in the iTunes Library" % metadata.title) + logger().info(" waiting %.1f seconds for artwork to become writable (attempt #%d)" % + (delay, (10 - attempts))) + else: if self.verbose: - logger().warning(" iTunes automation interface reported an error" - " adding artwork to '%s' in the iTunes Library" % metadata.title) - pass + logger().info(" failed to write artwork") if db_added: + delay = 2.0 + self._wait_for_writable_metadata(db_added, delay=delay) + + # Wait for updatable artwork + attempts = 9 + while attempts: + try: + db_added.artworks[1].data_.set(cover_data) + break + except: + attempts -= 1 + time.sleep(delay) + if self.verbose: + logger().info(" waiting %.1f seconds for artwork to become writable (attempt #%d)" % + (delay, (10 - attempts))) + else: + if self.verbose: + logger().info(" failed to write artwork") + + """ try: db_added.artworks[1].data_.set(cover_data) logger().info(" writing '%s' cover to iDevice" % metadata.title) @@ -1504,6 +1565,7 @@ class ITUNES(DriverBase): #from calibre import ipython #ipython(user_ns=locals()) pass + """ elif iswindows: ''' Write the data to a real file for Windows iTunes ''' @@ -1524,10 +1586,28 @@ class ITUNES(DriverBase): pass if db_added: - if db_added.Artwork.Count: - db_added.Artwork.Item(1).SetArtworkFromFile(tc) + delay = 2.0 + self._wait_for_writable_metadata(db_added, delay=delay) + + # Wait for updatable artwork + attempts = 9 + while attempts: + try: + if db_added.Artwork.Count: + db_added.Artwork.Item(1).SetArtworkFromFile(tc) + else: + db_added.AddArtworkFromFile(tc) + break + except: + attempts -= 1 + time.sleep(delay) + if self.verbose: + logger().info(" waiting %.1f seconds for artwork to become writable (attempt #%d)" % + (delay, (10 - attempts))) else: - db_added.AddArtworkFromFile(tc) + if self.verbose: + logger().info(" failed to write artwork") + elif format == 'pdf': if self.verbose: @@ -1844,7 +1924,7 @@ class ITUNES(DriverBase): ub['title'], ub['author'])) - def _find_device_book(self, search): + def _find_device_book(self, search, attempts=9): ''' Windows-only method to get a handle to device book in the current pythoncom session ''' @@ -1854,7 +1934,7 @@ class ITUNES(DriverBase): logger().info(" %s._find_device_book()" % self.__class__.__name__) logger().info(" searching for '%s' by '%s' (%s)" % (search['title'], search['author'], search['uuid'])) - attempts = 9 + while attempts: # Try by uuid - only one hit if 'uuid' in search and search['uuid']: @@ -1908,8 +1988,8 @@ class ITUNES(DriverBase): attempts -= 1 time.sleep(0.5) - if self.verbose: - logger().warning(" attempt #%d" % (10 - attempts)) + if attempts and self.verbose: + logger().info(" attempt #%d" % (10 - attempts)) if self.verbose: logger().error(" no hits") @@ -2010,10 +2090,10 @@ class ITUNES(DriverBase): attempts -= 1 time.sleep(0.5) if self.verbose: - logger().warning(" attempt #%d" % (10 - attempts)) + logger().info(" attempt #%d" % (10 - attempts)) if self.verbose: - logger().error(" search for '%s' yielded no hits" % search['title']) + logger().info(" search for '%s' yielded no hits" % search['title']) return None def _generate_thumbnail(self, book_path, book): @@ -2084,7 +2164,7 @@ class ITUNES(DriverBase): zfw.writestr(thumb_path, thumb_data) except: if self.verbose: - logger().error(" error generating thumb for '%s', caching empty marker" % book.name()) + logger().info(" ERROR: error generating thumb for '%s', caching empty marker" % book.name()) self._dump_hex(data[:32]) thumb_data = None # Cache the empty cover @@ -2490,6 +2570,7 @@ class ITUNES(DriverBase): ''' if self.verbose: + import platform logger().info(" %s %s" % (__appname__, __version__)) logger().info(" [OSX %s, %s %s (%s), %s driver version %d.%d.%d]" % (platform.mac_ver()[0], @@ -2524,7 +2605,7 @@ class ITUNES(DriverBase): raise OpenFeedback('Unable to launch iTunes.\n' + 'Try launching calibre as Administrator') - if not DEBUG: + if not self.verbose: self.iTunes.Windows[0].Minimized = True self.initial_status = 'launched' @@ -2617,14 +2698,14 @@ class ITUNES(DriverBase): if self.manual_sync_mode: # Delete existing from Device|Books, add to self.update_list - # for deletion from booklist[0] during add_books_to_metadata + # for deletion from booklist[0] during remove_books_to_metadata for book in self.cached_books: if (self.cached_books[book]['uuid'] == metadata.uuid or (self.cached_books[book]['title'] == metadata.title and self.cached_books[book]['author'] == metadata.author)): self.update_list.append(self.cached_books[book]) self._remove_from_device(self.cached_books[book]) - self._remove_from_iTunes(self.cached_books[book]) + #self._remove_from_iTunes(self.cached_books[book]) break else: if self.verbose: @@ -2659,7 +2740,7 @@ class ITUNES(DriverBase): except: logger().error(" error deleting '%s'" % cached_book['title']) elif iswindows: - hit = self._find_device_book(cached_book) + hit = self._find_device_book(cached_book, attempts=1) if hit: if self.verbose: logger().info(" deleting '%s' from iDevice" % cached_book['title']) @@ -2987,7 +3068,8 @@ class ITUNES(DriverBase): break if db_added: - logger().warning(" waiting for db_added to become writeable ") + if self.verbose: + logger().info(" waiting for db_added to become writable ") time.sleep(1.0) # If no title_sort plugboard tweak, create sort_name from series/index if metadata.title_sort == metadata_x.title_sort: @@ -3029,7 +3111,8 @@ class ITUNES(DriverBase): lb_added.Year = metadata_x.pubdate.year if db_added: - logger().warning(" waiting for db_added to become writeable ") + if self.verbose: + logger().info(" waiting for db_added to become writable ") time.sleep(1.0) db_added.Name = metadata_x.title db_added.Album = metadata_x.title @@ -3157,11 +3240,11 @@ class ITUNES(DriverBase): attempts -= 1 time.sleep(delay) if self.verbose: - logger().warning(" waiting %.1f seconds for iDevice metadata to become writable (attempt #%d)" % + logger().info(" waiting %.1f seconds for iDevice metadata to become writable (attempt #%d)" % (delay, (10 - attempts))) else: if self.verbose: - logger().error(" failed to write device metadata") + logger().info(" ERROR: failed to write device metadata") def _xform_metadata_via_plugboard(self, book, format): ''' Transform book metadata from plugboard templates ''' @@ -3172,7 +3255,7 @@ class ITUNES(DriverBase): pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards) newmi = book.deepcopy_metadata() newmi.template_to_attribute(book, pb) - if pb is not None and DEBUG: + if pb is not None and self.verbose: #logger().info(" transforming %s using %s:" % (format, pb)) logger().info(" title: '%s' %s" % (book.title, ">>> '%s'" % newmi.title if book.title != newmi.title else '')) diff --git a/src/calibre/devices/idevice/libimobiledevice.py b/src/calibre/devices/idevice/libimobiledevice.py index a760f5e345..28a12404b5 100644 --- a/src/calibre/devices/idevice/libimobiledevice.py +++ b/src/calibre/devices/idevice/libimobiledevice.py @@ -9,294 +9,15 @@ __copyright__ = '2013, Gregory Riker' http://www.libimobiledevice.org/docs/html/globals.html ''' -import binascii, os, sys, time +import os, sys from collections import OrderedDict from ctypes import * -from datetime import datetime from calibre.constants import DEBUG, islinux, isosx, iswindows from calibre.devices.usbms.driver import debug_print -#from calibre.devices.idevice.parse_xml import XmlPropertyListParser -# *** Temporarily here until added to the code tree - -class PropertyListParseError(Exception): - """Raised when parsing a property list is failed.""" - pass - -class XmlPropertyListParser(object): - """ - The ``XmlPropertyListParser`` class provides methods that - convert `Property Lists`_ objects from xml format. - Property list objects include ``string``, ``unicode``, - ``list``, ``dict``, ``datetime``, and ``int`` or ``float``. - - :copyright: 2008 by Takanori Ishikawa - :license: MIT License - - .. _Property List: http://developer.apple.com/documentation/Cocoa/Conceptual/PropertyLists/ - """ - - def _assert(self, test, message): - if not test: - raise PropertyListParseError(message) - - # ------------------------------------------------ - # SAX2: ContentHandler - # ------------------------------------------------ - def setDocumentLocator(self, locator): - pass - def startPrefixMapping(self, prefix, uri): - pass - def endPrefixMapping(self, prefix): - pass - def startElementNS(self, name, qname, attrs): - pass - def endElementNS(self, name, qname): - pass - def ignorableWhitespace(self, whitespace): - pass - def processingInstruction(self, target, data): - pass - def skippedEntity(self, name): - pass - - def startDocument(self): - self.__stack = [] - self.__plist = self.__key = self.__characters = None - # For reducing runtime type checking, - # the parser caches top level object type. - self.__in_dict = False - - def endDocument(self): - self._assert(self.__plist is not None, "A top level element must be .") - self._assert( - len(self.__stack) is 0, - "multiple objects at top level.") - - def startElement(self, name, attributes): - if name in XmlPropertyListParser.START_CALLBACKS: - XmlPropertyListParser.START_CALLBACKS[name](self, name, attributes) - if name in XmlPropertyListParser.PARSE_CALLBACKS: - self.__characters = [] - - def endElement(self, name): - if name in XmlPropertyListParser.END_CALLBACKS: - XmlPropertyListParser.END_CALLBACKS[name](self, name) - if name in XmlPropertyListParser.PARSE_CALLBACKS: - # Creates character string from buffered characters. - content = ''.join(self.__characters) - # For compatibility with ``xml.etree`` and ``plistlib``, - # convert text string to ascii, if possible - try: - content = content.encode('ascii') - except (UnicodeError, AttributeError): - pass - XmlPropertyListParser.PARSE_CALLBACKS[name](self, name, content) - self.__characters = None - - def characters(self, content): - if self.__characters is not None: - self.__characters.append(content) - - # ------------------------------------------------ - # XmlPropertyListParser private - # ------------------------------------------------ - def _push_value(self, value): - if not self.__stack: - self._assert(self.__plist is None, "Multiple objects at top level") - self.__plist = value - else: - top = self.__stack[-1] - #assert isinstance(top, (dict, list)) - if self.__in_dict: - k = self.__key - if k is None: - raise PropertyListParseError("Missing key for dictionary.") - top[k] = value - self.__key = None - else: - top.append(value) - - def _push_stack(self, value): - self.__stack.append(value) - self.__in_dict = isinstance(value, dict) - - def _pop_stack(self): - self.__stack.pop() - self.__in_dict = self.__stack and isinstance(self.__stack[-1], dict) - - def _start_plist(self, name, attrs): - self._assert(not self.__stack and self.__plist is None, " more than once.") - self._assert(attrs.get('version', '1.0') == '1.0', - "version 1.0 is only supported, but was '%s'." % attrs.get('version')) - - def _start_array(self, name, attrs): - v = list() - self._push_value(v) - self._push_stack(v) - - def _start_dict(self, name, attrs): - v = dict() - self._push_value(v) - self._push_stack(v) - - def _end_array(self, name): - self._pop_stack() - - def _end_dict(self, name): - if self.__key is not None: - raise PropertyListParseError("Missing value for key '%s'" % self.__key) - self._pop_stack() - - def _start_true(self, name, attrs): - self._push_value(True) - - def _start_false(self, name, attrs): - self._push_value(False) - - def _parse_key(self, name, content): - if not self.__in_dict: - print("XmlPropertyListParser() WARNING: ignoring %s ( elements must be contained in element)" % content) - #raise PropertyListParseError(" element '%s' must be in element." % content) - else: - self.__key = content - - def _parse_string(self, name, content): - self._push_value(content) - - def _parse_data(self, name, content): - import base64 - self._push_value(base64.b64decode(content)) - - # http://www.apple.com/DTDs/PropertyList-1.0.dtd says: - # - # Contents should conform to a subset of ISO 8601 - # (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. - # Smaller units may be omitted with a loss of precision) - import re - DATETIME_PATTERN = re.compile(r"(?P\d\d\d\d)(?:-(?P\d\d)(?:-(?P\d\d)(?:T(?P\d\d)(?::(?P\d\d)(?::(?P\d\d))?)?)?)?)?Z$") - - def _parse_date(self, name, content): - import datetime - - units = ('year', 'month', 'day', 'hour', 'minute', 'second', ) - pattern = XmlPropertyListParser.DATETIME_PATTERN - match = pattern.match(content) - if not match: - raise PropertyListParseError("Failed to parse datetime '%s'" % content) - - groups, components = match.groupdict(), [] - for key in units: - value = groups[key] - if value is None: - break - components.append(int(value)) - while len(components) < 3: - components.append(1) - - d = datetime.datetime(*components) - self._push_value(d) - - def _parse_real(self, name, content): - self._push_value(float(content)) - - def _parse_integer(self, name, content): - self._push_value(int(content)) - - START_CALLBACKS = { - 'plist': _start_plist, - 'array': _start_array, - 'dict': _start_dict, - 'true': _start_true, - 'false': _start_false, - } - - END_CALLBACKS = { - 'array': _end_array, - 'dict': _end_dict, - } - - PARSE_CALLBACKS = { - 'key': _parse_key, - 'string': _parse_string, - 'data': _parse_data, - 'date': _parse_date, - 'real': _parse_real, - 'integer': _parse_integer, - } - - # ------------------------------------------------ - # XmlPropertyListParser - # ------------------------------------------------ - def _to_stream(self, io_or_string): - if isinstance(io_or_string, basestring): - # Creates a string stream for in-memory contents. - from cStringIO import StringIO - return StringIO(io_or_string) - elif hasattr(io_or_string, 'read') and callable(getattr(io_or_string, 'read')): - return io_or_string - else: - raise TypeError('Can\'t convert %s to file-like-object' % type(io_or_string)) - - def _parse_using_etree(self, xml_input): - from xml.etree.cElementTree import iterparse - - parser = iterparse(self._to_stream(xml_input), events=('start', 'end')) - self.startDocument() - try: - for action, element in parser: - name = element.tag - if action == 'start': - if name in XmlPropertyListParser.START_CALLBACKS: - XmlPropertyListParser.START_CALLBACKS[name](self, element.tag, element.attrib) - elif action == 'end': - if name in XmlPropertyListParser.END_CALLBACKS: - XmlPropertyListParser.END_CALLBACKS[name](self, name) - if name in XmlPropertyListParser.PARSE_CALLBACKS: - XmlPropertyListParser.PARSE_CALLBACKS[name](self, name, element.text or "") - element.clear() - except SyntaxError, e: - raise PropertyListParseError(e) - - self.endDocument() - return self.__plist - - def _parse_using_sax_parser(self, xml_input): - from xml.sax import make_parser, handler, xmlreader, \ - SAXParseException - source = xmlreader.InputSource() - source.setByteStream(self._to_stream(xml_input)) - reader = make_parser() - reader.setContentHandler(self) - try: - reader.parse(source) - except SAXParseException, e: - raise PropertyListParseError(e) - - return self.__plist - - def parse(self, xml_input): - """ - Parse the property list (`.plist`, `.xml, for example) ``xml_input``, - which can be either a string or a file-like object. - - >>> parser = XmlPropertyListParser() - >>> parser.parse(r'' - ... r'Python.py' - ... r'') - {'Python': '.py'} - """ - try: - return self._parse_using_etree(xml_input) - except ImportError: - # No xml.etree.ccElementTree found. - return self._parse_using_sax_parser(xml_input) - -# *** End temporary addition - class libiMobileDeviceException(Exception): def __init__(self, value): self.value = value @@ -304,6 +25,7 @@ class libiMobileDeviceException(Exception): def __str__(self): return repr(self.value) + class libiMobileDeviceIOException(Exception): def __init__(self, value): self.value = value @@ -311,6 +33,7 @@ class libiMobileDeviceIOException(Exception): def __str__(self): return repr(self.value) + class AFC_CLIENT_T(Structure): ''' http://www.libimobiledevice.org/docs/html/structafc__client__private.html @@ -342,6 +65,7 @@ class AFC_CLIENT_T(Structure): # afc_client_private (afc.h) ('free_parent', c_int)] + class HOUSE_ARREST_CLIENT_T(Structure): ''' http://www.libimobiledevice.org/docs/html/structhouse__arrest__client__private.html @@ -359,7 +83,8 @@ class HOUSE_ARREST_CLIENT_T(Structure): # (house_arrest.h) ('mode', c_int) - ] + ] + class IDEVICE_T(Structure): ''' @@ -370,6 +95,7 @@ class IDEVICE_T(Structure): ("conn_type", c_int), ("conn_data", c_void_p)] + class INSTPROXY_CLIENT_T(Structure): ''' http://www.libimobiledevice.org/docs/html/structinstproxy__client__private.html @@ -394,6 +120,7 @@ class INSTPROXY_CLIENT_T(Structure): ('status_updater', c_void_p) ] + class LOCKDOWND_CLIENT_T(Structure): ''' http://www.libimobiledevice.org/docs/html/structlockdownd__client__private.html @@ -416,6 +143,7 @@ class LOCKDOWND_CLIENT_T(Structure): ('udid', c_char_p), ('label', c_char_p)] + class LOCKDOWND_SERVICE_DESCRIPTOR(Structure): ''' from libimobiledevice/include/libimobiledevice/lockdown.h @@ -423,7 +151,7 @@ class LOCKDOWND_SERVICE_DESCRIPTOR(Structure): _fields_ = [ ('port', c_uint), ('ssl_enabled', c_ubyte) - ] + ] class libiMobileDevice(): @@ -468,7 +196,6 @@ class libiMobileDevice(): self.load_library() - # ~~~ Public methods ~~~ def connect_idevice(self): ''' @@ -551,14 +278,18 @@ class libiMobileDevice(): ''' Determine if path exists - Returns [True|False] or file_info + Returns file_info or {} ''' self._log_location("'%s'" % path) return self._afc_get_file_info(path) def get_device_info(self): ''' - Return device profile + Return device profile: + {'Model': 'iPad2,5', + 'FSTotalBytes': '14738952192', + 'FSFreeBytes': '11264917504', + 'FSBlockSize': '4096'} ''' self._log_location() self.device_info = self._afc_get_device_info() @@ -709,7 +440,7 @@ class libiMobileDevice(): self._lockdown_start_service("com.apple.mobile.house_arrest") self.house_arrest = self._house_arrest_client_new() self._house_arrest_send_command(command='VendContainer', - appid=self.installed_apps[app_name]['app_id']) + appid=self.installed_apps[app_name]['app_id']) self._house_arrest_get_result() self.afc = self._afc_client_new_from_house_arrest_client() self._lockdown_client_free() @@ -804,8 +535,8 @@ class libiMobileDevice(): self._log_location("from: '%s' to: '%s'" % (from_name, to_name)) error = self.lib.afc_rename_path(byref(self.afc), - str(from_name), - str(to_name)) + str(from_name), + str(to_name)) if error and self.verbose: self.log(" ERROR: %s" % self.afc_error(error)) @@ -855,7 +586,8 @@ class libiMobileDevice(): self.log(" could not open file for writing") raise libiMobileDeviceIOException("could not open file for writing") - # ~~~ lib helpers ~~~ + # ~~~ AFC functions ~~~ + # http://www.libimobiledevice.org/docs/html/include_2libimobiledevice_2afc_8h.html def _afc_client_free(self): ''' Frees up an AFC client. @@ -1148,7 +880,7 @@ class libiMobileDevice(): data = content datatype = c_char * len(content) else: - data = bytearray(content,'utf-8') + data = bytearray(content, 'utf-8') datatype = c_char * len(content) error = self.lib.afc_file_write(byref(self.afc), @@ -1247,7 +979,6 @@ class libiMobileDevice(): while infolist[num_items]: item_list.append(infolist[num_items]) num_items += 1 - item_type = None for i in range(0, len(item_list), 2): if item_list[i].contents.value in ['st_mtime', 'st_birthtime']: integer = item_list[i+1].contents.value[:10] @@ -1305,7 +1036,8 @@ class libiMobileDevice(): self.current_dir = directory return file_stats - + # ~~~ house_arrest functions ~~~ + # http://www.libimobiledevice.org/docs/html/include_2libimobiledevice_2house__arrest_8h.html def _house_arrest_client_free(self): ''' Disconnects a house_arrest client from the device, frees up the @@ -1411,8 +1143,8 @@ class libiMobileDevice(): self._log_location() plist = c_char_p() - error = self.lib.house_arrest_get_result(byref(self.house_arrest), - byref(plist)) & 0xFFFF + self.lib.house_arrest_get_result(byref(self.house_arrest), + byref(plist)) & 0xFFFF plist = c_void_p.from_buffer(plist) # Convert the plist to xml @@ -1474,7 +1206,8 @@ class libiMobileDevice(): desc=self._house_arrest_error(error)) raise libiMobileDeviceException(error_description) - + # ~~~ idevice functions ~~~ + # http://www.libimobiledevice.org/docs/html/libimobiledevice_8h.html def _idevice_error(self, error): e = "UNKNOWN ERROR" if not error: @@ -1556,7 +1289,8 @@ class libiMobileDevice(): self._log_location(debug) self.lib.idevice_set_debug_level(debug) - + # ~~~ instproxy functions ~~~ + # http://www.libimobiledevice.org/docs/html/include_2libimobiledevice_2installation__proxy_8h.html def _instproxy_browse(self, applist=[]): ''' Fetch the app list @@ -1655,7 +1389,7 @@ class libiMobileDevice(): self._log_location("'%s', '%s'" % (app_type, domain)) self.lib.instproxy_client_options_add(self.client_options, - app_type, domain, None) + app_type, domain, None) def _instproxy_client_options_free(self): ''' @@ -1693,7 +1427,8 @@ class libiMobileDevice(): e = "Operation failed (-5)" return e - + # ~~~ lockdown functions ~~~ + # http://www.libimobiledevice.org/docs/html/include_2libimobiledevice_2lockdown_8h.html def _lockdown_client_free(self): ''' Close the lockdownd client session if one is running, free up the lockdown_client struct @@ -1930,7 +1665,7 @@ class libiMobileDevice(): desc=self._lockdown_error(error)) raise libiMobileDeviceException(error_description) - + # ~~~ logging ~~~ def _log_location(self, *args): ''' ''' @@ -1945,5 +1680,4 @@ class libiMobileDevice(): arg2 = args[1] self.log(self.LOCATION_TEMPLATE.format(cls=self.__class__.__name__, - func=sys._getframe(1).f_code.co_name, arg1=arg1, arg2=arg2)) - + func=sys._getframe(1).f_code.co_name, arg1=arg1, arg2=arg2)) \ No newline at end of file diff --git a/src/calibre/devices/idevice/parse_xml.py b/src/calibre/devices/idevice/parse_xml.py index 562ce22025..8da68756e6 100755 --- a/src/calibre/devices/idevice/parse_xml.py +++ b/src/calibre/devices/idevice/parse_xml.py @@ -15,6 +15,7 @@ a property list file and get back a python native data structure. .. _Property Lists: http://developer.apple.com/documentation/Cocoa/Conceptual/PropertyLists/ """ + class PropertyListParseError(Exception): """Raised when parsing a property list is failed.""" pass @@ -42,18 +43,25 @@ class XmlPropertyListParser(object): # ------------------------------------------------ def setDocumentLocator(self, locator): pass + def startPrefixMapping(self, prefix, uri): pass + def endPrefixMapping(self, prefix): pass + def startElementNS(self, name, qname, attrs): pass + def endElementNS(self, name, qname): pass + def ignorableWhitespace(self, whitespace): pass + def processingInstruction(self, target, data): pass + def skippedEntity(self, name): pass @@ -125,7 +133,7 @@ class XmlPropertyListParser(object): def _start_plist(self, name, attrs): self._assert(not self.__stack and self.__plist is None, " more than once.") self._assert(attrs.get('version', '1.0') == '1.0', - "version 1.0 is only supported, but was '%s'." % attrs.get('version')) + "version 1.0 is only supported, but was '%s'." % attrs.get('version')) def _start_array(self, name, attrs): v = list() @@ -259,8 +267,7 @@ class XmlPropertyListParser(object): return self.__plist def _parse_using_sax_parser(self, xml_input): - from xml.sax import make_parser, handler, xmlreader, \ - SAXParseException + from xml.sax import make_parser, xmlreader, SAXParseException source = xmlreader.InputSource() source.setByteStream(self._to_stream(xml_input)) reader = make_parser() @@ -287,4 +294,4 @@ class XmlPropertyListParser(object): return self._parse_using_etree(xml_input) except ImportError: # No xml.etree.ccElementTree found. - return self._parse_using_sax_parser(xml_input) \ No newline at end of file + return self._parse_using_sax_parser(xml_input)