def _process_xml(request, doc, device): x_response = qxml.get_child(doc, 'response') x_items = qxml.get_child(x_response, 'items') if not x_items: return False was_updated = False # rewrite urls for x_item in qxml.list_children(x_items, 'item'): was_updated |= _filter_item(request, x_items, x_item) was_updated |= _consume_action_queue(request, device, x_items) was_updated |= _update_annotations(device, x_items) if features.download_updated_books: for book in calibre.books().values(): if book.needs_update_on(device) and book.cde_content_type in ( 'EBOK', ): # PDOC updates are not supported ATM logging.warn( "book %s updated in library, telling device %s to download it again", book, device) _add_item(x_items, 'GET', book.cde_content_type, key=book.asin, title=book.title, forced=True) was_updated = True if was_updated: x_total_count = qxml.get_child(x_response, 'total_count') qxml.set_text(x_total_count, len(x_items.childNodes)) return was_updated
def _call_and_process(handler, request, device): response = handler.call_upstream(request, device) cookie = None pkcs12 = None alias = None with minidom.parseString(response.body_text()) as doc: x_response = qxml.get_child(doc, 'response') x_alias = qxml.get_child(x_response, 'user_device_name') alias = qxml.get_text(x_alias) x_adp_token = qxml.get_child(x_response, 'adp_token') if x_adp_token: cookie = qxml.get_text(x_adp_token) else: x_cookie = qxml.get_child(x_response, 'store_authentication_cookie') cookie = qxml.get_text(x_cookie) x_key = qxml.get_child(x_response, 'device_private_key') pkcs12 = qxml.get_text(x_key) if pkcs12: try: pkcs12 = b64decode(bytes(pkcs12, 'ascii')) except: logging.exception("failed to decode incoming device key") pkcs12 = None devices.update(device, alias = alias, cookie = cookie, pkcs12_bytes = pkcs12) return response
def _process_xml(doc, device, reason): x_response = qxml.get_child(doc, 'response') x_items = qxml.get_child(x_response, 'items') if not x_items: return False was_updated = False # rewrite urls for x_item in qxml.list_children(x_items, 'item'): was_updated |= _filter_item(x_items, x_item) was_updated |= _consume_action_queue(device, x_items) was_updated |= _update_annotations(device, x_items) if features.download_updated_books: for book in calibre.books().values(): if book.needs_update_on(device) and book.cde_content_type in ('EBOK', ): # PDOC updates are not supported ATM logging.warn("book %s updated in library, telling device %s to download it again", book, device) _add_item(x_items, 'GET', book.cde_content_type, key = book.asin, title = book.title, forced = True) was_updated = True if was_updated: x_total_count = qxml.get_child(x_response, 'total_count') qxml.set_text(x_total_count, len(x_items.childNodes)) return was_updated
def _call_and_process(handler, request, device): response = handler.call_upstream(request, device) cookie = None pkcs12 = None alias = None with minidom.parseString(response.body_text()) as doc: x_response = qxml.get_child(doc, 'response') x_alias = qxml.get_child(x_response, 'user_device_name') alias = qxml.get_text(x_alias) x_adp_token = qxml.get_child(x_response, 'adp_token') if x_adp_token: cookie = qxml.get_text(x_adp_token) else: x_cookie = qxml.get_child(x_response, 'store_authentication_cookie') cookie = qxml.get_text(x_cookie) x_key = qxml.get_child(x_response, 'device_private_key') pkcs12 = qxml.get_text(x_key) if pkcs12: try: pkcs12 = b64decode(bytes(pkcs12, 'ascii')) except: logging.exception("failed to decode incoming device key") pkcs12 = None devices.update(device, alias=alias, cookie=cookie, pkcs12_bytes=pkcs12) return response
def get_query_params(req): _, _, query = req.path.partition('?') if query: return query_params(query) q = {} if is_signed(req) and req.command == 'POST' and req.body and req.headers['Content-Type'] == 'text/xml': try: with minidom.parseString(req.body) as doc: x_request = qxml.get_child(doc, 'request') x_parameters = qxml.get_child(x_request, 'parameters') for p in qxml.list_children(x_parameters): q[p.nodeName] = qxml.get_text(p) except: pass return q
def get_query_params(req): _, _, query = req.path.partition('?') if query: return query_params(query) q = {} if is_signed(req) and req.command == 'POST' and req.body and req.headers[ 'Content-Type'] == 'text/xml': try: with minidom.parseString(req.body) as doc: x_request = qxml.get_child(doc, 'request') x_parameters = qxml.get_child(x_request, 'parameters') for p in qxml.list_children(x_parameters): q[p.nodeName] = qxml.get_text(p) except: pass return q
def _process_xml(request, doc, device): books_on_device = set() book_nodes = [] was_modified = False x_annotations = qxml.get_child(doc, 'annotations') for x_book in qxml.list_children(x_annotations, 'book'): asin = x_book.getAttribute('key') if is_uuid(asin, x_book.getAttribute('type')): books_on_device.add(asin) x_annotations.removeChild(x_book) book_nodes.append(x_book) was_modified = True for x_collection in qxml.iter_children(x_annotations, 'collection'): for x_book in qxml.iter_children(x_collection, 'book'): asin = x_book.getAttribute('id') if is_uuid(asin, x_book.getAttribute('type')) and 'add' == x_book.getAttribute('action'): books_on_device.add(asin) if books_on_device or book_nodes: postprocess.enqueue(_process_sidecar_upload, device, books_on_device, book_nodes) if was_modified: qxml.remove_whitespace(x_annotations) if not len(x_annotations.childNodes): doc.removeChild(x_annotations) if not len(doc.childNodes): # there's no more content relevant to Amazon, just reply with an empty 200 raise ExceptionResponse() return was_modified
def _process_xml(doc, device, reason): x_response = qxml.get_child(doc, 'response') x_items = qxml.get_child(x_response, 'items') if not x_items: return False was_updated = False # rewrite urls for x_item in qxml.list_children(x_items, 'item'): was_updated |= _filter_item(x_items, x_item) if features.download_updated_books: for book in calibre.books().values(): if book.needs_update_on(device) and book.cde_content_type in ('EBOK', ): # PDOC updates are not supported ATM logging.warn("book %s updated in library, telling device %s to download it again", book, device) # <item action="GET" is_incremental="false" key="asin" priority="600" sequence="0" type="EBOK">title</item> _add_item(x_items, 'GET', book.cde_content_type, key = book.asin, text = book.title, forced = True) # book.title) was_updated = True while device.actions_queue: action = device.actions_queue.pop() # logging.debug("checking action %s", action) if list(qxml.filter(x_items, 'item', action = action[0], type = action[1])): # logging.debug("action %s already found in %s, skipping", action, x_items) continue if action == ('SET', 'SCFG'): _add_item(x_items, 'SET', 'SCFG', text = _servers_config(device), key = 'KSP.set.scfg', priority = 100) was_updated = True elif action == ('UPLOAD', 'SNAP'): _add_item(x_items, 'UPLOAD', 'SNAP', key = 'KSP.upload.snap', priority = 1000, url = config.server_url + 'FionaCDEServiceEngine/UploadSnapshot') was_updated = True # elif action == ('GET', 'NAMS'): # _add_item(x_items, 'GET', 'NAMS', key = 'NameChange' if device.is_kindle() else 'AliasChange') # was_updated = True elif action == ('UPLOAD', 'SCFG'): _add_item(x_items, 'UPLOAD', 'SCFG', key = 'KSP.upload.scfg', priority = 50, url = config.server_url + 'ksp/scfg') was_updated = True else: logging.warn("unknown action %s", action) if was_updated: x_total_count = qxml.get_child(x_response, 'total_count') qxml.set_text(x_total_count, len(x_items.childNodes)) return was_updated
def _process_xml_response(doc, device): x_request = qxml.get_child(doc, 'request') x_items = qxml.get_child(x_request, 'items') was_updated = False for x_item in qxml.list_children(x_items, 'item'): action = x_item.getAttribute('action') cde_type = x_item.getAttribute('type') key = x_item.getAttribute('key') complete_status = x_item.getAttribute('complete_status') if _process_item(device, action, cde_type, key, complete_status): x_items.removeChild(x_item) was_updated = True continue if was_updated and not len(x_items.childNodes): raise ExceptionResponse(data = _DUMMY_BODY) return was_updated
def _process_xml_response(doc, device): x_request = qxml.get_child(doc, 'request') x_items = qxml.get_child(x_request, 'items') was_updated = False for x_item in qxml.list_children(x_items, 'item'): action = x_item.getAttribute('action') cde_type = x_item.getAttribute('type') key = x_item.getAttribute('key') complete_status = x_item.getAttribute('complete_status') if _process_item(device, action, cde_type, key, complete_status): x_items.removeChild(x_item) was_updated = True continue if was_updated and not len(x_items.childNodes): raise ExceptionResponse(data=_DUMMY_BODY) return was_updated
def _process_xml(doc, device): x_response = qxml.get_child(doc, 'response') x_add_update_list = qxml.get_child(x_response, 'add_update_list') x_removal_list = qxml.get_child(x_response, 'removal_list') if not x_add_update_list or not x_removal_list: return False sync_type = x_response.getAttribute('syncType') logging.info("sync (%s) for device %s", sync_type, device) # a full sync is always done just after the proxy started # also, a full sync is usually done when the device just restarted last_sync = 0 if sync_type == 'full' else device.last_sync calibre_books = calibre.books(True) # since we're doing a sync, reload the library was_updated = False for book in calibre_books.values(): if not book.file_path: continue if book.cde_content_type == 'PDOC' and not device.supports_pdoc(): # desktop clients do not handle PDOCs continue if book.last_modified > last_sync or not book.is_known_to(device) or book.needs_update_on(device): if last_sync != 0: logging.warn("book %s newer in library than on %s", book, device) book.mark_known_to(device) x_add_update_list.appendChild(_book_node(doc, book)) was_updated = True for asin, last_downloaded in list(device.books.items()): book = calibre_books.get(asin) if not book or not book.file_path: logging.warn("%s has book %s, but it's not in the library", device, asin) calibre.missing_from_library(asin, device) x_removal_list.appendChild(_slim_book_node(doc, asin, 'EBOK')) # may have been PDOC, who knows? let's see how this works was_updated = True # we could parse the response's sync_time value, but the difference should be under a few seconds in the worst case scenario return was_updated