diff --git a/README.md b/README.md index 3a0f1a1..254aeff 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -### apt-offline -- An Offline Package Manager +### apt-offline -- An Offline Package Manager (C) 2005 - 2019 Ritesh Raj Sarraf ![apt-offline-gui](https://camo.githubusercontent.com/867002ddf0a84bc99625d4221eb1f7f6779020e9/68747470733a2f2f6c68362e676f6f676c6575736572636f6e74656e742e636f6d2f5f5f6574717a2d79655034732f545a3778705772717965492f41414141414141414255552f66705a565f316f496e37342f733634302f6170742d6f66666c696e652d616476616e6365642d6f7074696f6e732e706e67) -apt-offline is an offline package management tool written in the Python Programming Language. This program, as of now, is intended for people using Debian (And Debian based) systems. +apt-offline is an offline package management tool written in the Python Programming Language. This program, as of now, is intended for people using Debian (And Debian based) systems. It can help you install/upgrade packages, and their dependencies, on a Debian box with no direct internet connection. It can also download full bug report (Debian only) for those packages. diff --git a/apt-offline-gui b/apt-offline-gui index bb3805c..27af6ce 100755 --- a/apt-offline-gui +++ b/apt-offline-gui @@ -40,4 +40,4 @@ if __name__ == "__main__": myapp = AptOfflineQtMain() myapp.show() sys.exit(app.exec_()) - + diff --git a/apt-offline.8 b/apt-offline.8 index b7b93e4..3908fe8 100644 --- a/apt-offline.8 +++ b/apt-offline.8 @@ -162,8 +162,8 @@ already available, they are stored in the temporary apt cache, where apt validat it for further processing. Note: This does have the caveat that apt may need network availability even though it doesn't download anything over the network. But it does invoke the download routines and realizes that the payload is already available. It then further proceeds with checksum validation -The default behavior is to -.B not +The default behavior is to +.B not do strict checksum validation for .deb files. Instead, apt-offline copies the .deb files to apt's download location. apt still does size validation of the available .deb files and discards them in case there is a mismatch. .IP "\fB\-\-install\-src\-path PATH\fP" 10 @@ -192,8 +192,8 @@ apt\-offline relies on argparse for argument/option parsing. To explicitly instr By specifying the .B \-\- -delimiter, we instruct apt\-offline that foo.sig is an argument to the -.B apt\-offline +delimiter, we instruct apt\-offline that foo.sig is an argument to the +.B apt\-offline command and not to the .B \-\-install\-packages option. @@ -227,10 +227,10 @@ Depending on where the data was downloaded to or packed into, either the absolute FOLDER path or the archive FILE path can be specified. .B NOTE1: -On a freshly installed box, that was installed without the network, the package database is null. In that case, you first need to run -.B apt-offline -with just the -.B \-\-update +On a freshly installed box, that was installed without the network, the package database is null. In that case, you first need to run +.B apt-offline +with just the +.B \-\-update option to ensure you have a meaningful package database .B Example: apt-offline set set.uris \-\-update @@ -290,7 +290,7 @@ Sequence 2: With successful completion of Sequence 1, the APT database on the di (Installs the data needed to upgrade the upgradable packages. Should be run on the disconnected machine) -.TP +.TP After successful completion of .B Sequence 1 and diff --git a/apt_offline_core/AptOfflineCoreLib.py b/apt_offline_core/AptOfflineCoreLib.py index 9285499..f480e44 100644 --- a/apt_offline_core/AptOfflineCoreLib.py +++ b/apt_offline_core/AptOfflineCoreLib.py @@ -49,7 +49,7 @@ except ImportError: # Only available on platform Unix FCNTL_LOCK = False - + # On Debian, python-debianbts package provides this library DebianBTS = True try: @@ -86,7 +86,7 @@ PythonApt = True except ImportError: PythonApt = False - + from apt_offline_core import AptOfflineLib #INFO: Set the default timeout to 30 seconds for the packages that are being downloaded. @@ -105,7 +105,7 @@ terminal_license = "This program comes with ABSOLUTELY NO WARRANTY.\n\ This is free software, and you are welcome to redistribute it under\n\ the GNU GPL Version 3 (or later) License\n" - + errlist = [] supported_platforms = ["Linux", "GNU/kFreeBSD", "GNU"] apt_update_target_path = '/var/lib/apt/lists/partial' @@ -126,10 +126,10 @@ Bool_Verbose = False #Bool_TestWindows = True - + log = AptOfflineLib.Log( Bool_Verbose, lock=True ) - + class FetchBugReports: def __init__( self, apt_bug_file_format, ArchiveFile=None, lock=False, DownloadDir=None ): self.bugsList = [] @@ -137,15 +137,15 @@ def __init__( self, apt_bug_file_format, ArchiveFile=None, lock=False, DownloadD self.apt_bug = apt_bug_file_format self.DownloadDir = DownloadDir self.ArchiveFile = ArchiveFile - + self.fileMgmt = AptOfflineLib.FileMgmt() - - + + def FetchBugsDebian( self, PackageName): '''0 => False 1 => No Bug Reports 2 => True''' - + try: self.bugs_list = debianbts.get_bugs(package=PackageName) num_of_bugs = len(self.bugs_list) @@ -154,12 +154,12 @@ def FetchBugsDebian( self, PackageName): log.err("Foreign exception raised in module debianbts\n") log.err("Failed to download bug report for package %s\n" % (PackageName)) return 0 - - + + if num_of_bugs: atleast_one_bug_report_downloaded = False for eachBug in self.bugs_list: - + # Fetch bug report.. # TODO: Handle exceptions later try: @@ -171,20 +171,20 @@ def FetchBugsDebian( self, PackageName): log.warn("Failed to download bug report for %s\nWill continue to download others\n" % (eachBug)) log.verbose(traceback.format_exc()) continue - + # This tells us how many follow-ups for the bug report are present. bugReportLength = bugReport.__len__() writeBugReport = 0 self.fileName = os.path.join(tempfile.gettempdir(), PackageName + "{}" + str(eachBug) + "{}" + self.apt_bug) file_handle = open( self.fileName, 'w' ) - + #TODO: Can we manipulate these headers in a more efficient way??? for line in bugReport[writeBugReport]['header'].split("\n"): if line.startswith("Subject:"): file_handle.write(line) file_handle.write("\n") break - + while writeBugReport < bugReportLength: file_handle.write(bugReport[writeBugReport]['body']) file_handle.write("\n\n") @@ -206,7 +206,7 @@ def FetchBugsDebian( self, PackageName): log.verbose("%s added to download dir %s\n" % (self.fileName, self.DownloadDir)) except AptOfflineLibShutilError as msg: log.warn("%s\n" % (msg)) - + atleast_one_bug_report_downloaded = True if atleast_one_bug_report_downloaded: return 2 @@ -216,7 +216,7 @@ def FetchBugsDebian( self, PackageName): #FIXME: When no bug reports are there, i.e. bug count is 0, we hit here # We shouldn't be returning False return 1 - + def AddToArchive(self, ArchiveFile, fileName): try: if self.compress_the_file(ArchiveFile, fileName): @@ -225,7 +225,7 @@ def AddToArchive(self, ArchiveFile, fileName): except AptOfflineErrors as message: log.warn("%s\n" % (message)) return True - + class ExecCmd: def __init__(self, Simulate=False): self.Simulate = Simulate @@ -233,7 +233,7 @@ def __init__(self, Simulate=False): def Simulate(self): if self.Simulate is True: pass - + def ExecSystemCmd(self, cmd, sigFile=None): ''' Execute command 'cmd' with subprocess module @@ -241,7 +241,7 @@ def ExecSystemCmd(self, cmd, sigFile=None): ''' if self.Simulate: return True - + if sigFile is None: #subprocess.call does take None as an arg fh = None else: @@ -250,12 +250,12 @@ def ExecSystemCmd(self, cmd, sigFile=None): except Exception: log.verbose(traceback.format_exc()) return False - + if fh is not None: preState = fh.tell() log.verbose("Command is: %s\n" % (cmd)) - + p = subprocess.call(cmd, universal_newlines=True, stdout=fh) if fh is not None: fh.flush() @@ -269,15 +269,15 @@ def ExecSystemCmd(self, cmd, sigFile=None): fh.flush() return False return True - + class AptManip(ExecCmd): def __init__(self, OutputFile, Simulate=False, AptType="apt-get", AptReinstall=False): - + ExecCmd.__init__(self, Simulate) self.WriteTo = OutputFile self.AptReinstall = AptReinstall self.ShouldSimulate = Simulate - + if AptType == "apt": self.apt = "apt" elif AptType == "apt-get": @@ -288,8 +288,8 @@ def __init__(self, OutputFile, Simulate=False, AptType="apt-get", AptReinstall=F self.apt = "python-apt" else: self.apt = "apt-get" - - + + def Update(self): log.verbose("APT Update Method is of type: %s\n" % self.apt) if self.apt == "apt-get": @@ -303,8 +303,8 @@ def Update(self): else: log.err("Method not supported") sys.exit(1) - - + + def Upgrade(self, UpgradeType="upgrade", ReleaseType=None): log.verbose("APT Upgrade Method is of type: %s\n" % self.apt) if self.apt == "apt-get": @@ -320,7 +320,7 @@ def Upgrade(self, UpgradeType="upgrade", ReleaseType=None): else: log.err("Method not supported") sys.exit(1) - + def InstallPackages(self, PackageList, ReleaseType): log.verbose("APT Install Method is of type: %s\n" % self.apt) if self.apt == "apt-get": @@ -332,7 +332,7 @@ def InstallPackages(self, PackageList, ReleaseType): else: log.err("Method not supported") sys.exit(1) - + def InstallSrcPackages(self, SrcPackageList, ReleaseType, BuildDependency): log.verbose("APT Install Source Method is of type: %s\n" % self.apt) if self.apt == "apt-get": @@ -344,8 +344,8 @@ def InstallSrcPackages(self, SrcPackageList, ReleaseType, BuildDependency): else: log.err("Method not supported") sys.exit(1) - - + + def __FixAptSigs(self): if self.ShouldSimulate is True: log.msg("In simulation mode, no changes applied\n") @@ -356,24 +356,24 @@ def __FixAptSigs(self): log.verbose("Recovering gpg signature %s.\n" % (localFile) ) localFile = os.path.join(apt_update_target_path, localFile) os.rename(localFile, os.path.join(apt_update_final_path + sig_file) ) - + def __AptUpdate(self): log.msg("Gathering details needed for 'update' operation\n") if self.ExecSystemCmd(["/usr/bin/apt", "-qq", "--print-uris", "update"], self.WriteTo) is False: log.err( "FATAL: Something is wrong with the apt system.\n" ) log.verbose("Calling __FixAptSigs to fix the apt sig problem") self.__FixAptSigs() - + def __AptGetUpdate(self): log.msg("Gathering details needed for 'update' operation\n") if self.ExecSystemCmd(["/usr/bin/apt-get", "-q", "--print-uris", "update"], self.WriteTo) is False: log.err( "FATAL: Something is wrong with the apt system.\n" ) log.verbose("Calling __FixAptSigs to fix the apt sig problem") self.__FixAptSigs() - + def __AptitudeUpdate(self): pass - + def __PythonAptUpdate(self): log.verbose("Open file %s for write" % self.WriteTo) try: @@ -382,30 +382,30 @@ def __PythonAptUpdate(self): log.verbose(traceback.format_exc()) log.err("Failed to open file %s for write. Exiting\n" % (self.WriteTo)) sys.exit(1) - + log.msg("Gathering details needed for 'update' operation\n") log.verbose("\nUsing Python apt interface\n") - + apt_pkg.init_config() apt_pkg.init_system() - + acquire = apt_pkg.Acquire() slist = apt_pkg.SourceList() - + # Read the main list slist.read_main_list() - + # Add all indexes to the fetcher slist.get_indexes(acquire, True) - + # Now write the URI of every item for item in acquire.items: - + #INFO: For update files, there's no checksum present. # Also, their size is not determined. # Hence filesize is always returned '0' # And checksum is something I'm writing as ':' - + # We strip item.destfile because that's how apt-get had historically presented it to us destFile = item.destfile.split("/")[-1] @@ -413,22 +413,22 @@ def __PythonAptUpdate(self): log.verbose("Writing string %s %s %d %s to file %s\n" % (item.desc_uri, destFile, item.filesize, ":", self.WriteTo) ) self.writeFH.flush() self.writeFH.close() - + def __PythonAptUpgrade(self, UpgradeType="upgrade", ReleaseType=None): - + log.verbose("Open file %s for write\n" % self.WriteTo) - + try: self.writeFH = open(self.WriteTo, 'a') except Exception: log.verbose(traceback.format_exc()) log.err("Failed to open file %s for write. Exiting\n") sys.exit(1) - + log.msg("Gathering details needed for 'upgrade' operation\n") log.warn("Option --upgrade-type not supported with this backend\n") log.verbose("\nUsing Python apt interface\n") - + cache = apt.Cache() cache.open(None) if UpgradeType == "dist-upgrade": @@ -437,7 +437,7 @@ def __PythonAptUpgrade(self, UpgradeType="upgrade", ReleaseType=None): cache.upgrade(dist_upgrade=False) else: cache.upgrade() - + for pkg in cache.get_changes(): log.verbose("Generable data is: %s %s %d %s\n" % (pkg.candidate.uri, pkg.candidate.filename.split('/')[-1], pkg.candidate.size, pkg.candidate.md5)) self.writeFH.write("%s %s %d %s\n" % (pkg.candidate.uri, pkg.candidate.filename.split('/')[-1], pkg.candidate.size, pkg.candidate.md5)) @@ -446,7 +446,7 @@ def __PythonAptUpgrade(self, UpgradeType="upgrade", ReleaseType=None): def __AptUpgrade(self, UpgradeType="upgrade", ReleaseType=None): self.ReleaseType = ReleaseType - + if ReleaseType is not None: cmd = ["/usr/bin/apt", "-qqq", "--print-uris", "-t"] cmd.append(self.ReleaseType) @@ -461,7 +461,7 @@ def __AptUpgrade(self, UpgradeType="upgrade", ReleaseType=None): def __AptGetUpgrade(self, UpgradeType="upgrade", ReleaseType=None): self.ReleaseType = ReleaseType - + if ReleaseType is not None: cmd = ["/usr/bin/apt-get", "-qq", "--print-uris", "-t"] cmd.append(self.ReleaseType) @@ -474,7 +474,7 @@ def __AptGetUpgrade(self, UpgradeType="upgrade", ReleaseType=None): if self.ExecSystemCmd(cmd, self.WriteTo) is False: log.err("FATAL: Something is wrong with the APT system\n") - + def __AptInstallPackage(self, PackageList=None, ReleaseType=None): self.ReleaseType = ReleaseType @@ -494,11 +494,11 @@ def __AptInstallPackage(self, PackageList=None, ReleaseType=None): log.err( "FATAL: Something is wrong with the apt system.\n" ) def __AptInstallSrcPackages(self, SrcPackageList=None, ReleaseType=None, BuildDependency=False): - + self.ReleaseType = ReleaseType - + log.msg("Gathering installation details for source package %s\n" % (PackageList) ) - + if self.ReleaseType is not None: cmd = ["/usr/bin/apt", "-qqq", "--print-uris", "source", "-t"] cmd.append(self.ReleaseType) @@ -511,15 +511,15 @@ def __AptInstallSrcPackages(self, SrcPackageList=None, ReleaseType=None, BuildDe for pkg in SrcPackageList: cmd.append(pkg) cmdBuildDep.append(pkg) - + if self.ExecSystemCmd(cmd, self.WriteTo) is False: log.err( "FATAL: Something is wrong with the apt system.\n" ) if BuildDependency: log.msg("Generating Build-Dependency for source packages %s.\n" % (SrcPackageList) ) if self.ExecSystemCmd(cmdBuildDep, self.WriteTo) is False: log.err( "FATAL: Something is wrong with the apt system.\n" ) - - + + def __AptGetInstallPackage(self, PackageList=None, ReleaseType=None): self.ReleaseType = ReleaseType @@ -539,11 +539,11 @@ def __AptGetInstallPackage(self, PackageList=None, ReleaseType=None): log.err( "FATAL: Something is wrong with the apt system.\n" ) def __AptGetInstallSrcPackages(self, SrcPackageList=None, ReleaseType=None, BuildDependency=False): - + self.ReleaseType = ReleaseType - + log.msg("Gathering installation details for source package %s\n" % (SrcPackageList) ) - + if self.ReleaseType is not None: cmd = ["/usr/bin/apt-get", "-qq", "--print-uris", "source", "-t"] cmd.append(self.ReleaseType) @@ -556,7 +556,7 @@ def __AptGetInstallSrcPackages(self, SrcPackageList=None, ReleaseType=None, Buil for pkg in SrcPackageList: cmd.append(pkg) cmdBuildDep.append(pkg) - + if self.ExecSystemCmd(cmd, self.WriteTo) is False: log.err( "FATAL: Something is wrong with the apt system.\n" ) if BuildDependency: @@ -566,16 +566,16 @@ def __AptGetInstallSrcPackages(self, SrcPackageList=None, ReleaseType=None, Buil class APTVerifySigs(ExecCmd): - + def __init__(self, gpgv=None, keyring=None, Simulate=False): ''' Initialize keyring based on environment Uses python-apt or apt-config ''' - + ExecCmd.__init__(self, Simulate) self.defaultPaths = [] - + if PythonApt is True: apt_pkg.init() self.defaultPaths.append(apt_pkg.config.find_dir('Dir::Etc::trustedparts')) @@ -596,7 +596,7 @@ def __init__(self, gpgv=None, keyring=None, Simulate=False): output = process.communicate(input=command)[0] trusted, trustedparts = output.decode('utf-8').split('\x00') log.verbose("trusted is %s and trustedparts is %s\n" % (trusted, trustedparts)) - + self.defaultPaths.append(trusted) self.defaultPaths.append(trustedparts) log.verbose("APT Signature verification path is: %s\n" % self.defaultPaths) @@ -605,10 +605,10 @@ def __init__(self, gpgv=None, keyring=None, Simulate=False): self.gpgv="/usr/bin/gpgv" else: self.gpgv=gpgv - - self.opts = [] + + self.opts = [] if keyring is None: - + self.opts.append("--ignore-time-conflict") for eachPath in self.defaultPaths: if os.path.isfile(eachPath): @@ -625,16 +625,16 @@ def __init__(self, gpgv=None, keyring=None, Simulate=False): else: finalKeyring = "--keyring %s --ignore-time-conflict" % (keyring) self.opts.extend(finalKeyring.split()) - + def VerifySig(self, signature_file, signed_file): - + if not os.access(signature_file, os.F_OK): log.err("%s is bad. Can't proceed.\n" % (signature_file) ) return False if not os.access(signed_file, os.F_OK): log.err("%s is bad. Can't proceed.\n" % (signed_file) ) return False - + #INFO: Commands can escape and inject. So carefully craft the command # Thanks: Bernd Dietzel gpgvCmd = [] @@ -643,13 +643,13 @@ def VerifySig(self, signature_file, signed_file): gpgvCmd.append(signature_file) gpgvCmd.append(signed_file) return self.ExecSystemCmd(gpgvCmd, None) - + class LockAPT: '''Manipulate locks on the APT Database''' - + def __init__(self, lists, packages): - + try: self.listLock = os.open(lists, os.O_RDWR | os.O_TRUNC | os.O_CREAT, 0o640) self.pkgLock = os.open(packages, os.O_RDWR | os.O_TRUNC | os.O_CREAT, 0o640) @@ -657,7 +657,7 @@ def __init__(self, lists, packages): log.verbose(traceback.format_exc()) log.err("Couldn't open lockfile\n") return False - + def lockLists(self): try: fcntl.lockf(self.listLock, fcntl.LOCK_EX | fcntl.LOCK_NB) @@ -665,7 +665,7 @@ def lockLists(self): except Exception: log.verbose(traceback.format_exc()) return False - + def lockPackages(self): try: fcntl.lockf(self.pkgLock, fcntl.LOCK_EX | fcntl.LOCK_NB) @@ -673,7 +673,7 @@ def lockPackages(self): except Exception: log.verbose(traceback.format_exc()) return False - + def unlockLists(self): try: fcntl.lockf(self.listLock, fcntl.LOCK_UN) @@ -681,7 +681,7 @@ def unlockLists(self): except Exception: log.verbose(traceback.format_exc()) return False - + def unlockPackages(self): try: fcntl.lockf(self.pkgLock, fcntl.LOCK_UN) @@ -698,13 +698,13 @@ def download_from_web(self, url, localFile, download_dir): block_size = 4096 i = 0 counter = 0 - + os.chdir(download_dir) try: temp = urllib.request.urlopen(url) headers = temp.info() size = int(headers['Content-Length']) - + #INFO: Add the download thread into the Global ProgressBar Thread self.addItem(size) except urllib.error.HTTPError as errstring: @@ -713,7 +713,7 @@ def download_from_web(self, url, localFile, download_dir): except urllib.error.URLError as errstring: #INFO: Weird. But in urllib2.URLError, I noticed that for # error type "timeouts", no errno was defined. - # errstring.errno was listed as None + # errstring.errno was listed as None # In my tests, wget categorized this behavior as: # 504: gateway timeout # So I am doing the same here. @@ -734,7 +734,7 @@ def download_from_web(self, url, localFile, download_dir): except socket.timeout: log.err("Socket timeout. Skipping URL: %s\n" % (url)) return False - + data = open(localFile,'wb') socket_counter = 0 while i < size: @@ -747,19 +747,19 @@ def download_from_web(self, url, localFile, download_dir): except socket.error: socket_timeout = True socket_counter += 1 - + if socket_counter == SOCKET_TIMEOUT_RETRY: errfunc(101010, "Max timeout retry count reached. Discontinuing download.\n", url) - + # Clean the half downloaded file. data.close() os.unlink(localFile) return False - + if socket_timeout is True: errfunc(10054, "Socket Timeout. Retry - %d\n" % (socket_counter) , url) continue - + increment = min(block_size, size - i) i += block_size counter += 1 @@ -786,16 +786,16 @@ def download_from_web(self, url, localFile, download_dir): # errfunc(e.code, e.reason, localFile) # except socket.timeout: # errfunc(10054, "Socket timeout.\n", url) - + class DownloadFromWeb(AptOfflineLib.ProgressBar, GenericDownloadFunction): '''Class for DownloadFromWeb This class also inherits progressbar functionalities from parent class, ProgressBar''' - + def __init__(self, width, total_items): '''width = Progress Bar width''' AptOfflineLib.ProgressBar.__init__(self, width=width, total_items=total_items) - + def stripper(item): '''Strips extra characters from "item". @@ -805,7 +805,7 @@ def stripper(item): size - The file size checksum - The checksum string and returns them.''' - + log.verbose("Item before split is: %s\n" % (item)) try: url, localFile, size, checksum = item.split(' ') @@ -815,15 +815,15 @@ def stripper(item): url, localFile, size = item.split(' ') checksum = None log.verbose("Item after split is: %s %s %s %s\n" % (url, localFile, size, checksum)) - + url = url.rstrip("'") url = url.lstrip("'") log.verbose("Stripped item URL is: %s\n" % url) - + localFile = localFile.rstrip("'") localFile = localFile.lstrip("'") log.verbose("Stripped item FILE is: %s\n" % localFile) - + try: size = size.rstrip("'") size = size.lstrip("'") @@ -840,7 +840,7 @@ def stripper(item): checksum = checksum.lstrip("'") checksum = checksum.rstrip("\n") log.verbose("Stripped item CHECKSUM is: %s\n" % checksum) - + return url, localFile, size, checksum @@ -865,7 +865,7 @@ def errfunc(errno, errormsg, filename): #TODO: Find out what these error codes are for # and better document them the next time you find it out. - # 13 is for "Permission Denied" when you don't have privileges to access the destination + # 13 is for "Permission Denied" when you don't have privileges to access the destination if errno in retriable_error_codes: log.verbose("%s - %s - %s %s\n" % (filename, errno, errormsg, LINE_OVERWRITE_FULL)) log.verbose("Will still try with other package uris\n") @@ -882,10 +882,10 @@ def errfunc(errno, errormsg, filename): sys.exit(errno) else: log.err("I don't understand this error code %s\nPlease file a bug report\n" % (errno)) - + def fetcher( args ): - + # get opts Str_GetArg = args.get Int_SocketTimeout = args.socket_timeout @@ -902,7 +902,7 @@ def fetcher( args ): Bool_DisableCertCheck = args.disable_cert_check Bool_BugReports = args.deb_bugs global guiTerminateSignal - + if Int_SocketTimeout: try: Int_SocketTimeout.__int__() @@ -967,31 +967,31 @@ def fetcher( args ): log.err( "Incorrect value set for HttpBasicAuth.\n" ) sys.exit( 1 ) - + #INFO: Python 2.5 has hashlib which supports sha256 # If we don't have Python 2.5, disable MD5/SHA256 checksum if AptOfflineLib.Python_2_5 is False: Bool_DisableMD5Check = True log.verbose( "\nMD5/SHA256 Checksum is being disabled. You need at least Python 2.5 to do checksum verification.\n" ) - + if Str_GetArg: if os.path.isfile(Str_GetArg): log.msg( "\nFetching APT Data\n\n" ) else: log.err( "File %s not present. Check path.\n" % (Str_GetArg) ) sys.exit( 1 ) - + if Str_CacheDir: if os.path.isdir( Str_CacheDir ) is False: log.err( "WARNING: cache dir %s is incorrect. Did you give the full path ?\n" % (Str_CacheDir) ) sys.exit(1) - + if not Str_DownloadDir and not Str_BundleFile: log.err("Please provide a target download file/folder location.\n") sys.exit(1) if Str_DownloadDir: - if os.path.exists(Str_DownloadDir): + if os.path.exists(Str_DownloadDir): if os.path.isdir(Str_DownloadDir): if os.access( Str_DownloadDir, os.W_OK ) is True: Str_DownloadDir = os.path.abspath( Str_DownloadDir ) @@ -1017,7 +1017,7 @@ def fetcher( args ): os.mkdir(tempdir) Str_DownloadDir = os.path.abspath(tempdir) else: - log.err( "%s is not writable\n" % (tempdir) ) + log.err( "%s is not writable\n" % (tempdir) ) errfunc ( 1, '', tempdir) if Str_BundleFile: @@ -1045,7 +1045,7 @@ def fetcher( args ): class FetcherClass( DownloadFromWeb, AptOfflineLib.Archiver, AptOfflineLib.Checksum, AptOfflineLib.FileMgmt, FetchBugReports): #def __init__( self, width, lock, total_items, BoolCheckSum=False, BoolBundleFile=False, BoolBugReports=False, BoolDownloadDir=False, BoolCacheDir=False): def __init__( self, *args, **kwargs): - + DownloadFromWeb.__init__( self, width=kwargs.pop('width'), total_items=kwargs.pop('total_items') ) #ProgressBar.__init__(self, width) #self.width = width @@ -1061,48 +1061,48 @@ def __init__( self, *args, **kwargs): self.BugReports = Bool_BugReports self.DownloadDir = Str_DownloadDir self.CacheDir = Str_CacheDir - - + + def verifyPayloadIntegrity(self, payload, checksum): '''Verify the integrity of the payload against the checksum''' - + if self.CheckSum is True: return True - + if self.CheckHashDigest(payload, checksum): return True else: return False - + def writeData(self, data): '''Write data to backend''' if self.BundleFile is not False: self.writeToArchive(data) else: self.writeToDir(data) - + def writeToDir(self, data): '''Write data to directory''' self.copy_file(data, self.DownloadDir) - + def writeToArchive(self, data): '''Write data to archive file''' try: self.compress_the_file(self.BundleFile, data) except AptOfflineErrors as message: log.warn("%s\n" % (message)) - + def writeToCache(self, data): '''Write data to cacheDir''' if self.CacheDir: self.copy_file(data, self.CacheDir) - + def processBugReports(self, pkgName): '''Process Bug Reports''' - + if not self.BugReports: return False - + log.msg("Fetching bug report for %s%s\n" % (pkgName, LINE_OVERWRITE_FULL)) #INFO: Payload is written to destination inside the method self.FetchBugsDebian(pkgName) @@ -1111,7 +1111,7 @@ def processBugReports(self, pkgName): def buildChangelog(self, pkgPath, installedVersion): '''Return latest changes against installedVersion''' constChangelog = "changelog.Debian.gz" - + if PythonApt is not True: log.err("Cannot provide changelog feature\n") return False @@ -1129,12 +1129,12 @@ def buildChangelog(self, pkgPath, installedVersion): log.warn("Couldn't extract changelog for package %s\n" % (pkgHandle.pkgname)) log.verbose(traceback.format_exc()) break - + chlogFile.flush() - + #Seek to beginning chlogFile.seek(0) - + for eachLine in chlogFile.readlines(): if installedVersion in eachLine: break @@ -1155,10 +1155,10 @@ def buildChangelog(self, pkgPath, installedVersion): chlogFile.close() pkgLogFile.close() break - + FetchData = {} #Info: Initialize an empty dictionary. PackageInstalledVersion = {} #INFO: This key/val dict contains record of installed packages - + #INFO: We don't distinguish in between what to fetch # We just rely on what a signature file lists us to get # It can be just debs or just package updates or both @@ -1169,10 +1169,10 @@ def buildChangelog(self, pkgPath, installedVersion): ( errno, strerror ) = e.args log.err( "%s %s %s\n" % ( errno, strerror, Str_GetArg ) ) sys.exit(errno) - + FetchData['Item'] = [] for item in raw_data_list: - + if item.startswith("Changelog/"): (strConstant, pkgName, pkgVersion) = item.split("/") pkgVersion = pkgVersion.strip() @@ -1189,30 +1189,30 @@ def buildChangelog(self, pkgPath, installedVersion): ExtraItemFile = ItemFile.rstrip(ItemFile.split("_")[-1]) GPGItemFile = ExtraItemFile + "Release.gpg" ReleaseItemFile = ExtraItemFile + "Release" - + FetchData['Item'].append(GPGItemURL + " " + GPGItemFile + " " + str(ItemSize)) log.verbose("Printing GPG URL/Files\n") log.verbose("%s %s" % (GPGItemURL, GPGItemFile) ) - + FetchData['Item'].append(ReleaseItemURL + " " + ReleaseItemFile + " " + str(ItemSize)) log.verbose("Printing Release URL/Files\n") log.verbose("%s %s" % (ReleaseItemURL, ReleaseItemFile) ) FetchData['Item'].append( item ) del raw_data_list - + # INFO: Let's get the total number of items. This will get the # correct total count in the progress bar. total_items = len(FetchData['Item']) #BoolCheckSum=False, BoolBundleFile=False, BoolBugReports=False, BoolDownloadDir=False, BoolCacheDir=False): FetcherInstance = FetcherClass(Bool_DisableMD5Check, Str_BundleFile, Bool_BugReports, Str_DownloadDir, Str_CacheDir, total_items=total_items, width=30, lock=True) - + #INFO: Thread Support if Int_NumOfThreads > 2: log.msg("WARNING: If you are on a slow connection, it is good to\n") log.msg("WARNING: limit the number of threads to a low number like 2.\n") log.msg("WARNING: Else higher number of threads executed could cause\n") log.msg("WARNING: network congestion and timeouts.\n\n") - + def DataFetcher(request, response, func=FetcherInstance.find_first_match): '''Get items from the request Queue, process them with func(), put the results along with the @@ -1223,7 +1223,7 @@ def DataFetcher(request, response, func=FetcherInstance.find_first_match): #if tuple_item_key is None: # break #(key, item) = tuple_item_key - + (key, item) = request # On many boxes, the cdrom apt repository will be enabled. @@ -1231,31 +1231,31 @@ def DataFetcher(request, response, func=FetcherInstance.find_first_match): if item.startswith("\'cdrom"): log.err("cdrom apt repository not supported. Skipping %s\n" % (item)) return True - - + + #INFO: Everything (url, pkgFile, download_size, checksum) = stripper(item) thread_name = threading.currentThread().getName() log.verbose("Thread is %s\n" % (thread_name) ) - + if url.endswith(".deb"): try: PackageName = pkgFile.split("_")[0] except IndexError: log.err("Not getting a package name here is problematic. Better bail out.\n") sys.exit(1) - + #INFO: For Package version, we don't want to fail try: PackageVersion = pkgFile.split("_")[1] except IndexError: PackageVersion = "NA" log.verbose("Weird!! Package version not present. Is it really a deb file?\n") - - + + #INFO: find_first_match() returns False or a file name with absolute path full_file_path = func(Str_CacheDir, pkgFile) - + #INFO: If we find the file in the local Str_CacheDir, we'll execute this block. if full_file_path is not False: if FetcherInstance.verifyPayloadIntegrity(full_file_path, checksum): @@ -1303,7 +1303,7 @@ def DownloadPackages(PackageName, PackageFile): return True else: return False - + #INFO: Handle the multiple Packages formats. # See DTBS #583502 SupportedFormats = ["bz2", "gz", "xz", "lzma"] @@ -1325,26 +1325,26 @@ def DownloadPackages(PackageName, PackageFile): pkgFileWithType = pkgFile + "." + url.split("/")[-1].split(".")[-1] if PackageFormat in SupportedFormats: SupportedFormats.remove(PackageFormat) #Remove the already tried format - - log.msg("Downloading %s %s\n" % (PackageName, LINE_OVERWRITE_FULL) ) + + log.msg("Downloading %s %s\n" % (PackageName, LINE_OVERWRITE_FULL) ) if DownloadPackages(PackageName, pkgFileWithType) is False and guiTerminateSignal is False: # don't proceed retry if Ctrl+C in cli log.verbose("%s failed. Retry with the remaining possible formats\n" % (url) ) FetcherInstance.completed() - + # We could fail with the Packages format of what apt gave us. We can try the rest of the formats that apt or the archive could support reallyFailed = True for Format in SupportedFormats: NewPackageFile = pkgFileWithType.rstrip(pkgFileWithType.split(".")[-1]).rstrip(".") + "." + Format NewUrl = url.replace(PackageFormat, Format) log.verbose("Retry download %s %s\n" % (NewUrl, LINE_OVERWRITE_FULL) ) - + #INFO: Why are we doing this? # Because ProgressBar's total_item is fixed # And download_from_web's addItem() increases the active item upon every # cycle through apt's backend archive formats # This ends up resulting in active items being more than total_items - # By increasing the counter, the active/total item list is reflected correctly + # By increasing the counter, the active/total item list is reflected correctly FetcherInstance.items += 1 if DownloadPackages(NewUrl, NewPackageFile) is True: reallyFailed = False @@ -1385,10 +1385,10 @@ def DownloadPackages(PackageName, PackageFile): else: errfunc(errstring.errno, errstring.reason, url) log.verbose(traceback.format_exc()) - + if not guiTerminateSignal: ConnectThread = AptOfflineLib.MyThread(DataFetcher, requestQueue, responseQueue, Int_NumOfThreads) - + ConnectThread.startThreads() # Queue up the requests. #for item in raw_data_list: requestQueue.put(item) @@ -1429,7 +1429,7 @@ def DownloadPackages(PackageName, PackageFile): ConnectThread.stopQueue() log.err("\nInterrupted by user. Exiting!\n") sys.exit(0) - + if args.bundle_file: log.msg("\nDownloaded data to %s\n" % (Str_BundleFile) ) else: @@ -1450,11 +1450,11 @@ def DownloadPackages(PackageName, PackageFile): def installer( args ): - - + + class InstallerClass(AptOfflineLib.Archiver, AptOfflineLib.Checksum, AptOfflineLib.FileMgmt, LockAPT): def __init__(self, args): - + # install opts self.Str_InstallArg = args.install self.Bool_TestWindows = args.install_simulate @@ -1464,19 +1464,19 @@ def __init__(self, args): self.Bool_SkipChangelog = args.skip_changelog self.tempdir = tempfile.gettempdir() self.Bool_StrictDebCheck = args.strict_deb_check - + if not os.access(self.tempdir, os.W_OK): log.err("Temporary path %s in not writable. Some functionality may fail\n") return False AptOfflineLib.Archiver.__init__(self) LockAPT.__init__(self, apt_lists_lock, apt_packages_lock) - + if MagicLib is False: log.err("Please ensure libmagic is installed\n") return False self.magicMIME = AptOfflineMagicLib.open(AptOfflineMagicLib.MAGIC_MIME_TYPE) - + if self.Str_InstallSrcPath is None: pidname = os.getpid() randomjunk = ''.join(chr(random.randint(97,122)) for x in range(5)) if guiBool else '' @@ -1485,33 +1485,33 @@ def __init__(self, args): srcDownloadsPath = os.path.join(self.tempdir , "apt-offline-src-downloads-" + str(pidname) + randomjunk ) os.mkdir(srcDownloadsPath) self.Str_InstallSrcPath = os.path.abspath(srcDownloadsPath) - + if not os.path.isdir(self.Str_InstallSrcPath): log.err("Not a folder.\n") return False - + if os.access(self.Str_InstallSrcPath, os.W_OK) is not True: log.err("%s is not writable.\n" % (self.Str_InstallSrcPath)) return False - + if self.Bool_StrictDebCheck: # Force copying of debs to apt_package_target_path, which should be the 'partial/' folder self.apt_package_path = apt_package_target_path else: self.apt_package_path = apt_package_final_path - + if self.Bool_TestWindows: pidname = os.getpid() tempdir = os.path.join(self.tempdir , "apt-package-target-path-" + str(pidname) ) log.verbose("apt-package-target-path is %s\n" % (tempdir) ) os.mkdir(tempdir) self.apt_package_path = os.path.abspath(tempdir) - + tempdir = os.path.join(self.tempdir , "apt-update-target-path-" + str(pidname) ) log.verbose("apt-update-target-path is %s\n" % (tempdir) ) os.mkdir(tempdir) self.apt_update_target_path = os.path.abspath(tempdir) - + tempdir = os.path.join(self.tempdir , "apt-update-final-path-" + str(pidname) ) log.verbose("apt-update-final-path is %s\n" % (tempdir) ) os.mkdir(tempdir) @@ -1519,7 +1519,7 @@ def __init__(self, args): else: self.apt_update_target_path = apt_update_target_path self.apt_update_final_path = apt_update_final_path - + try: if os.geteuid() != 0: log.err("EACCES: You need superuser privileges to execute this option\n") @@ -1528,15 +1528,15 @@ def __init__(self, args): log.err("EOPNOTSUPP: Unsupported platform: %s\n" % (platform.platform())) sys.exit(95) - + def verifyPayloadIntegrity(self, payload, checksum, checksumType): '''Verify the integrity of the payload against the checksum''' - + if self.HashMessageDigestAlgorithms(checksum, checksumType, payload): return True else: return False - + def cleanAptPartial(self, path): self.lockLists() @@ -1544,7 +1544,7 @@ def cleanAptPartial(self, path): eachPath = os.path.abspath(eachPath) os.unlink(eachPath) self.unlockLists() - + def display_options(self,dispType): log.msg( "(Y) Yes. Proceed with installation\n" ) log.msg( "(N) No, Abort.\n" ) @@ -1554,7 +1554,7 @@ def display_options(self,dispType): elif dispType == "Chlog": log.msg( "(C) Display changelog\n") log.msg( "(?) Display this help message.\n" ) - + def get_response(self): response = input( "What would you like to do next:\t (y, N, ?)" ) response = response.rstrip( "\r" ) @@ -1575,9 +1575,9 @@ def list_bugs(self, dictList): bug_num = each_bug.split( '{}' )[-2] bug_subject = dictList[each_bug] log.msg( "%s\t%s\t%s\n" % ( bug_num, pkg_name, bug_subject ) ) - + def magic_check_and_uncompress(self, archive_file=None, filename=None): - + self.magicMIME.load() retval = False if self.magicMIME.file( archive_file ) == "application/x-bzip2" or self.magicMIME.file( archive_file ) == "application/gzip" or self.magicMIME.file(archive_file) == "application/x-xz": @@ -1640,12 +1640,12 @@ def magic_check_and_uncompress(self, archive_file=None, filename=None): sys.exit( 1 ) else: log.err( "I couldn't understand file %s of type %s.\n" % ( filename, magicMIME.file( archive_file ) ) ) - + #INFO: Close the handle and conserve precious memory #self.magicMIME.close() if retval: #CHANGE: track progress - totalSize[0]+=1 + totalSize[0]+=1 if guiBool: log.msg("[%d/%d]" % (totalSize[0], totalSize[1])) #ENDCHANGE @@ -1653,10 +1653,10 @@ def magic_check_and_uncompress(self, archive_file=None, filename=None): def displayChangelog(self, dataType=None): '''Takes file or directory as input''' - + chlogFile = tempfile.NamedTemporaryFile() chlogPresent = False - + if os.path.isdir(dataType): for eachItem in os.listdir(dataType): eachItem = os.path.join(dataType, eachItem) @@ -1672,17 +1672,17 @@ def displayChangelog(self, dataType=None): chlogPresent = True else: return False - + if chlogPresent is False: log.verbose("No changelog available\n") response = 'y' - else: + else: chlogFile.seek(0) pydoc.pager(chlogFile.read().decode('utf-8')) - + self.display_options("Chlog") response = self.get_response() - + while True: if response == "?": self.display_options("Chlog") @@ -1698,18 +1698,18 @@ def displayChangelog(self, dataType=None): else: log.err("Aborting installation, on user request\n") sys.exit(1) - + def displayBugs(self, dataType=None): ''' Takes keywords "file" or "dir" as type input ''' - + if dataType is None: return False - + # Display the list of bugs self.list_bugs(bugs_number) self.display_options("BugReports") response = self.get_response() - + while True: if response == "?": self.display_options("BugReports") @@ -1717,14 +1717,14 @@ def displayBugs(self, dataType=None): elif response.startswith( 'y' ) or response.startswith( 'Y' ): if dataType == "file": for filename in zipBugFile.namelist(): - + #INFO: Take care of Src Pkgs found = False for item in list(SrcPkgDict.keys()): if filename in SrcPkgDict[item]: found = True break - + data = tempfile.NamedTemporaryFile() try: data.file.write( zipBugFile.read( filename ) ) @@ -1737,21 +1737,21 @@ def displayBugs(self, dataType=None): # The same zip archive, if unarchived with plain unix unizp, works file. # On the internet, there are many bug reports of Python's zipfile having certain bugs. # Hence we continue hoping to milk the possible payloads from the archive - + archive_file = data.name - + if found is True: # found is True. That means this is a src package shutil.copy2(archive_file, os.path.join(self.Str_InstallSrcPath, filename) ) log.msg("Installing src package file %s to %s.\n" % (filename, self.Str_InstallSrcPath) ) continue - - + + if self.Bool_TestWindows: log.verbose("In simulate mode. No locking required\n") elif self.lockPackages() is False: log.err("Couldn't acquire lock on %s\nIs another apt process running?\n" % (archive_file)) sys.exit(1) - + self.magic_check_and_uncompress( archive_file, filename ) if self.Bool_TestWindows: @@ -1798,17 +1798,17 @@ def displayBugs(self, dataType=None): else: log.err( 'Incorrect choice. Exiting\n' ) sys.exit( 1 ) - + def verifyAptFileIntegrity(self, FileList): self.AptSecure = APTVerifySigs(Simulate=InstallerInstance.Bool_TestWindows) self.lFileList = FileList self.lFileList.sort() - + self.lVerifiedWhitelist = [] self.checksumList = [] self.checksumHeader = "SHA256:" - + for localFile in self.lFileList: if localFile.endswith('.gpg'): log.verbose("%s\n" % (localFile) ) @@ -1817,7 +1817,7 @@ def verifyAptFileIntegrity(self, FileList): self.lVerifiedWhitelist.append(gpgFile) self.lVerifiedWhitelist.append(gpgFile.rstrip(".gpg")) log.verbose("%s is gpg clean\n" % (gpgFile.rstrip(".gpg"))) - + # Let's build a list of all checksums whose gpg signature was clean #INFO: We take the Release file and mark the start with keyword "SHA256Sum:" # and we keep track of the lines (' checksum') @@ -1834,7 +1834,7 @@ def verifyAptFileIntegrity(self, FileList): except: log.verbose("This error should be ignored") continue - + aptPkgFile = gpgFile.rstrip("Release.gpg") aptPkgFile = aptPkgFile[:-1] #Remove the trailing _ underscore aptPkgFile = aptPkgFile.split("/")[-1] @@ -1854,16 +1854,16 @@ def verifyAptFileIntegrity(self, FileList): self.lVerifiedWhitelist.append(localFile) else: log.verbose("localFile %s integrity doesn't match to checksum %s\n" % (localFile, checksumItem[0])) - + return self.lVerifiedWhitelist - + def installVerifiedList(self, verifiedWhiteList, masterList): #INFO: Above, when verifying the integritiy of the Release Files, we built a list of the names of aptPkgFiles # which were gpg clean. # And in lFileList, we have the full list of aptPkgFiles that have been downloaded - # Below, we loop through both the list's items and match out gpg clean list item's name to be present in + # Below, we loop through both the list's items and match out gpg clean list item's name to be present in # lFileList's items - + for whitelist_item in verifiedWhiteList: for final_item in masterList: if whitelist_item == final_item: @@ -1887,7 +1887,7 @@ def listdir_fullpath(self, d): totalSize[1] = len(zipBugFile.namelist()) totalSize[0] = 0 #ENDCHANGE - + #INFO: Handle source packages with care. # Build a dict and populate its files based on details in .dsc SrcPkgDict = {} @@ -1905,18 +1905,18 @@ def listdir_fullpath(self, d): if SrcPkgIdentifier.startswith('Files:'): marker = True continue - + if SrcPkgIdentifier.startswith('\n'): marker = False continue - + if marker is True: SrcPkgData = SrcPkgIdentifier.split(' ')[3].rstrip("\n") if SrcPkgData in SrcPkgDict[SrcPkgName]: break else: SrcPkgDict[SrcPkgName].append(SrcPkgData) - + SrcPkgDict[SrcPkgName].append(filename) temp.file.close() @@ -1925,7 +1925,7 @@ def listdir_fullpath(self, d): log.verbose("Skipping display of changelog as requested\n") else: InstallerInstance.displayChangelog(InstallerInstance.Str_InstallArg) - + bugs_number = {} if InstallerInstance.Bool_SkipBugReports: log.verbose("Skipping bug report check as requested") @@ -1944,7 +1944,7 @@ def listdir_fullpath(self, d): break bugs_number[filename] = subject temp.file.close() - + log.verbose(str(bugs_number) + "\n") if bugs_number: InstallerInstance.displayBugs(dataType="file") @@ -1961,19 +1961,19 @@ def listdir_fullpath(self, d): if filename in SrcPkgDict[item]: found = True break - + tempZipFile = os.path.join(tempDir, filename) data = open(tempZipFile, 'wb') data.write( zipBugFile.read( filename ) ) data.flush() archive_file = tempZipFile - + if found is True: #We are src packages. And don't need a lock on the APT Database shutil.copy2(archive_file, os.path.join(InstallerInstance.Str_InstallSrcPath, filename) ) log.msg("Installing src package file %s to %s.\n" % (filename, InstallerInstance.Str_InstallSrcPath) ) continue FileList.append(archive_file) - + #INFO: Integrity of the .deb packages is delegated to apt on the target system # We just copy the files to partial/ and apt will only use it if it meets all integrity checks if filename.endswith(".deb"): @@ -1990,18 +1990,18 @@ def listdir_fullpath(self, d): if not InstallerInstance.installVerifiedList(verifiedList, FileList): log.err("Failed to verify File Checksum integrity of APT files\n") sys.exit(1) - + elif os.path.isdir(installPath): def DirInstallPackages(InstallDirPath): for eachfile in os.listdir( InstallDirPath ): filename = eachfile FullFileName = os.path.abspath(os.path.join(InstallDirPath, eachfile) ) - + if os.path.isdir(FullFileName): log.verbose("Skipping!! %s is a directory\n" % (FullFileName)) continue - + #INFO: Take care of Src Pkgs found = False for item in list(SrcPkgDict.keys()): @@ -2012,12 +2012,12 @@ def DirInstallPackages(InstallDirPath): shutil.copy2(FullFileName, InstallerInstance.Str_InstallSrcPath) log.msg("Installing src package file %s to %s.\n" % (filename, InstallerInstance.Str_InstallSrcPath) ) continue - + #INFO: Take care of all remaining deb packages if eachfile.endswith(".deb"): InstallerInstance.magic_check_and_uncompress( FullFileName, filename ) return True - + #TODO: Needs refactoring with the previous common code SrcPkgDict = {} for filename in os.listdir( installPath ): @@ -2031,11 +2031,11 @@ def DirInstallPackages(InstallDirPath): if SrcPkgIdentifier.startswith('Files:'): marker = True continue - + if SrcPkgIdentifier.startswith('\n'): marker = False continue - + if marker is True: SrcPkgData = SrcPkgIdentifier.split(' ')[3].rstrip("\n") if SrcPkgData in SrcPkgDict[SrcPkgName]: @@ -2050,8 +2050,8 @@ def DirInstallPackages(InstallDirPath): log.verbose("Skipping display of changelog as requested\n") else: InstallerInstance.displayChangelog(InstallerInstance.Str_InstallArg) - - bugs_number = {} + + bugs_number = {} if InstallerInstance.Bool_SkipBugReports: log.verbose("Skipping bug report check as requested") else: @@ -2085,13 +2085,13 @@ def DirInstallPackages(InstallDirPath): if not InstallerInstance.installVerifiedList(verifiedList, lFileList): log.err("Failed to verify File Checksum integrity of APT files\n") sys.exit(1) - + # Call for processing the debs and source package metadata DirInstallPackages(installPath) else: log.err("Invalid path argument specified: %s\n" % (installPath)) sys.exit(1) - + def setter(args): #log.verbose(str(args)) @@ -2106,20 +2106,20 @@ def setter(args): Bool_SrcBuildDep = args.src_build_dep Bool_TestWindows = args.set_simulate Bool_Changelog = args.generate_changelog - + if Bool_SetUpdate is False and Bool_SetUpgrade is False and List_SetInstallPackages is None \ and List_SetInstallSrcPackages is None: Default_Operation = True else: Default_Operation = False - + #INFO: Don't run the default behavior, of SetUpdate and SetUpgrade, if the # user requests only for Package Installs if Default_Operation: Bool_SetUpdate = True Bool_SetUpgrade = True - - + + #FIXME: We'll use python-apt library to make it cleaner. # For now, we need to set markers using shell variables. if os.path.isfile(Str_SetArg): @@ -2127,11 +2127,11 @@ def setter(args): os.unlink(Str_SetArg) except OSError: log.err("Cannot remove file %s.\n" % (Str_SetArg) ) - - + + # Pick apt backend based on what option the user chose AptInst = AptManip(Str_SetArg, Simulate=Bool_TestWindows, AptType=args.apt_backend) - + if Bool_SetUpdate: if platform.system() in supported_platforms: if not Bool_TestWindows and os.geteuid() != 0: @@ -2142,7 +2142,7 @@ def setter(args): else: log.err( "This argument is supported only on Unix like systems with apt installed\n" ) sys.exit( 1 ) - + if Bool_SetUpgrade: if platform.system() in supported_platforms: if not Bool_TestWindows and os.geteuid() != 0: @@ -2160,25 +2160,25 @@ def setter(args): else: log.err( "This argument is supported only on Unix like systems with apt installed\n" ) sys.exit( 1 ) - + if List_SetInstallPackages != None and List_SetInstallPackages != []: if platform.system() in supported_platforms: if not Bool_TestWindows and os.geteuid() != 0: log.err( "This option requires super-user privileges. Execute as root or use sudo/su\n" ) sys.exit(1) - + AptInst.InstallPackages(List_SetInstallPackages, Str_SetInstallRelease) else: log.err( "This argument is supported only on Unix like systems with apt installed\n" ) sys.exit( 1 ) - + if List_SetInstallSrcPackages != None and List_SetInstallSrcPackages != []: if platform.system() in supported_platforms: AptInst.InstallSrcPackages(List_SetInstallSrcPackages, Str_SetInstallRelease, Bool_SrcBuildDep) else: log.err( "This argument is supported only on Unix like systems with apt installed\n" ) sys.exit( 1 ) - + if Bool_Changelog: if not PythonApt: #INFO: No crude ways. Will only work with python-apt @@ -2188,12 +2188,12 @@ def setter(args): log.verbose("Initializing apt cache\n") aptCache = apt.Cache() aptCache.open() - + try: sigFile = open(Str_SetArg, 'r+') except Exception: log.err(traceback.format_exc()) - + for eachLine in sigFile.readlines(): (pkgUrl, pkgFile, pkgSize, pkgChecksum) = stripper(eachLine) pkgName = pkgFile.split("_")[0] @@ -2211,7 +2211,7 @@ def setter(args): except Exception: log.err(traceback.format_exc()) raise - + #INFO: '/' will be the delimiter log.verbose("Writing to Changelog, pkgName: %s, pkgInstalledVersion %s\n" % (pkgName, pkgInstalledVersion)) sigFile.writelines("Changelog/%s/%s\n" % (pkgName, pkgInstalledVersion)) @@ -2220,115 +2220,115 @@ def setter(args): def main(): '''Here we basically do the sanity checks, some validations and then accordingly call the corresponding functions. - + Contains most of the variables that are required by the application to run. Also does command-line option parsing and variable validation.''' - - # INFO: One way to handle global options in argparse so that they are available to + + # INFO: One way to handle global options in argparse so that they are available to # subparsers also - + # Global options global_options = argparse.ArgumentParser(add_help=False) global_options.add_argument("--verbose", dest="verbose", help="Enable verbose messages", action="store_true" ) - + if float(argparse.__version__) >= 1.1: parser = argparse.ArgumentParser( prog=app_name, description="Offline APT Package Manager" + ' - ' + version, epilog=myCopyright + " - " + terminal_license, parents=[global_options]) parser.add_argument("-v", "--version", action='version', version=version) else: - # Remain backward compatible with older argparse versions + # Remain backward compatible with older argparse versions parser = argparse.ArgumentParser( prog=app_name, version=app_name + " - " + version, description="Offline APT Package Manager", epilog=myCopyright + " - " + terminal_license, parents=[global_options]) - + # We need subparsers for set/get/install subparsers = parser.add_subparsers() - + # SET command options # parser_set = subparsers.add_parser('set', parents=[global_options]) parser_set.set_defaults(func=setter) - + parser_set.add_argument('set', help="Generate a signature file", action="store", type=str, metavar="apt-offline.sig", default="apt-offline.sig") - + parser_set.add_argument("--simulate", dest="set_simulate", help="Just simulate. Very helpful when debugging", action="store_true" ) #TODO: Handle nargs here. parser_set.add_argument("--install-packages", dest="set_install_packages", help="Packages that need to be installed", action="store", type=str, nargs='*', metavar="PKG") - + parser_set.add_argument("--install-src-packages", dest="set_install_src_packages", help="Source Packages that need to be installed", action="store", type=str, nargs='*', metavar="SOURCE PKG") - + parser_set.add_argument("--src-build-dep", dest="src_build_dep", help="Install Build Dependency packages for requested source packages", action="store_true") - + parser_set.add_argument("--release", dest="set_install_release", help="Release target to install packages from", action="store", type=str, metavar="release_name" ) - + parser_set.add_argument("--update", dest="set_update", help="Generate Signature to update APT Database", action="store_true") - + parser_set.add_argument("--upgrade", dest="set_upgrade", help="Generate Signature of packages to be upgraded", action="store_true") - + parser_set.add_argument("--upgrade-type", dest="upgrade_type", help="Type of upgrade to do. Use one of upgrade, dist-upgrade, dselect-ugprade", action="store", type=str, metavar="upgrade", default="upgrade") - + parser_set.add_argument("--generate-changelog", dest="generate_changelog", help="Generate changelog of the version to be downloaded", action="store_true") - + parser_set.add_argument("--apt-backend", dest="apt_backend", help="APT backend to use. One of: apt, apt-get, python-apt", action="store", type=str, metavar="apt-get", default="apt-get") - + # GET command options parser_get = subparsers.add_parser('get', parents=[global_options]) - + #INFO: When get option is called, call the fetcher() function parser_get.set_defaults(func=fetcher) - + parser_get.add_argument('get', help="Get apt-offline data", action="store", type=str, metavar="apt-offline.sig", default="apt-offline.sig") - + parser_get.add_argument("--socket-timeout", dest="socket_timeout", help="Set Socket Timeout", action="store", type=int, metavar="30", default=30) - + parser_get.add_argument("-d", "--download-dir", dest="download_dir", help="Folder path to save files to", action="store", type=str, metavar="apt-downloads") - + parser_get.add_argument("-s", "--cache-dir", dest="cache_dir", help="Cache folder to search for", action="store", type=str, metavar="/var/cache/apt/archives/") - + parser_get.add_argument("--no-checksum", dest="disable_md5check", help="Do not validate checksum of downloaded files", action="store_true") - + parser_get.add_argument("-t", "--threads", dest="num_of_threads", help="Number of threads to spawn", action="store", type=int, metavar="1", default=1 ) - + parser_get.add_argument("--bundle", dest="bundle_file", help="Bundle output data to a file", action="store", type=str, metavar="apt-offline-bundle.zip") - + parser_get.add_argument("--bug-reports", dest="deb_bugs", help="Fetch bug reports from the BTS", action="store_true" ) - + parser_get.add_argument("--proxy-host", dest="proxy_host", help="Proxy Host to use", type=str, default=None) - + parser_get.add_argument("--proxy-port", dest="proxy_port", help="Proxy port number to use", type=int, default=None) - + parser_get.add_argument("--https-cert-file", dest="https_cert_file", help="Certificate file for https client authentication", type=str, default=None) - + parser_get.add_argument("--https-key-file", dest="https_key_file", help="Certificate key for https client authentication", type=str, default=None) @@ -2337,11 +2337,11 @@ def main(): parser_get.add_argument("--disable-cert-check", dest="disable_cert_check", help="Disable Certificate check on https connections", action="store_true") - + # INSTALL command options parser_install = subparsers.add_parser('install', parents=[global_options]) parser_install.set_defaults(func=installer) - + parser_install.add_argument('install', help="Install apt-offline data, a bundle file or a directory", action="store", type=str, metavar="apt-offline-download.zip | apt-offline-download/") @@ -2351,32 +2351,32 @@ def main(): parser_install.add_argument("--install-src-path", dest="install_src_path", help="Install src packages to specified path.", default=None) - + parser_install.add_argument("--skip-bug-reports", dest="skip_bug_reports", help="Skip the bug report check", action="store_true") - + parser_install.add_argument("--skip-changelog", dest="skip_changelog", help="Skip display of changelog", action="store_true") - + parser_install.add_argument("--allow-unauthenticated", dest="allow_unauthenticated", help="Ignore apt gpg signatures mismatch", action="store_true") - + parser_install.add_argument("--strict-deb-check", dest="strict_deb_check", help="Perform strict checksum validaton for downloaded .deb files", action="store_true") if len(sys.argv) <= 1: sys.argv.append('--help') - + args = parser.parse_args() try: # Global opts Bool_Verbose = args.verbose - + global log log = AptOfflineLib.Log( Bool_Verbose, lock=True ) log.verbose(str(args) + "\n") args.func(args) - + except KeyboardInterrupt: log.err("\nInterrupted by user. Exiting!\n") - sys.exit(0) + sys.exit(0) diff --git a/apt_offline_core/AptOfflineLib.py b/apt_offline_core/AptOfflineLib.py index a14bab8..71fc21c 100644 --- a/apt_offline_core/AptOfflineLib.py +++ b/apt_offline_core/AptOfflineLib.py @@ -42,13 +42,13 @@ modLZMA = False sys.stderr.write("WARN: lzma module unavailable\n") sys.stderr.write("WARN: Please install Python lzma module for APT lzma backend\n") - + WindowColor = True try: import WConio except ImportError: WindowColor = False - + from array import array import signal @@ -59,7 +59,7 @@ import termios except ImportError: pass - + #INFO: Python 2.5 introduces hashlib. # This module supports many hash/digest algorithms @@ -69,12 +69,12 @@ import hashlib except ImportError: Python_2_5 = False - - + + class Checksum: - + def HashMessageDigestAlgorithms( self, checksum, HashType, checksumFile ): - + try: data = open( checksumFile, 'rb' ) if HashType == "sha256": @@ -85,34 +85,34 @@ def HashMessageDigestAlgorithms( self, checksum, HashType, checksumFile ): except IOError: return False data.close() - + if Hash == checksum: return True return False - + def sha256( self, data ): sha256 = hashlib.sha256() sha256.update( data.read() ) return sha256.hexdigest() - + def md5( self, data ): md5hash = hashlib.md5() md5hash.update( data.read() ) - return md5hash.hexdigest() - + return md5hash.hexdigest() + def CheckHashDigest( self, checksumFile, checksum ): '''Return Bool against file and its checksum''' - + checksumType = checksum.split(":")[0] checksumType = checksumType.lower() checksum = checksum.split( ":" )[1] return self.HashMessageDigestAlgorithms( checksum, checksumType, checksumFile ) - + class Log: ''' To display color on Windows CMD, we have optional dependency on WConio - + WConio can provide simple coloring mechanism for Microsoft Windows console Color Codes: Black = 0 @@ -121,22 +121,22 @@ class Log: White = 15 Light Red = 12 Light Cyan = 11 - + #FIXME: The Windows Command Interpreter does support colors natively. I think that support has been since Win2k. That's all for Windows Command Interpreter. - + As for ANSI Compliant Terminals (which most Linux/Unix Terminals are.)..... I think the ANSI Color Codes would be good enough for my requirements to print colored text on an ANSI compliant terminal. The ANSI Terminal Specification gives programs the ability to change the text color or background color. An ansi code begins with the ESC character [^ (ascii 27) followed by a number (or 2 or more separated by a semicolon) and a letter. - + In the case of colour codes, the trailing letter is "m"... - + So as an example, we have ESC[31m ... this will change the foreground colour to red. - + The codes are as follows: - + For Foreground Colors 1m - Hicolour (bold) mode 4m - Underline (doesn't seem to work) @@ -150,9 +150,9 @@ class Log: 35m - Magenta 36m - Cyan 37m - White - + For Background Colors - + 40m - Change Background to Black 41m - Red 42m - Green @@ -161,24 +161,24 @@ class Log: 45m - Magenta 46m - Cyan 47m - White - + 7m - Change to Black text on a White bg 0m - Turn off all attributes. - + Now for example, say I wanted blinking, yellow text on a magenta background... I'd type ESC[45;33;5m ''' - + def __init__( self, verbose, lock=None ): self.VERBOSE = bool( verbose ) self.color_syntax = '\033[1;' - + if lock is True: self.DispLock = threading.Lock() self.lock = True else: self.DispLock = False self.lock = False - + if os.name == 'posix': self.platform = 'posix' self.color = {'Red': '31m', 'Black': '30m', @@ -187,10 +187,10 @@ def __init__( self, verbose, lock=None ): 'Cyan': '36m', 'White': '37m', 'Bold_Text': '1m', 'Underline': '4m', 'Blink': '5m', 'SwitchOffAttributes': '0m'} - + elif os.name in ['nt', 'dos']: self.platform = None - + if WindowColor is True: self.platform = 'microsoft' self.color = {'Red': 4, 'Black': 0, @@ -199,7 +199,7 @@ def __init__( self, verbose, lock=None ): else: self.platform = None self.color = None - + def set_color( self, color ): '''Check the platform and set the color''' if self.platform == 'posix': @@ -207,17 +207,17 @@ def set_color( self, color ): sys.stderr.write( self.color_syntax + self.color[color] ) elif self.platform == 'microsoft': WConio.textcolor( self.color[color] ) - + def msg( self, msg ): '''Print general messages. If locking is available use them.''' if self.lock: self.DispLock.acquire() - + #self.set_color( 'White' ) sys.stdout.write( msg ) sys.stdout.flush() #self.set_color( 'SwitchOffAttributes' ) - + if self.lock: self.DispLock.release() @@ -225,56 +225,56 @@ def warn( self, msg ): '''Print messages with a warningr. If locking is available use them.''' if self.lock: self.DispLock.acquire() - + self.set_color( 'Magenta' ) sys.stderr.write( "WARN: " + msg ) sys.stderr.flush() self.set_color( 'SwitchOffAttributes' ) - + if self.lock: self.DispLock.release() - + def err( self, msg ): '''Print messages with an error. If locking is available use them.''' if self.lock: self.DispLock.acquire() - + self.set_color( 'Red' ) sys.stderr.write( "ERROR: " + msg ) sys.stderr.flush() self.set_color( 'SwitchOffAttributes' ) - + if self.lock: self.DispLock.release() - + def success( self, msg ): '''Print messages with a success. If locking is available use them.''' if self.lock: self.DispLock.acquire() - + self.set_color( 'Green' ) sys.stdout.write( msg ) sys.stdout.flush() self.set_color( 'SwitchOffAttributes' ) - + if self.lock: self.DispLock.release() - + # For the rest, we need to check the options also def verbose( self, msg ): '''Print verbose messages. If locking is available use them.''' if self.lock: self.DispLock.acquire() - + if self.VERBOSE is True: self.set_color( 'Cyan' ) sys.stdout.write( "VERBOSE: " + msg ) sys.stdout.flush() self.set_color( 'SwitchOffAttributes' ) - + if self.lock: self.DispLock.release() - + def calcSize( self, size ): ''' Takes number of kB and returns a string of proper size. Like if > 1024, return a megabyte ''' @@ -288,7 +288,7 @@ def calcSize( self, size ): class ProgressBar( object ): - + def __init__( self, minValue=0, maxValue=0, width=None, total_items=None, fd=sys.stderr ): #width does NOT include the two places for [] markers self.min = minValue @@ -298,7 +298,7 @@ def __init__( self, minValue=0, maxValue=0, width=None, total_items=None, fd=sys self.span = 1 self.fd = fd self.signal_set = False - + if width is None: try: self.handle_resize( None, None ) @@ -306,53 +306,53 @@ def __init__( self, minValue=0, maxValue=0, width=None, total_items=None, fd=sys self.signal_set = True except: self.width = 79 #The standard - + else: self.width = width - + self.value = self.min - + if total_items is None or total_items <= 0: self.items = 0 #count of items being tracked self.items_update = True else: self.items = total_items self.items_update = False - + self.complete = 0 - + def handle_resize( self, signum, frame ): h, w = array( 'h', ioctl( self.fd, termios.TIOCGWINSZ, '\0' * 8 ) )[:2] self.width = w - + def updateValue( self, newValue ): #require caller to supply a value! newValue is the increment from last call self.value = max( self.min, min( self.max, self.value + newValue ) ) self.display() - + def completed( self ): self.complete = self.complete + 1 - + if self.signal_set: signal.signal( signal.SIGWINCH, signal.SIG_DFL ) self.display() - + def addItem( self, maxValue ): self.max = self.max + maxValue self.span = float( self.max - self.min ) if self.items_update is True: self.items = self.items + 1 self.display() - + def display( self ): sys.stdout.write("\r%3s / %3s items: %s\r" % ( self.complete, self.items, str( self ) )) - + def __str__( self ): #compute display fraction percentFilled = ( ( self.value - self.min ) / self.span ) widthFilled = int( self.width * percentFilled + 0.5 ) return ( "[" + "#"*widthFilled + " " * ( self.width - widthFilled ) + "]" + " %5.1f%% of %s" % ( percentFilled * 100.0, self.__numStr__( self.max / 1024 ) ) ) - + def __numStr__( self, size ): if size > 1024: size = size / 1024 @@ -370,7 +370,7 @@ def __init__(self, message): class AptOfflineLibShutilError(Exception): def __init__(self, message): Exception.__init__(self, message) - + class Archiver: def __init__( self, lock=None ): if lock is None or lock != 1: @@ -383,7 +383,7 @@ def __init__( self, lock=None ): # for the same src package # https://github.com/rickysarraf/apt-offline/issues/44 self.file_possibly_deleted = False - + def TarGzipBZ2_Uncompress( self, SourceFileHandle, TargetFileHandle ): try: TargetFileHandle.write( SourceFileHandle.read() ) @@ -392,7 +392,7 @@ def TarGzipBZ2_Uncompress( self, SourceFileHandle, TargetFileHandle ): except IOError: #TODO: What constitutes an "IOError: invalid data stream" ??? # Couldn't find much from the docs. Needs to be investigated. - + # Answer: # A BZ2 file corruption is seen during file creation only. # Perhaps it has to do with the bad network, loss of packets et cetera @@ -400,7 +400,7 @@ def TarGzipBZ2_Uncompress( self, SourceFileHandle, TargetFileHandle ): # downloaded in damaged form. return False return True - + def compress_the_file( self, zip_file_name, files_to_compress ): '''Condenses all the files into one single file for easy transfer''' @@ -470,12 +470,12 @@ def decompress_the_file( self, archive_file, target_file, archive_type ): return False else: return False - + try: write_to = open ( target_file, 'wb' ) except IOError: return False - + if self.TarGzipBZ2_Uncompress( read_from, write_to ) != True: #INFO: Return False for the stream that failed. return False @@ -508,19 +508,19 @@ def decompress_the_file( self, archive_file, target_file, archive_type ): return False class FileMgmt( object ): - + def __init__( self ): self.duplicate_files = [] - def files( self, root ): - for path, folders, files in os.walk( root ): - for f in files: - yield path, f + def files( self, root ): + for path, folders, files in os.walk( root ): + for f in files: + yield path, f def find_first_match( self, cache_dir=None, filename=None ): '''Return the full path of the filename if a match is found Else Return False''' - + if cache_dir is None: return False elif filename is None: @@ -528,11 +528,11 @@ def find_first_match( self, cache_dir=None, filename=None ): elif os.path.isdir( cache_dir ) is False: return False else: - for path, f in self.files( cache_dir ): + for path, f in self.files( cache_dir ): if f == filename: return os.path.join( path, f ) return False - + def rename_file( self, orig, new ): '''Rename file from orig to new''' if not os.path.isfile( orig ): @@ -561,7 +561,7 @@ def move_file( self, src, dest ): # So we propagate the failure and warn out loud in the caller raise AptOfflineLibShutilError("Possbile duplicate file %s already present in %s\n" % (src, dest)) return True - + def copy_file(self, src, dest): '''Copy file from src to dest''' try: @@ -574,7 +574,7 @@ def copy_file(self, src, dest): return True SFH = open(srcFile, 'rb') DFH = open(destFile, 'wb') - + DFH.write(SFH.read()) DFH.flush() DFH.close() @@ -582,7 +582,7 @@ def copy_file(self, src, dest): except IOError: SFH.close() return False - + def move_folder( self, src, dest ): '''Move folder from src to dest.''' if os.path.isdir( dest ): @@ -594,11 +594,11 @@ def move_folder( self, src, dest ): def find_dup( self, dir ): '''"dir" will be the directory within which duplicate files are searched Returns a list with the duplicates''' - + #TODO: This is buggy currently - + for xpath, yfile in dir: - + for path, file in dir: if file == yfile: if not ( xpath + "/" + yfile == path + "/" + file ): @@ -607,7 +607,7 @@ def find_dup( self, dir ): else: self.duplicate_files += [ [xpath + "/" + yfile, path + "/" + file] ] #self.duplicate_files = set(self.duplicate_files) - + len = self.duplicate_files.__len__() print(len) for x in range( len ): @@ -625,7 +625,7 @@ def find_dup( self, dir ): self.duplicate_files.pop( number ) num -= 0 number += 1 - + return self.duplicate_files class MyThread( threading.Thread ): @@ -639,7 +639,7 @@ def __init__( self, WorkerFunction, requestQueue=None, responseQueue=None, NumOf self.guiTerminateSignal=False self.WorkerFunction = WorkerFunction self.thread_pool = [ - threading.Thread( + threading.Thread( target=self.run, args=() ) @@ -647,20 +647,20 @@ def __init__( self, WorkerFunction, requestQueue=None, responseQueue=None, NumOf ] for thread in self.thread_pool: thread.guiTerminateSignal=False - + def startThreads( self ): for thread in self.thread_pool: thread.start() - + def stopThreads( self ): '''Shut down the threads after all requests end. (Put one None "sentinel" for each thread.)''' for thread in self.thread_pool: self.requestQueue.put( None ) - + def populateQueue( self, item ): self.requestQueue.put( item ) - + def stopQueue( self, timeout=0 ): '''Don't end the program prematurely. (Note that because Queue.get() is blocking by @@ -671,14 +671,14 @@ def stopQueue( self, timeout=0 ): if timeout !=0: self.threads_finished = 0 # recount finished threads if gui handler needs for thread in self.thread_pool: - if timeout==0: + if timeout==0: thread.join() else: # let threads also lookout for gui signals of cancellation thread.join(timeout) if not thread.is_alive(): self.threads_finished += 1 - + def run( self, item=None): while True: if threading.currentThread().guiTerminateSignal: @@ -686,12 +686,12 @@ def run( self, item=None): break if self.requestQueue is not None: item = self.requestQueue.get() - + if item is None: break - + thread_name = threading.currentThread().getName() - + if self.responseQueue is not None: self.responseQueue.put( self.WorkerFunction( item, thread_name ) ) exit_status = self.responseQueue.get() diff --git a/apt_offline_gui/AptOfflineQtAbout.py b/apt_offline_gui/AptOfflineQtAbout.py index da44cda..56e0b88 100644 --- a/apt_offline_gui/AptOfflineQtAbout.py +++ b/apt_offline_gui/AptOfflineQtAbout.py @@ -11,13 +11,13 @@ def __init__(self, parent=None): self.ui = Ui_AboutAptOffline() self.ui.setupUi(self) self.setupLicense() - + def setupLicense(self): - ''' LICENSE is looked for in - + ''' LICENSE is looked for in - 1. Current directory (dev / possibly windows) 2. /usr/local/share/doc/apt-offline (source install) 3. /usr/share/doc/apt-offline (package install) - + TODO: to resolve location on window ''' filename = 'LICENSE' diff --git a/apt_offline_gui/AptOfflineQtAbout.ui b/apt_offline_gui/AptOfflineQtAbout.ui index 19173f4..dec6309 100644 --- a/apt_offline_gui/AptOfflineQtAbout.ui +++ b/apt_offline_gui/AptOfflineQtAbout.ui @@ -100,7 +100,7 @@ - apt-offline can fully update/upgrade your disconnected Debian box without the need of connecting it to the network. + apt-offline can fully update/upgrade your disconnected Debian box without the need of connecting it to the network. This is a Graphical User Interface which exposes the functionality of apt-offline. diff --git a/apt_offline_gui/AptOfflineQtCommon.py b/apt_offline_gui/AptOfflineQtCommon.py index a086e3f..f767849 100644 --- a/apt_offline_gui/AptOfflineQtCommon.py +++ b/apt_offline_gui/AptOfflineQtCommon.py @@ -23,5 +23,5 @@ def updateInto(myobject,text): if ("Completed" in text): text = style(text,'green_fin') - + myobject.append (text) \ No newline at end of file diff --git a/apt_offline_gui/AptOfflineQtCreateProfile.py b/apt_offline_gui/AptOfflineQtCreateProfile.py index 034e186..1ab9d1b 100644 --- a/apt_offline_gui/AptOfflineQtCreateProfile.py +++ b/apt_offline_gui/AptOfflineQtCreateProfile.py @@ -13,25 +13,25 @@ def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.ui = Ui_CreateProfile() self.ui.setupUi(self) - + # Connect the clicked signal of the Browse button to it's slot #QtCore.QObject.connect(self.ui.browseFilePathButton, QtCore.SIGNAL("clicked()"), # self.popupDirectoryDialog ) self.ui.browseFilePathButton.clicked.connect(self.popupDirectoryDialog) - + # Connect the clicked signal of the Save to it's Slot - accept #QtCore.QObject.connect(self.ui.createProfileButton, QtCore.SIGNAL("clicked()"), # self.CreateProfile ) self.ui.createProfileButton.clicked.connect(self.CreateProfile) - + # Connect the clicked signal of the Cancel to it's Slot - reject #QtCore.QObject.connect(self.ui.cancelButton, QtCore.SIGNAL("clicked()"), # self.reject ) self.ui.cancelButton.clicked.connect(self.reject) - + # Disable or Enable the Package List field #QtCore.QObject.connect(self.ui.installPackagesCheckBox, QtCore.SIGNAL("toggled(bool)"), # self.PackageListFieldStatus ) @@ -75,16 +75,16 @@ def SrcPackageListFieldStatus(self): self.ui.targetReleaseCheckBox.setEnabled(self.isFieldChecked) self.ui.upgradeTaskComboBox.setEnabled(self.isFieldChecked) - + def PackageListFieldStatus(self): # If Install Packages Box is selected self.isFieldChecked = self.ui.installPackagesCheckBox.isChecked() self.ui.packageList.setEnabled(self.isFieldChecked) - + self.ui.targetReleaseCheckBox.setEnabled(self.isFieldChecked) self.ui.generateChangelog.setEnabled(self.isFieldChecked) self.ui.upgradeTaskComboBox.setEnabled(self.isFieldChecked) - + def CreateProfile(self): # Is the Update requested self.updateChecked = self.ui.updateCheckBox.isChecked() @@ -92,19 +92,19 @@ def CreateProfile(self): self.upgradeChecked = self.ui.upgradePackagesCheckBox.isChecked() # Is Install Requested self.installChecked = self.ui.installPackagesCheckBox.isChecked() - + self.installSrcChecked = self.ui.installSrcPackagesCheckBox.isChecked() - + self.chlogChecked = self.ui.generateChangelog.isChecked() self.aptBackend = self.ui.aptBackendComboBox.currentText() self.upgradeType = self.ui.upgradeTaskComboBox.currentText() self.releaseBtnChecked = self.ui.targetReleaseCheckBox.isChecked() - + # Clear the consoleOutputHolder self.ui.consoleOutputHolder.setText("") - + self.filepath = str(self.ui.profileFilePath.text()) - + if os.path.exists(os.path.dirname(self.filepath)) == False: if (len(self.filepath) == 0): self.ui.consoleOutputHolder.setText ( \ @@ -113,30 +113,30 @@ def CreateProfile(self): self.ui.consoleOutputHolder.setText ( \ guicommon.style("Could not access %s" % self.filepath,'red')) return - + # If at least one is requested if self.updateChecked or self.upgradeChecked or self.installChecked or self.installSrcChecked: if self.installChecked: self.packageList = str(self.ui.packageList.text()).split(",") else: self.packageList = None - + if self.installSrcChecked: self.srcPackageList = str(self.ui.srcPackageList.text()).split(",") self.srcBuildDeps = self.ui.srcBuildDeps else: self.srcPackageList = None self.srcBuildDeps = False - + if self.releaseBtnChecked: self.release = str(self.ui.targetReleaseTextInput.text()) else: self.release = None - + # setup i/o redirects before call sys.stdout = self sys.stderr = self - + args = SetterArgs(filename=self.filepath, update=self.updateChecked, upgrade=self.upgradeChecked, install_packages=self.packageList, \ install_src_packages=self.srcPackageList, src_build_dep=self.srcBuildDeps, changelog=self.chlogChecked, \ release=self.release, apt_backend=self.aptBackend, simulate=False) @@ -150,8 +150,8 @@ def CreateProfile(self): self.ui.cancelButton.setIcon(QtGui.QIcon()) else: pass - - + + def popupDirectoryDialog(self): # Popup a Directory selection box signatureFilePath = os.path.join (os.path.expanduser("~"), "/Desktop/"+"apt-offline.sig") diff --git a/apt_offline_gui/AptOfflineQtFetch.py b/apt_offline_gui/AptOfflineQtFetch.py index 9e792f0..52d3aa4 100644 --- a/apt_offline_gui/AptOfflineQtFetch.py +++ b/apt_offline_gui/AptOfflineQtFetch.py @@ -16,19 +16,19 @@ class Worker(QtCore.QThread): status = QtCore.pyqtSignal(str) finished = QtCore.pyqtSignal() terminated = QtCore.pyqtSignal() - + def __init__(self, parent = None): QtCore.QThread.__init__(self, parent) self.parent = parent self.exiting = False - + #INFO: Qt5 Signal and Slots self.output.emit('') self.progress.emit('','') self.status.emit('') self.finished.emit() self.terminated.emit() - + def __del__(self): self.exiting = True self.wait() @@ -36,7 +36,7 @@ def __del__(self): def run(self): # setup i/o redirects before call sys.stdout = self - sys.stderr = self + sys.stderr = self apt_offline_core.AptOfflineCoreLib.fetcher(self.args) def setArgs (self,args): @@ -48,7 +48,7 @@ def write(self, text): if apt_offline_core.AptOfflineCoreLib.guiTerminateSignal: # ^ so artificial, the threads still remain frozen in time I suppose return - + if ("MSG_START" in text): self.status.emit("Fetching missing meta data...") elif ("MSG_END" in text): @@ -78,25 +78,25 @@ def quit(self): class AptOfflineQtFetch(QtWidgets.QDialog): - + def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.ui = Ui_AptOfflineQtFetch() self.ui.setupUi(self) self.advancedOptionsDialog = AptOfflineQtFetchOptions() - + # Connect the clicked signal of the Signature File Browse button to it's slot self.ui.browseFilePathButton.clicked.connect(self.popupDirectoryDialog) - + # Connect the clicked signal of the Zip File Browse button to it's slot self.ui.browseZipFileButton.clicked.connect(self.popupZipFileDialog) - + # Connect the clicked signal of the Save to it's Slot - accept self.ui.startDownloadButton.clicked.connect(self.StartDownload) - + # Connect the clicked signal of the Cancel to it's Slot - reject self.ui.cancelButton.clicked.connect(self.handleCancel) - + self.ui.profileFilePath.textChanged.connect(self.controlStartDownloadBox) self.ui.zipFilePath.textChanged.connect(self.controlStartDownloadBox) self.ui.advancedOptionsButton.clicked.connect(self.showAdvancedOptions) @@ -117,34 +117,34 @@ def __init__(self, parent=None): def showAdvancedOptions(self): self.advancedOptionsDialog.show() - + def popupDirectoryDialog(self): # Popup a Directory selection box directory, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Select the signature file') # Show the selected file path in the field marked for showing directory path self.ui.profileFilePath.setText(directory) - + self.controlStartDownloadBox() - + def popupZipFileDialog(self): - + if self.ui.saveDatacheckBox.isChecked() is True: filename, _ = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select the folder to save downlaods to') else: # Popup a Zip File selection box filename, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Select the zip file to save downloads') - + # Show the selected file path in the field marked for showing directory path self.ui.zipFilePath.setText(filename) - + self.controlStartDownloadBox() - + def StartDownload(self): # Do all the download related work here and then close # Clear the consoleOutputHolder self.ui.rawLogHolder.setText("") - + self.filepath = str(self.ui.profileFilePath.text()) if os.path.isfile(self.filepath) == False: @@ -155,16 +155,16 @@ def StartDownload(self): self.ui.rawLogHolder.setText ( \ guicommon.style("%s does not exist." % self.filepath,'red')) return - + self.zipfilepath = str(self.ui.zipFilePath.text()) - + # First we need to determine if the input is a file path or a directory path - if self.ui.saveDatacheckBox.isChecked() is not True: + if self.ui.saveDatacheckBox.isChecked() is not True: #Input is a file path # if file already exists if os.path.exists(self.zipfilepath): - ret = QMessageBox.warning(self, "Replace archive file?", "The file %s already exists.\n" + ret = QMessageBox.warning(self, "Replace archive file?", "The file %s already exists.\n" "Do you want to overwrite it?" % self.zipfilepath, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if ret == QMessageBox.Yes: @@ -173,7 +173,7 @@ def StartDownload(self): #TODO: If "/" is the path, then os.unlink quietly fails crashing the GUI os.unlink(self.zipfilepath) except: - guicommon.updateInto (self.ui.rawLogHolder, + guicommon.updateInto (self.ui.rawLogHolder, guicommon.style("Couldn't write to %s!" % self.zipfilepath,'red')) else: return @@ -199,7 +199,7 @@ def StartDownload(self): try: os.mkdir(self.zipfilepath) except: - guicommon.updateInto (self.ui.rawLogHolder, + guicommon.updateInto (self.ui.rawLogHolder, guicommon.style("Couldn't create directory %s!" % self.zipfilepath,'red')) return else: @@ -207,26 +207,26 @@ def StartDownload(self): targetFilePath = None targetDirPath = self.zipfilepath - - args = GetterArgs(filename=self.filepath, bundle_file=targetFilePath, progress_bar=self.ui.statusProgressBar, + + args = GetterArgs(filename=self.filepath, bundle_file=targetFilePath, progress_bar=self.ui.statusProgressBar, progress_label=self.ui.progressStatusDescription, proxy_host=self.advancedOptionsDialog.proxy_host, proxy_port=self.advancedOptionsDialog.proxy_port, num_of_threads=self.advancedOptionsDialog.num_of_threads, socket_timeout=self.advancedOptionsDialog.socket_timeout, cache_dir=self.advancedOptionsDialog.cache_dir, download_dir=targetDirPath, disable_md5check=self.advancedOptionsDialog.disable_md5check, deb_bugs=self.advancedOptionsDialog.deb_bugs) - + #returnStatus = apt_offline_core.AptOfflineCoreLib.fetcher(args) # TODO: deal with return status laters - + self.ui.cancelButton.setText("Cancel") self.disableAction() self.disableAtDownload() self.worker.setArgs (args) self.worker.start() - + #if (returnStatus): ''' TODO: do something with self.zipfilepath ''' - + # TODO to be implemented later # self.accept() @@ -292,7 +292,7 @@ def resetUI(self): def disableAction(self): self.ui.startDownloadButton.setEnabled(False) - + def disableAtDownload(self): self.ui.advancedOptionsButton.setEnabled(False) self.ui.browseZipFileButton.setEnabled(False) @@ -300,7 +300,7 @@ def disableAtDownload(self): self.ui.zipFilePath.setEnabled(False) self.ui.profileFilePath.setEnabled(False) self.ui.saveDatacheckBox.setEnabled(False) - + def enableAction(self): self.ui.startDownloadButton.setEnabled(True) diff --git a/apt_offline_gui/AptOfflineQtFetchOptions.py b/apt_offline_gui/AptOfflineQtFetchOptions.py index 25a94bf..cd8201a 100644 --- a/apt_offline_gui/AptOfflineQtFetchOptions.py +++ b/apt_offline_gui/AptOfflineQtFetchOptions.py @@ -8,20 +8,20 @@ def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.ui = Ui_downloadOptionsDialog() self.ui.setupUi(self) - + # Connect the clicked signal of the Ok button to it's slot #QtCore.QObject.connect(self.ui.downloadOptionDialogOkButton, QtCore.SIGNAL("clicked()"), # self.validateOptions ) self.ui.downloadOptionDialogOkButton.clicked.connect(self.validateOptions) - + #QtCore.QObject.connect(self.ui.useProxyCheckBox, QtCore.SIGNAL("toggled(bool)"), # self.toggleProxyControls ) self.ui.useProxyCheckBox.toggled.connect(self.toggleProxyControls) - + #QtCore.QObject.connect(self.ui.cacheDirBrowseButton, QtCore.SIGNAL("clicked()"), # self.populateCacheDir ) self.ui.cacheDirBrowseButton.clicked.connect(self.populateCacheDir) - + # defaults self.num_of_threads = 1 self.socket_timeout = 30 @@ -30,31 +30,31 @@ def __init__(self, parent=None): self.deb_bugs = False self.proxy_host = None self.proxy_port = None - + def storeOptions(self): - + self._num_of_threads = self.ui.spinThreads.value() self._socket_timeout = self.ui.spinTimeout.value() - + self._cache_dir = str(self.ui.cacheDirLineEdit.text() ) - + self._disable_md5check = self.ui.disableChecksumCheckBox.isChecked() self._deb_bugs = self.ui.fetchBugReportsCheckBox.isChecked() - + if self.ui.useProxyCheckBox.isChecked(): self._proxy_host = str(self.ui.proxyHostLineEdit.text() ) self._proxy_port = str(self.ui.proxyPortLineEdit.text() ) else: self._proxy_host = None self._proxy_port = None - + def validateOptions(self): self.storeOptions() - + if len(self._cache_dir) > 0 and not (os.access(self._cache_dir, os.W_OK) or os.access(self._cache_dir, os.R_OK) ): QtWidgets.QMessageBox.critical(self, "Error", "Could not locate cache directory") return - + if self._proxy_port: try: int(self._proxy_port) @@ -63,19 +63,19 @@ def validateOptions(self): return self.applyOptionValues() self.hide() - + def applyOptionValues(self): self.num_of_threads = self._num_of_threads self.socket_timeout = self._socket_timeout - + self.cache_dir = self._cache_dir - + self.disable_md5check = self._disable_md5check self.deb_bugs = self._deb_bugs - + self.proxy_host = self._proxy_host self.proxy_port = self._proxy_port - + def toggleProxyControls(self): if self.ui.useProxyCheckBox.isChecked(): self.ui.proxyHostLineEdit.setEnabled(True) @@ -83,14 +83,14 @@ def toggleProxyControls(self): else: self.ui.proxyHostLineEdit.setEnabled(False) self.ui.proxyPortLineEdit.setEnabled(False) - - + + def populateCacheDir(self): directory = QtWidgets.QFileDialog.getExistingDirectory(None, 'Provide path to APT\'s Cache Dir') self.ui.cacheDirLineEdit.setText(directory) self._cache_dir = directory - - + + if __name__ == "__main__": app = QtGui.QApplication(sys.argv) myapp = AptOfflineQtFetchOptions() diff --git a/apt_offline_gui/AptOfflineQtInstall.py b/apt_offline_gui/AptOfflineQtInstall.py index 0499faa..548eedf 100644 --- a/apt_offline_gui/AptOfflineQtInstall.py +++ b/apt_offline_gui/AptOfflineQtInstall.py @@ -11,7 +11,7 @@ from apt_offline_gui.AptOfflineQtInstallChangelog import AptOfflineQtInstallChangelog class Worker(QtCore.QThread): - + output = QtCore.pyqtSignal(str) progress = QtCore.pyqtSignal(str, str) status = QtCore.pyqtSignal(str) @@ -22,7 +22,7 @@ def __init__(self, parent = None): QtCore.QThread.__init__(self, parent) self.parent = parent self.exiting = False - + def __del__(self): self.exiting = True self.wait() @@ -62,42 +62,42 @@ def write(self, text): ''' nothing to do ''' else: self.output.emit(text) - + def flush(self): ''' nothing to do :D ''' - + def quit(self): self.finished.emit() - - + + class AptOfflineQtInstall(QtWidgets.QDialog): - + def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.ui = Ui_AptOfflineQtInstall() self.ui.setupUi(self) - + # Connect the clicked signal of the Browse button to it's slot self.ui.browseFilePathButton.clicked.connect(self.popupDirectoryDialog) - + # Connect the clicked signal of the Save to it's Slot - accept self.ui.startInstallButton.clicked.connect(self.StartInstall) - + # Connect the clicked signal of the Cancel to it's Slot - reject self.ui.cancelButton.clicked.connect(self.reject) - + self.ui.bugReportsButton.clicked.connect(self.showBugReports) self.ui.changelogButton.clicked.connect(self.showChangelog) self.ui.zipFilePath.editingFinished.connect(self.ControlStartInstallBox) self.ui.zipFilePath.textChanged.connect(self.ControlStartInstallBox) - + self.worker = Worker(parent=self) self.worker.output.connect(self.updateLog) self.worker.progress.connect(self.updateProgress) self.worker.status.connect(self.updateStatus) self.worker.finished.connect(self.finishedWork) self.worker.terminated.connect(self.finishedWork) - + def StartInstall(self): # gui validation # Clear the consoleOutputHolder @@ -117,7 +117,7 @@ def showBugReports(self): self.bugReportsDialog = AptOfflineQtInstallBugList(self.filepath) self.bugReportsDialog.filepath= self.filepath self.bugReportsDialog.show() - + def showChangelog(self): self.filepath = str(self.ui.zipFilePath.text()) self.changelogDialog = AptOfflineQtInstallChangelog(self.filepath) @@ -131,11 +131,11 @@ def popupDirectoryDialog(self): directory = QtWidgets.QFileDialog.getExistingDirectory(self, 'Select the folder') else: directory = QtWidgets.QFileDialog.getOpenFileName(self, 'Select the Zip File') - + # Show the selected file path in the field marked for showing directory path self.ui.zipFilePath.setText(directory) self.ui.zipFilePath.setFocus() - + def ControlStartInstallBox(self): if os.path.isdir(self.ui.zipFilePath.text()) or os.path.isfile(self.ui.zipFilePath.text() ): self.ui.startInstallButton.setEnabled(True) @@ -145,7 +145,7 @@ def ControlStartInstallBox(self): self.ui.startInstallButton.setEnabled(False) self.ui.bugReportsButton.setEnabled(False) self.ui.changelogButton.setEnabled(False) - + def updateLog(self,text): guicommon.updateInto (self.ui.rawLogHolder,text) @@ -167,7 +167,7 @@ def finishedWork(self): guicommon.style("Finished syncing updates/packages","green_fin")) self.ui.progressStatusDescription.setText("Finished Syncing") self.ui.cancelButton.setText("Close") - + def disableActions(self): self.ui.browseFileFoldercheckBox.setEnabled(False) self.ui.cancelButton.setEnabled(False) diff --git a/apt_offline_gui/AptOfflineQtInstallBugList.py b/apt_offline_gui/AptOfflineQtInstallBugList.py index b2dd24f..3ba4691 100644 --- a/apt_offline_gui/AptOfflineQtInstallBugList.py +++ b/apt_offline_gui/AptOfflineQtInstallBugList.py @@ -17,15 +17,15 @@ class AptOfflineQtInstallBugList(QtWidgets.QDialog): def __init__(self, filepath, parent=None): QtWidgets.QWidget.__init__(self, parent) self.ui = Ui_AptOfflineQtInstallBugList() - + self.bugList = {} self.filepath = filepath - + self.ui.setupUi(self) - self.populateBugList(self.filepath) - + self.populateBugList(self.filepath) + self.ui.bugListViewWindow.itemSelectionChanged.connect(self.populateBugListPlainTextEdit) - + # Connect the clicked signal of the Browse button to it's slot #QtCore.QObject.connect(self.ui.closeButton, QtCore.SIGNAL("clicked()"), # self.reject ) @@ -34,23 +34,23 @@ def __init__(self, filepath, parent=None): def populateBugListPlainTextEdit(self): self.ui.bugListplainTextEdit.clear() textItem = str(self.ui.bugListViewWindow.currentItem().text() ) - + extractedText = self.bugList[textItem] self.ui.bugListplainTextEdit.appendPlainText(" ".join(extractedText)) - + myCursor = self.ui.bugListplainTextEdit.textCursor() myCursor.movePosition(myCursor.Start) self.ui.bugListplainTextEdit.setTextCursor(myCursor) - + def noBugPopulateBugListPlainTextEdit(self): self.ui.bugListplainTextEdit.clear() self.ui.bugListplainTextEdit.appendPlainText("No Bug Reports Found") def populateBugList(self, path): - + if os.path.isfile(path): zipFile = zipfile.ZipFile(path, "r") - + for filename in zipFile.namelist(): if filename.endswith( AptOfflineCoreLib.apt_bug_file_format ): #INFO: The splitter is use is "{}". Also used at other places @@ -68,7 +68,7 @@ def populateBugList(self, path): self.bugList[bug_subject_identifier] = temp.file.readlines() break temp.file.close() - + elif os.path.isdir(path): for filename in os.listdir( path ): if filename.endswith( AptOfflineCoreLib.apt_bug_file_format ): @@ -93,7 +93,7 @@ def populateBugList(self, path): for eachItem in list(self.bugList.keys()): item = QtGui.QListWidgetItem(eachItem) self.ui.bugListViewWindow.addItem(item) - + if __name__ == "__main__": app = QtGui.QApplication(sys.argv) diff --git a/apt_offline_gui/AptOfflineQtInstallChangelog.py b/apt_offline_gui/AptOfflineQtInstallChangelog.py index aff4867..99a058e 100644 --- a/apt_offline_gui/AptOfflineQtInstallChangelog.py +++ b/apt_offline_gui/AptOfflineQtInstallChangelog.py @@ -16,22 +16,22 @@ class AptOfflineQtInstallChangelog(QtWidgets.QDialog): def __init__(self, filepath, parent=None): QtWidgets.QWidget.__init__(self, parent) self.ui = Ui_AptOfflineQtInstallChangelog() - + self.filepath = filepath - + self.ui.setupUi(self) - self.populateChangelog(self.filepath) - + self.populateChangelog(self.filepath) + # Connect the clicked signal of the Browse button to it's slot #QtCore.QObject.connect(self.ui.closeButton, QtCore.SIGNAL("clicked()"), # self.reject ) self.ui.closeButton.clicked.connect(self.reject) def populateChangelog(self, path): - + self.chlogFile = tempfile.NamedTemporaryFile() self.chlogPresent = False - + if os.path.isdir(path): for eachItem in os.listdir(path): eachItem = os.path.join(path, eachItem) @@ -47,11 +47,11 @@ def populateChangelog(self, path): self.chlogPresent = True else: return False - + if self.chlogPresent is False: self.ui.changelogPlainTextEdit.clear() self.ui.changelogPlainTextEdit.appendPlainText('No changelog present') - else: + else: self.ui.changelogPlainTextEdit.clear() self.chlogFile.seek(0) self.ui.changelogPlainTextEdit.appendPlainText(self.chlogFile.read().decode('utf-8')) @@ -59,7 +59,7 @@ def populateChangelog(self, path): myCursor = self.ui.changelogPlainTextEdit.textCursor() myCursor.movePosition(myCursor.Start) self.ui.changelogPlainTextEdit.setTextCursor(myCursor) - + if __name__ == "__main__": app = QtGui.QApplication(sys.argv) diff --git a/apt_offline_gui/AptOfflineQtMain.py b/apt_offline_gui/AptOfflineQtMain.py index 86a4aa1..47e248a 100644 --- a/apt_offline_gui/AptOfflineQtMain.py +++ b/apt_offline_gui/AptOfflineQtMain.py @@ -13,17 +13,17 @@ def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.ui = Ui_AptOfflineMain() self.ui.setupUi(self) - + # Configure the various actions self.ConfigureCreateProfile() self.ConfigureDownload() self.ConfigureInstall() self.ConfigureAbout() self.ConfigureMenuExit() - + # Configure Hover over Buttons for Help self.CreateButtonHoverHelp() - + def ConfigureCreateProfile(self): #QtCore.QObject.connect(self.ui.menuCreateProfile, QtCore.SIGNAL("triggered()"), self.CreateProfile) #QtCore.QObject.connect(self.ui.createProfileButton,QtCore.SIGNAL("clicked()"), self.CreateProfile) @@ -34,7 +34,7 @@ def ConfigureCreateProfile(self): self.createProfileDialog = AptOfflineQtCreateProfile() # setup hover hack self.ui.createProfileButton.installEventFilter(self) - + def ConfigureDownload(self): #QtCore.QObject.connect(self.ui.menuDownload, QtCore.SIGNAL("triggered()"), self.DownloadPackagesUpgrades) #QtCore.QObject.connect(self.ui.downloadButton, QtCore.SIGNAL("clicked()"), self.DownloadPackagesUpgrades) @@ -45,7 +45,7 @@ def ConfigureDownload(self): self.createDownloadDialog = AptOfflineQtFetch() # setup hover hack self.ui.downloadButton.installEventFilter(self) - + def ConfigureInstall(self): #QtCore.QObject.connect(self.ui.menuInstall, QtCore.SIGNAL("triggered()"), self.InstallPackagesUpgrades) #QtCore.QObject.connect(self.ui.restoreButton, QtCore.SIGNAL("clicked()"), self.InstallPackagesUpgrades) @@ -65,7 +65,7 @@ def ConfigureAbout(self): self.ui.menuAbout.triggered.connect(self.ShowAbout) # Create an object for About Dialog self.createAboutDialog = AptOfflineQtAbout() - + def ConfigureMenuExit(self): #QtCore.QObject.connect(self.ui.menuExit, QtCore.SIGNAL("triggered()"), self.ExitApp) #QtCore.QObject.connect(self.ui.exitButton, QtCore.SIGNAL("clicked()"), self.ExitApp) @@ -81,12 +81,12 @@ def eventFilter(self,target,event): self.ui.descriptionField.setText("Once you are on an internet connected machine, use this to download packages as per your signature file.") if target.objectName() == 'restoreButton': self.ui.descriptionField.setText("Once you've downloaded all the packages, click here to install them on the offline machine.") - + if event.type() == QtCore.QEvent.HoverLeave: self.ui.descriptionField.setText("Hover your mouse over the buttons to get the description.") return False - - + + def CreateProfile(self): try: if os.geteuid() != 0: @@ -114,11 +114,11 @@ def InstallPackagesUpgrades(self): return # Code for creating Modal Dialog for Installing Packages/Upgrades self.createInstallDialog.show() - + def ShowAbout(self): # Code for showing Model Dialog for About Application self.createAboutDialog.show() - + def ShowHelp(self): QtWidgets.QMessageBox.information(self, "Info", "Please refer to the apt-offline(8) man page") diff --git a/apt_offline_gui/AptOfflineQtSaveZip.py b/apt_offline_gui/AptOfflineQtSaveZip.py index aae360d..81ff25f 100644 --- a/apt_offline_gui/AptOfflineQtSaveZip.py +++ b/apt_offline_gui/AptOfflineQtSaveZip.py @@ -9,19 +9,19 @@ def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_SaveZipFile() self.ui.setupUi(self) - + # Connect the clicked signal of the Browse button to it's slot QtCore.QObject.connect(self.ui.browseFilePathButton, QtCore.SIGNAL("clicked()"), self.popupDirectoryDialog ) - + # Connect the clicked signal of the Save to it's Slot - accept QtCore.QObject.connect(self.ui.saveButton, QtCore.SIGNAL("clicked()"), self.accept ) - + # Connect the clicked signal of the Cancel to it's Slot - reject QtCore.QObject.connect(self.ui.cancelButton, QtCore.SIGNAL("clicked()"), self.reject ) - + def popupDirectoryDialog(self): # Popup a Directory selection box directory = QtGui.QFileDialog.getExistingDirectory(self, 'Open Directory') diff --git a/apt_offline_gui/QtProgressBar.py b/apt_offline_gui/QtProgressBar.py index 86e5323..faafbf0 100644 --- a/apt_offline_gui/QtProgressBar.py +++ b/apt_offline_gui/QtProgressBar.py @@ -1,64 +1,64 @@ class QtProgressBar( object ): - + def __init__( self, progressbar=None,label =None, minValue=0, maxValue=0, total_items=None): self.min = minValue self.max = maxValue self.span = float( self.max - self.min ) self.fd = fd self.signal_set = False - + # This field stores QProgessBar self.progressBar = progressbar # This field stores the Label self.progressLabel = label - + self.value = self.min - + if total_items is None or total_items <= 0: self.items = 0 #count of items being tracked self.items_update = True else: self.items = total_items self.items_update = False - + self.complete = 0 - + def handle_resize( self, signum, frame ): h, w = array( 'h', ioctl( self.fd, termios.TIOCGWINSZ, '\0' * 8 ) )[:2] self.width = w - + def updateValue( self, newValue ): #require caller to supply a value! newValue is the increment from last call self.value = max( self.min, min( self.max, self.value + newValue ) ) self.display() - + def completed( self ): self.complete = self.complete + 1 - + #if self.signal_set: #signal.signal( signal.SIGWINCH, signal.SIG_DFL ) self.display() - + def addItem( self, maxValue ): self.max = self.max + maxValue self.span = float( self.max - self.min ) if self.items_update is True: self.items = self.items + 1 self.display() - + def display( self ): #print "\r%3s /%3s items: %s\r" % ( self.complete, self.items, str( self ) ), self.progressBar.setValue(int(self.__str__())) progressText = "%3s /%3s Size: %s" % ( self.complete, self.items, self.__numStr__( self.max / 1024 )) self.progressLabel.setText(progressText) - + def __str__( self ): #compute display fraction percentFilled = ( ( self.value - self.min ) / self.span ) #widthFilled = int( self.width * percentFilled + 0.5 ) #return ( "[" + "#"*widthFilled + " " * ( self.width - widthFilled ) + "]" + " %5.1f%% of %s" % ( percentFilled * 100.0, self.__numStr__( self.max / 1024 ) ) ) return percentFilled - + def __numStr__( self, size ): if size > 1024: size = size / 1024 diff --git a/apt_offline_gui/UiDataStructs.py b/apt_offline_gui/UiDataStructs.py index 03f3fb8..0a40ae5 100644 --- a/apt_offline_gui/UiDataStructs.py +++ b/apt_offline_gui/UiDataStructs.py @@ -1,30 +1,30 @@ # -*- coding: utf-8 -*- class SetterArgs(): - + def __init__(self, filename, update, upgrade, install_packages, install_src_packages, \ src_build_dep, changelog, release, apt_backend, simulate=False): self.set = filename - + # self.set_update is of type boolean self.set_update = update - + # self.set_upgrade can be either True or False self.set_upgrade = upgrade self.upgrade_type = "upgrade" - + # Should be set to None for disabling or Tuple for activating self.set_install_packages = install_packages - + # To be implemented later self.src_build_dep = src_build_dep self.set_install_src_packages = install_src_packages self.set_install_release = release self.apt_backend = apt_backend self.set_simulate=simulate - + self.generate_changelog = changelog - + def __str__(self): print("self.set=",self.set) print("self.set_update=",self.set_update) @@ -32,9 +32,9 @@ def __str__(self): print("self.upgrade_type=",self.upgrade_type) print("self.set_install_packages=",self.set_install_packages) print("self.set_simulate=", self.set_simulate) - + return "" - + class GetterArgs(): def __init__(self, filename=None, bundle_file=None, socket_timeout=30, \ @@ -52,10 +52,10 @@ def __init__(self, filename=None, bundle_file=None, socket_timeout=30, \ self.deb_bugs = deb_bugs self.download_dir = download_dir self.cache_dir = cache_dir - + self.proxy_host = proxy_host self.proxy_port = proxy_port - + self.progress_bar = progress_bar self.progress_label = progress_label @@ -69,7 +69,7 @@ def __str__(self): print("self.deb_bugs=",self.deb_bugs) print("self.download_dir=",self.download_dir) print("self.cache_dir=",self.cache_dir) - + return "" ''' @@ -88,11 +88,11 @@ def __init__(self, filename=None, skip_bug_reports=True, skip_changelog=True, al self.install = filename # TODO: to be implemented in next revision - self.install_simulate = simulate + self.install_simulate = simulate self.skip_bug_reports = skip_bug_reports self.skip_changelog = skip_changelog self.allow_unauthenticated = allow_unauthenticated self.install_src_path = install_src_path - + self.progress_bar = progress_bar self.progress_label = progress_label diff --git a/tests/apt-offline-tests.sh b/tests/apt-offline-tests.sh index 0afe3b6..92f2d98 100755 --- a/tests/apt-offline-tests.sh +++ b/tests/apt-offline-tests.sh @@ -20,7 +20,7 @@ set_features () { $APT_OFFLINE set $URI echo "Executing command 'set $URI --simulate '" - $APT_OFFLINE set $URI --simulate + $APT_OFFLINE set $URI --simulate echo "Executing command 'set $URI --update'" $APT_OFFLINE set $URI --update @@ -35,7 +35,7 @@ set_features () { $APT_OFFLINE set $URI --update --upgrade --upgrade-type upgrade echo "Executing command 'set $URI --update --upgrade --upgrade-type upgrade --release $RELEASE'" - $APT_OFFLINE set $URI --update --upgrade --upgrade-type upgrade --release $RELEASE + $APT_OFFLINE set $URI --update --upgrade --upgrade-type upgrade --release $RELEASE echo "Executing command 'set $URI --install-packages $DISLIKED_PACKAGES'" $APT_OFFLINE set $URI --install-packages $DISLIKED_PACKAGES @@ -62,7 +62,7 @@ get_features () { URI=$1 fi echo "Executing command 'get $URI '" - $APT_OFFLINE get $URI + $APT_OFFLINE get $URI echo "Executing command 'get $URI --threads $THREADS'" $APT_OFFLINE get $URI --threads $THREADS @@ -123,7 +123,7 @@ install_features_prompt () { BUNDLE_FILE=$1 fi echo "Executing command 'install $DOWNLOAD_DIR '" - $APT_OFFLINE install $DOWNLOAD_DIR + $APT_OFFLINE install $DOWNLOAD_DIR echo "Executing command 'install $DOWNLOAD_DIR --simulate'" $APT_OFFLINE install $DOWNLOAD_DIR --simulate @@ -135,7 +135,7 @@ install_features_prompt () { $APT_OFFLINE install $DOWNLOAD_DIR --simulate --allow-unauthenticated echo "Executing command 'install $BUNDLE_FILE '" - $APT_OFFLINE install $BUNDLE_FILE + $APT_OFFLINE install $BUNDLE_FILE echo "Executing command 'install $BUNDLE_FILE --simulate'" $APT_OFFLINE install $BUNDLE_FILE --simulate