def get_changelog(self, pkg_name:str, no_local:bool=False): self.refresh_cache() self.candidate = self.parse_package_metadata(pkg_name) # parse the package's origin if not self.candidate.downloadable: origin = "local_package" elif self.candidate.origin == "linuxmint": origin = "linuxmint" elif self.candidate.origin.startswith("LP-PPA-"): origin = "LP-PPA" elif self.apt_origins and self.candidate.origin in self.apt_origins.list(): origin = "APT" else: origin = "unsupported" # Check for changelog of installed package first has_local_changelog = False uri = None if not no_local and self.candidate.is_installed: if _DEBUG: print("Package is installed...") uri = self.get_changelog_from_filelist( self.candidate.installed_files, local=True) # Ubuntu kernel workarounds if self.candidate.origin == "Ubuntu": if self.candidate.source_name == "linux-signed": uri = uri.replace("linux-image","linux-modules") if self.candidate.source_name == "linux-meta": uri = None if uri and not os.path.isfile(uri): uri = None # Do nothing if local changelog exists if uri: has_local_changelog = True # all origins that APT supports elif origin == 'APT': uri = self.get_apt_changelog_uri( self.apt_origins.get(self.candidate.origin)) r = self.check_url(uri) if not r: self.exit_on_fail(2) # Linux Mint repo elif origin == 'linuxmint': # Mint repos don't have .debian.tar.xz files, only full packages, so # check the package cache first base_uri, _ = os.path.split(self.candidate.uri) r, uri = self.get_changelog_uri(base_uri) if not r: # fall back to last change info for the source package # Mint's naming scheme seems to be using amd64 unless source # is i386 only, we always check amd64 first base_uri = "http://packages.linuxmint.com/dev/%s_%s_%s.changes" uri = base_uri % (self.candidate.source_name, self.candidate.source_version, "amd64") r = self.check_url(uri, False) if not r: uri = base_uri % (self.candidate.source_name, self.candidate.source_version, "i386") r = self.check_url(uri, False) if not r: self.exit_on_fail(3) # Launchpad PPA elif origin == 'LP-PPA': ppa_owner, ppa_name, _ = \ self.candidate.uri.split("ppa.launchpad.net/")[1].split("/", 2) base_uri = "http://ppa.launchpad.net/%s/%s/ubuntu/pool/main/{self.source_prefix()}/%s" % (ppa_owner, ppa_name, self.candidate.source_name) r, uri = self.get_changelog_uri(base_uri) if not r: # fall back to last change info only uri = "https://launchpad.net/~%s/+archive/ubuntu/%s/+files/%s_%s_source.changes" % (ppa_owner, ppa_name, self.candidate.source_name, self.candidate.source_version) r = self.check_url(uri, False) if not r: self.exit_on_fail(4) # Not supported origin elif origin == 'unsupported': if _DEBUG: print("Unsupported Package") base_uri, _ = os.path.split(self.candidate.uri) r, uri = self.get_changelog_uri(base_uri) if not r: self.exit_on_fail(5) # Locally installed package without local changelog or remote # source, hope it's cached and contains a changelog elif origin == 'local_package': uri = self.apt_cache_path + self.candidate.filename if not os.path.isfile(uri): self.exit_on_fail(6) # Changelog downloading, extracting and processing: changelog = "" # local changelog if has_local_changelog and not no_local: if _DEBUG: print("Using local changelog:",uri) try: filename = os.path.basename(uri) # determine file type by name/extension # as per debian policy 4.4 the encoding must be UTF-8 # as per policy 12.7 the name must be changelog.Debian.gz or # changelog.gz (deprecated) if filename.lower().endswith('.gz'): changelog = gzip.open(uri,'r').read().decode('utf-8') elif filename.lower().endswith('.xz'): # just in case / future proofing changelog = lzma.open(uri,'r').read().decode('utf-8') elif filename.lower() == 'changelog': changelog = open(uri, 'r').read().encode().decode('utf-8') else: raise ValueError('Unknown changelog format') except Exception as e: _generic_exception_handler(e) self.exit_on_fail(1) # APT-format changelog, download directly # - unfortunately this is slow since the servers support no compression elif origin == "APT": if _DEBUG: print("Downloading: %s (%.2f MB)" % (uri, r.length / self.MB)) changelog = r.text r.close() # last change changelog, download directly elif uri.endswith('.changes'): if _DEBUG: print("Downloading: %s (%.2f MB)" % (uri, r.length / self.MB)) changes = r.text.split("Changes:")[1].split("Checksums")[0].split("\n") r.close() for change in changes: change = change.strip() if change: if change == ".": change = "" changelog += change + "\n" # compressed binary source, download and extract changelog else: source_is_cache = uri.startswith(self.apt_cache_path) if _DEBUG: print("Using cached package:" if source_is_cache else "Downloading: %s (%.2f MB)" % (uri, r.length / self.MB)) try: if not source_is_cache: # download stream to temporary file tmpFile = tempfile.NamedTemporaryFile(prefix="apt-changelog-") if self.interactive and r.length: # download chunks with progress indicator recv_length = 0 blocks = 60 for data in r.iter_content(chunk_size=16384): recv_length += len(data) tmpFile.write(data) recv_pct = recv_length / r.length recv_blocks = int(blocks * recv_pct) print("\r[%(progress)s%(spacer)s] %(percentage).1f%%" % { "progress": "=" * recv_blocks, "spacer": " " * (blocks - recv_blocks), "percentage": recv_pct * 100 }, end="", flush=True) # clear progress bar when done print("\r" + " " * (blocks + 10), end="\r", flush=True) else: # no content-length or non-interactive, download in one go # up to the configured max_download_size, ask only when # exceeded r.raw.decode_content = True size = 0 size_exceeded = False while True: buf = r.raw.read(16*1024) if not size_exceeded: size += len(buf) if size > self.max_download_size: if not self.user_confirm(self.max_download_size_msg_unknown): r.close() tmpFile.close() return "" else: size_exceeded = True if not buf: break tmpFile.write(buf) r.close() tmpFile.seek(0) if uri.endswith(".deb"): # process .deb file if source_is_cache: f = uri else: f = tmpFile.name # We could copy the downloaded .deb files to the apt # cache here but then we'd need to run the script elevated: # shutil.copy(f, self.apt_cache_path + os.path.basename(uri)) deb = DebPackage(f) changelog_file = self.get_changelog_from_filelist(deb.filelist) if changelog_file: changelog = deb.data_content(changelog_file) if changelog.startswith('Automatically decompressed:'): changelog = changelog[29:] else: raise ValueError('Malformed Debian package') elif uri.endswith(".diff.gz"): # Ubuntu partner repo has .diff.gz files, # we can extract a changelog from that data = gzip.open(tmpFile.name, "r").read().decode('utf-8') additions = data.split("+++") for addition in additions: lines = addition.split("\n") if "/debian/changelog" in lines[0]: for line in lines[2:]: if line.startswith("+"): changelog += "%s\n" % line[1:] else: break if not changelog: raise ValueError('No changelog in .diff.gz') else: # process .tar.xz file with tarfile.open(fileobj=tmpFile, mode="r:xz") as tar: changelog_file = self.get_changelog_from_filelist( [s.name for s in tar.getmembers() if s.type in (b"0", b"2")]) if changelog_file: changelog = tar.extractfile(changelog_file).read().decode() else: raise ValueError('No changelog in source package') except Exception as e: _generic_exception_handler(e) self.exit_on_fail(520) if 'tmpFile' in vars(): try: tmpFile.close() except Exception as e: _generic_exception_handler(e) # ALL DONE return changelog
def get_changelog(self, pkg_name:str, no_local:bool=False): self.refresh_cache() self.candidate = self.parse_package_metadata(pkg_name) # parse the package's origin if not self.candidate.downloadable: origin = "local_package" elif self.candidate.origin == "linuxmint": origin = "linuxmint" elif self.candidate.origin.startswith("LP-PPA-"): origin = "LP-PPA" elif self.apt_origins and self.candidate.origin in self.apt_origins.list(): origin = "APT" else: origin = "unsupported" # Check for changelog of installed package first has_local_changelog = False uri = None if not no_local and self.candidate.is_installed: if _DEBUG: print("Package is installed...") uri = self.get_changelog_from_filelist( self.candidate.installed_files, local=True) # Ubuntu kernel workarounds if self.candidate.origin == "Ubuntu": if self.candidate.source_name == "linux-signed": uri = uri.replace("linux-image","linux-modules") if self.candidate.source_name == "linux-meta": uri = None if uri and not os.path.isfile(uri): uri = None # Do nothing if local changelog exists if uri: has_local_changelog = True # all origins that APT supports elif origin == 'APT': uri = self.get_apt_changelog_uri( self.apt_origins.get(self.candidate.origin)) r = self.check_url(uri) if not r: self.exit_on_fail(2) # Linux Mint repo elif origin == 'linuxmint': # Mint repos don't have .debian.tar.xz files, only full packages, so # check the package cache first base_uri, _ = os.path.split(self.candidate.uri) r, uri = self.get_changelog_uri(base_uri) if not r: # fall back to last change info for the source package # Mint's naming scheme seems to be using amd64 unless source # is i386 only, we always check amd64 first base_uri = "http://packages.linuxmint.com/dev/%s_%s_%s.changes" uri = base_uri % (self.candidate.source_name, self.candidate.source_version, "amd64") r = self.check_url(uri, False) if not r: uri = base_uri % (self.candidate.source_name, self.candidate.source_version, "i386") r = self.check_url(uri, False) if not r: self.exit_on_fail(3) # Launchpad PPA elif origin == 'LP-PPA': ppa_owner, ppa_name, _ = \ self.candidate.uri.split("ppa.launchpad.net/")[1].split("/", 2) base_uri = "http://ppa.launchpad.net/%(owner)s/%(name)s/ubuntu/pool/main/%(source_prefix)s/%(source_name)s" % \ { "owner": ppa_owner, "name": ppa_name, "source_prefix": self.source_prefix(), "source_name": self.candidate.source_name } r, uri = self.get_changelog_uri(base_uri) if not r: # fall back to last change info only uri = "https://launchpad.net/~%(owner)s/+archive/ubuntu/%(name)s/+files/%(source_name)s_%(source_version)s_source.changes" % \ { "owner" : ppa_owner, "name": ppa_name, "source_name": self.candidate.source_name, "source_version": self.candidate.source_version } r = self.check_url(uri, False) if not r: self.exit_on_fail(4) # Not supported origin elif origin == 'unsupported': if _DEBUG: print("Unsupported Package") base_uri, _ = os.path.split(self.candidate.uri) r, uri = self.get_changelog_uri(base_uri) if not r: self.exit_on_fail(5) # Locally installed package without local changelog or remote # source, hope it's cached and contains a changelog elif origin == 'local_package': uri = self.apt_cache_path + self.candidate.filename if not os.path.isfile(uri): self.exit_on_fail(6) # Changelog downloading, extracting and processing: changelog = "" # local changelog if has_local_changelog and not no_local: if _DEBUG: print("Using local changelog:",uri) try: filename = os.path.basename(uri) # determine file type by name/extension # as per debian policy 4.4 the encoding must be UTF-8 # as per policy 12.7 the name must be changelog.Debian.gz or # changelog.gz (deprecated) if filename.lower().endswith('.gz'): changelog = gzip.open(uri,'r').read().decode('utf-8') elif filename.lower().endswith('.xz'): # just in case / future proofing changelog = lzma.open(uri,'r').read().decode('utf-8') elif filename.lower() == 'changelog': changelog = open(uri, 'r').read().encode().decode('utf-8') else: raise ValueError('Unknown changelog format') except Exception as e: _generic_exception_handler(e) self.exit_on_fail(1) # APT-format changelog, download directly # - unfortunately this is slow since the servers support no compression elif origin == "APT": if _DEBUG: print("Downloading: %s (%.2f MB)" % (uri, r.length / self.MB)) changelog = r.text r.close() # last change changelog, download directly elif uri.endswith('.changes'): if _DEBUG: print("Downloading: %s (%.2f MB)" % (uri, r.length / self.MB)) changes = r.text.split("Changes:")[1].split("Checksums")[0].split("\n") r.close() for change in changes: change = change.strip() if change: if change == ".": change = "" changelog += change + "\n" # compressed binary source, download and extract changelog else: source_is_cache = uri.startswith(self.apt_cache_path) if _DEBUG: print("Using cached package:" if source_is_cache else "Downloading: %s (%.2f MB)" % (uri, r.length / self.MB)) try: if not source_is_cache: # download stream to temporary file tmpFile = tempfile.NamedTemporaryFile(prefix="apt-changelog-") if self.interactive and r.length: # download chunks with progress indicator recv_length = 0 blocks = 60 for data in r.iter_content(chunk_size=16384): recv_length += len(data) tmpFile.write(data) recv_pct = recv_length / r.length recv_blocks = int(blocks * recv_pct) print("\r[%(progress)s%(spacer)s] %(percentage).1f%%" % { "progress": "=" * recv_blocks, "spacer": " " * (blocks - recv_blocks), "percentage": recv_pct * 100 }, end="", flush=True) # clear progress bar when done print("\r" + " " * (blocks + 10), end="\r", flush=True) else: # no content-length or non-interactive, download in one go # up to the configured max_download_size, ask only when # exceeded r.raw.decode_content = True size = 0 size_exceeded = False while True: buf = r.raw.read(16*1024) if not size_exceeded: size += len(buf) if size > self.max_download_size: if not self.user_confirm(self.max_download_size_msg_unknown): r.close() tmpFile.close() return "" else: size_exceeded = True if not buf: break tmpFile.write(buf) r.close() tmpFile.seek(0) if uri.endswith(".deb"): # process .deb file if source_is_cache: f = uri else: f = tmpFile.name # We could copy the downloaded .deb files to the apt # cache here but then we'd need to run the script elevated: # shutil.copy(f, self.apt_cache_path + os.path.basename(uri)) deb = DebPackage(f) changelog_file = self.get_changelog_from_filelist(deb.filelist) if changelog_file: changelog = deb.data_content(changelog_file) if changelog.startswith('Automatically decompressed:'): changelog = changelog[29:] else: raise ValueError('Malformed Debian package') elif uri.endswith(".diff.gz"): # Ubuntu partner repo has .diff.gz files, # we can extract a changelog from that data = gzip.open(tmpFile.name, "r").read().decode('utf-8') additions = data.split("+++") for addition in additions: lines = addition.split("\n") if "/debian/changelog" in lines[0]: for line in lines[2:]: if line.startswith("+"): changelog += "%s\n" % line[1:] else: break if not changelog: raise ValueError('No changelog in .diff.gz') else: # process .tar.xz file with tarfile.open(fileobj=tmpFile, mode="r:xz") as tar: changelog_file = self.get_changelog_from_filelist( [s.name for s in tar.getmembers() if s.type in (b"0", b"2")]) if changelog_file: changelog = tar.extractfile(changelog_file).read().decode() else: raise ValueError('No changelog in source package') except Exception as e: _generic_exception_handler(e) self.exit_on_fail(520) if 'tmpFile' in vars(): try: tmpFile.close() except Exception as e: _generic_exception_handler(e) # ALL DONE return changelog