From 319197c88b75479a24ef79f2d719548d1e581d31 Mon Sep 17 00:00:00 2001 From: Markus Birth Date: Mon, 16 Sep 2019 02:59:30 +0200 Subject: [PATCH] Return update results as unified UpdateInfo objects. Prepared more features in OptionParser. --- get_updates.py | 2 + grmn/updateserver.py | 100 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/get_updates.py b/get_updates.py index 8fa2d9f..03376b1 100644 --- a/get_updates.py +++ b/get_updates.py @@ -20,6 +20,8 @@ optp.add_option("-W", "--webupdater", action="store_false", dest="express", defa optp.add_option("--id", dest="unit_id", help="Specify custom Unit ID") optp.add_option("--code", action="append", dest="unlock_codes", metavar="UNLOCK_CODE", default=[], help="Specify map unlock codes") optp.add_option("--devicexml", dest="devicexml", metavar="FILE", help="Use specified GarminDevice.xml (also implies -E)") +optp.add_option("--json", action="store_true", dest="json", default=False, help="Output JSON") +optp.add_option("--list-devices", action="store_true", dest="list_devices", default=False, help="Show a list of SKUs and product names") optp.usage = """ %prog [options] SKU1 [SKU2..SKUn] diff --git a/grmn/updateserver.py b/grmn/updateserver.py index f69b463..1efb58a 100644 --- a/grmn/updateserver.py +++ b/grmn/updateserver.py @@ -7,7 +7,7 @@ See https://github.com/AlexWhiter/GarminRelatedStuff/tree/master/GetFirmwareUpda from . import devices from .proto import GetAllUnitSoftwareUpdates_pb2 -from xml.dom.minidom import getDOMImplementation +from xml.dom.minidom import getDOMImplementation, parseString import requests PROTO_API_GETALLUNITSOFTWAREUPDATES_URL = "http://omt.garmin.com/Rce/ProtobufApi/SoftwareUpdateService/GetAllUnitSoftwareUpdates" @@ -16,8 +16,85 @@ GRMN_CLIENT_VERSION = "6.17.0.0" class UpdateInfo: def __init__(self): + self.source = None + self.sku = None + self.device_name = None + self.fw_version = None + self.license_url = None + self.changelog = None + self.notes = None + self.language_code = None + self.update_type = None + self.local_filename = None + self.files = [] + self.build_type = None + self.additional_info_url = None + + def fill_from_protobuf(self, protobuf_info): + self.source = "Express" + self.sku = protobuf_info.product_sku + self.device_name = protobuf_info.device_name + self.fw_version = protobuf_info.fw_version + self.license_url = protobuf_info.license_url + self.changelog = "\n".join(protobuf_info.changelog) + self.language_code = protobuf_info.language + self.local_filename = protobuf_info.update_file + self.update_type = protobuf_info.file_type + for i in protobuf_info.files_list: + self.files.append( { + "url": i.url, + "md5": i.md5, + "size": i.file_size, + "size_is_rounded": False, + } ) + + # From https://docs.python.org/3/library/xml.dom.minidom.html + def dom_get_text(self, node_list): + rc = [] + for rnode in node_list: + for node in rnode.childNodes: + if node.nodeType == node.TEXT_NODE: + rc.append(node.data) + return ''.join(rc) + + def fill_from_response_dom(self, dom): + self.source = "WebUpdater" + self.sku = self.dom_get_text(dom.getElementsByTagName("RequestedPartNumber")) + self.device_name = self.dom_get_text(dom.getElementsByTagName("Description")) + version_major = self.dom_get_text(dom.getElementsByTagName("VersionMajor")) + version_minor = self.dom_get_text(dom.getElementsByTagName("VersionMinor")) + self.fw_version = "{}.{:0>2s}".format(version_major, version_minor) + self.license_url = self.dom_get_text(dom.getElementsByTagName("LicenseLocation")) + self.changelog = self.dom_get_text(dom.getElementsByTagName("ChangeDescription")) + self.notes = self.dom_get_text(dom.getElementsByTagName("Notes")) + self.language_code = self.dom_get_text(dom.getElementsByTagName("RequestedRegionId")) + self.build_type = self.dom_get_text(dom.getElementsByTagName("BuildType")) + self.additional_info_url = self.dom_get_text(dom.getElementsByTagName("AdditionalInfo")) + files = dom.getElementsByTagName("UpdateFile") + for f in files: + # For some reason, WebUpdater returns file size in KiB instead of Bytes + size_kb = self.dom_get_text(f.getElementsByTagName("Size")) + size_bytes = float(size_kb) * 1024 + self.files.append( { + "url": self.dom_get_text(f.getElementsByTagName("Location")), + "md5": self.dom_get_text(f.getElementsByTagName("MD5Sum")), + "size": size_bytes, + "size_is_rounded": True, + } ) + + def get_json(self): + # TODO pass + def __str__(self): + url = "-" + if len(self.files) > 0: + url = self.files[0]["url"] + return "[{}] {} {} {}: {}".format(self.source, self.sku, self.device_name, self.fw_version, url) + + def __repr__(self): + return "[{}] {} {} {}".format(self.source, self.sku, self.device_name, self.fw_version) + class UpdateServer: def __init__(self): @@ -29,13 +106,30 @@ class UpdateServer: # Garmin Express Protobuf API device_xml = self.get_device_xml(sku_numbers) reply = self.get_unit_updates(device_xml) - return reply + results = [] + for i in range(0, len(reply.update_info)): + ui = reply.update_info[i] + r = UpdateInfo() + r.fill_from_protobuf(ui) + results.append(r) + return results def query_webupdater(self, sku_numbers): # WebUpdater requests_xml = self.get_requests_xml(sku_numbers) reply = self.get_webupdater_softwareupdate(requests_xml) - return reply + + # ElementTree might have been easier if it wouldn't be so obnoxious with namespaces + # See https://stackoverflow.com/questions/14853243/parsing-xml-with-namespace-in-python-via-elementtree + dom = parseString(reply) + + results = [] + for resp in dom.getElementsByTagName("Response"): + r = UpdateInfo() + r.fill_from_response_dom(resp) + results.append(r) + + return results def query_updates(self, sku_numbers, query_express=True, query_webupdater=True): results = []