def local_display_percent_done(current, maximum): '''Bump up verboseness so we get download percentage done feedback.''' oldverbose = munkicommon.verbose munkicommon.verbose = oldverbose + 1 munkicommon.display_percent_done(current, maximum) # set verboseness back. munkicommon.verbose = oldverbose
def removeFilesystemItems(removalpaths, forcedeletebundles): """ Attempts to remove all the paths in the array removalpaths """ # we sort in reverse because we can delete from the bottom up, # clearing a directory before we try to remove the directory itself removalpaths.sort(reverse=True) removalerrors = "" removalcount = len(removalpaths) munkicommon.display_status_minor('Removing %s filesystem items' % removalcount) itemcount = len(removalpaths) itemindex = 0 munkicommon.display_percent_done(itemindex, itemcount) for item in removalpaths: itemindex += 1 pathtoremove = "/" + item # use os.path.lexists so broken links return true # so we can remove them if os.path.lexists(pathtoremove): munkicommon.display_detail("Removing: " + pathtoremove) if (os.path.isdir(pathtoremove) and not os.path.islink(pathtoremove)): diritems = munkicommon.listdir(pathtoremove) if diritems == ['.DS_Store']: # If there's only a .DS_Store file # we'll consider it empty ds_storepath = pathtoremove + "/.DS_Store" try: os.remove(ds_storepath) except (OSError, IOError): pass diritems = munkicommon.listdir(pathtoremove) if diritems == []: # directory is empty try: os.rmdir(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove directory %s - %s" % ( pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # the directory is marked for deletion but isn't empty. # if so directed, if it's a bundle (like .app), we should # remove it anyway - no use having a broken bundle hanging # around if forcedeletebundles and isBundle(pathtoremove): munkicommon.display_warning( "Removing non-empty bundle: %s", pathtoremove) retcode = subprocess.call( ['/bin/rm', '-r', pathtoremove]) if retcode: msg = "Couldn't remove bundle %s" % pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # if this path is inside a bundle, and we've been # directed to force remove bundles, # we don't need to warn because it's going to be # removed with the bundle. # Otherwise, we should warn about non-empty # directories. if not insideBundle(pathtoremove) or \ not forcedeletebundles: msg = \ "Did not remove %s because it is not empty." % \ pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # not a directory, just unlink it # I was using rm instead of Python because I don't trust # handling of resource forks with Python #retcode = subprocess.call(['/bin/rm', pathtoremove]) # but man that's slow. # I think there's a lot of overhead with the # subprocess call. I'm going to use os.remove. # I hope I don't regret it. retcode = '' try: os.remove(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove item %s: %s" % (pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg
def removeReceipts(pkgkeylist, noupdateapplepkgdb): """ Removes receipt data from /Library/Receipts, /Library/Receipts/boms, our internal package database, and optionally Apple's package database. """ munkicommon.display_status_minor('Removing receipt info') munkicommon.display_percent_done(0, 4) conn = sqlite3.connect(packagedb) curs = conn.cursor() os_version = munkicommon.getOsVersion(as_tuple=True) applepkgdb = '/Library/Receipts/db/a.receiptdb' if not noupdateapplepkgdb and os_version <= (10, 5): aconn = sqlite3.connect(applepkgdb) acurs = aconn.cursor() munkicommon.display_percent_done(1, 4) for pkgkey in pkgkeylist: pkgid = '' pkgkey_t = (pkgkey, ) row = curs.execute('SELECT pkgname, pkgid from pkgs where pkg_key = ?', pkgkey_t).fetchone() if row: pkgname = row[0] pkgid = row[1] receiptpath = None if os_version <= (10, 5): if pkgname.endswith('.pkg'): receiptpath = os.path.join('/Library/Receipts', pkgname) if pkgname.endswith('.bom'): receiptpath = os.path.join('/Library/Receipts/boms', pkgname) else: # clean up /Library/Receipts in case there's stuff left there receiptpath = findBundleReceiptFromID(pkgid) if receiptpath and os.path.exists(receiptpath): munkicommon.display_detail("Removing %s...", receiptpath) dummy_retcode = subprocess.call( ["/bin/rm", "-rf", receiptpath]) # remove pkg info from our database munkicommon.display_detail( "Removing package data from internal database...") curs.execute('DELETE FROM pkgs_paths where pkg_key = ?', pkgkey_t) curs.execute('DELETE FROM pkgs where pkg_key = ?', pkgkey_t) # then remove pkg info from Apple's database unless option is passed if not noupdateapplepkgdb and pkgid: if os_version <= (10, 5): # Leopard pkgid_t = (pkgid, ) row = acurs.execute('SELECT pkg_key FROM pkgs where pkgid = ?', pkgid_t).fetchone() if row: munkicommon.display_detail( "Removing package data from Apple package " + "database...") apple_pkg_key = row[0] pkgkey_t = (apple_pkg_key, ) acurs.execute('DELETE FROM pkgs where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM pkgs_paths where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM pkgs_groups where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM acls where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM taints where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM sha1s where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM oldpkgs where pkg_key = ?', pkgkey_t) else: # Snow Leopard or higher, must use pkgutil cmd = ['/usr/sbin/pkgutil', '--forget', pkgid] proc = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (output, dummy_err) = proc.communicate() if output: munkicommon.display_detail( str(output).decode('UTF-8').rstrip('\n')) munkicommon.display_percent_done(2, 4) # now remove orphaned paths from paths table # first, Apple's database if option is passed if not noupdateapplepkgdb: if os_version <= (10, 5): munkicommon.display_detail( "Removing unused paths from Apple package database...") acurs.execute('''DELETE FROM paths where path_key not in (select distinct path_key from pkgs_paths)''') aconn.commit() acurs.close() aconn.close() munkicommon.display_percent_done(3, 4) # we do our database last so its modtime is later than the modtime for the # Apple DB... munkicommon.display_detail("Removing unused paths from internal package " "database...") curs.execute('''DELETE FROM paths where path_key not in (select distinct path_key from pkgs_paths)''') conn.commit() curs.close() conn.close() munkicommon.display_percent_done(4, 4)
def initDatabase(forcerebuild=False): """ Builds or rebuilds our internal package database. """ if not shouldRebuildDB(packagedb) and not forcerebuild: return True munkicommon.display_status_minor( 'Gathering information on installed packages') if os.path.exists(packagedb): try: os.remove(packagedb) except (OSError, IOError): munkicommon.display_error( "Could not remove out-of-date receipt database.") return False os_version = munkicommon.getOsVersion(as_tuple=True) pkgcount = 0 receiptsdir = "/Library/Receipts" bomsdir = "/Library/Receipts/boms" if os.path.exists(receiptsdir): receiptlist = munkicommon.listdir(receiptsdir) for item in receiptlist: if item.endswith(".pkg"): pkgcount += 1 if os.path.exists(bomsdir): bomslist = munkicommon.listdir(bomsdir) for item in bomslist: if item.endswith(".bom"): pkgcount += 1 if os_version >= (10, 6): # Snow Leopard or later pkglist = [] cmd = ['/usr/sbin/pkgutil', '--pkgs'] proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) while True: line = proc.stdout.readline() if not line and (proc.poll() != None): break pkglist.append(line.rstrip('\n')) pkgcount += 1 conn = sqlite3.connect(packagedb) conn.text_factory = str curs = conn.cursor() CreateTables(curs) currentpkgindex = 0 munkicommon.display_percent_done(0, pkgcount) if os.path.exists(receiptsdir): receiptlist = munkicommon.listdir(receiptsdir) for item in receiptlist: if munkicommon.stopRequested(): curs.close() conn.close() #our package db isn't valid, so we should delete it os.remove(packagedb) return False if item.endswith(".pkg"): receiptpath = os.path.join(receiptsdir, item) munkicommon.display_detail("Importing %s...", receiptpath) ImportPackage(receiptpath, curs) currentpkgindex += 1 munkicommon.display_percent_done(currentpkgindex, pkgcount) if os.path.exists(bomsdir): bomslist = munkicommon.listdir(bomsdir) for item in bomslist: if munkicommon.stopRequested(): curs.close() conn.close() #our package db isn't valid, so we should delete it os.remove(packagedb) return False if item.endswith(".bom"): bompath = os.path.join(bomsdir, item) munkicommon.display_detail("Importing %s...", bompath) ImportBom(bompath, curs) currentpkgindex += 1 munkicommon.display_percent_done(currentpkgindex, pkgcount) if os_version >= (10, 6): # Snow Leopard or later for pkg in pkglist: if munkicommon.stopRequested(): curs.close() conn.close() #our package db isn't valid, so we should delete it os.remove(packagedb) return False munkicommon.display_detail("Importing %s...", pkg) ImportFromPkgutil(pkg, curs) currentpkgindex += 1 munkicommon.display_percent_done(currentpkgindex, pkgcount) # in case we didn't quite get to 100% for some reason if currentpkgindex < pkgcount: munkicommon.display_percent_done(pkgcount, pkgcount) # commit and close the db when we're done. conn.commit() curs.close() conn.close() return True
# I was using rm instead of Python because I don't trust # handling of resource forks with Python #retcode = subprocess.call(['/bin/rm', pathtoremove]) # but man that's slow. # I think there's a lot of overhead with the # subprocess call. I'm going to use os.remove. # I hope I don't regret it. retcode = '' try: os.remove(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove item %s: %s" % (pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg munkicommon.display_percent_done(itemindex, itemcount) if removalerrors: munkicommon.display_info( "---------------------------------------------------") munkicommon.display_info( "There were problems removing some filesystem items.") munkicommon.display_info( "---------------------------------------------------") munkicommon.display_info(removalerrors) def removepackages(pkgnames, forcedeletebundles=False, listfiles=False, rebuildpkgdb=False,
def removeFilesystemItems(removalpaths, forcedeletebundles): """ Attempts to remove all the paths in the array removalpaths """ # we sort in reverse because we can delete from the bottom up, # clearing a directory before we try to remove the directory itself removalpaths.sort(reverse=True) removalerrors = "" removalcount = len(removalpaths) munkicommon.display_status_minor( 'Removing %s filesystem items' % removalcount) itemcount = len(removalpaths) itemindex = 0 munkicommon.display_percent_done(itemindex, itemcount) for item in removalpaths: itemindex += 1 pathtoremove = "/" + item # use os.path.lexists so broken links return true # so we can remove them if os.path.lexists(pathtoremove): munkicommon.display_detail("Removing: " + pathtoremove) if (os.path.isdir(pathtoremove) and \ not os.path.islink(pathtoremove)): diritems = munkicommon.listdir(pathtoremove) if diritems == ['.DS_Store']: # If there's only a .DS_Store file # we'll consider it empty ds_storepath = pathtoremove + "/.DS_Store" try: os.remove(ds_storepath) except (OSError, IOError): pass diritems = munkicommon.listdir(pathtoremove) if diritems == []: # directory is empty try: os.rmdir(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove directory %s - %s" % ( pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # the directory is marked for deletion but isn't empty. # if so directed, if it's a bundle (like .app), we should # remove it anyway - no use having a broken bundle hanging # around if (forcedeletebundles and isBundle(pathtoremove)): munkicommon.display_warning( "Removing non-empty bundle: %s", pathtoremove) retcode = subprocess.call(['/bin/rm', '-r', pathtoremove]) if retcode: msg = "Couldn't remove bundle %s" % pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # if this path is inside a bundle, and we've been # directed to force remove bundles, # we don't need to warn because it's going to be # removed with the bundle. # Otherwise, we should warn about non-empty # directories. if not insideBundle(pathtoremove) or \ not forcedeletebundles: msg = \ "Did not remove %s because it is not empty." % \ pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # not a directory, just unlink it # I was using rm instead of Python because I don't trust # handling of resource forks with Python #retcode = subprocess.call(['/bin/rm', pathtoremove]) # but man that's slow. # I think there's a lot of overhead with the # subprocess call. I'm going to use os.remove. # I hope I don't regret it. retcode = '' try: os.remove(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove item %s: %s" % (pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg
def removeReceipts(pkgkeylist, noupdateapplepkgdb): """ Removes receipt data from /Library/Receipts, /Library/Receipts/boms, our internal package database, and optionally Apple's package database. """ munkicommon.display_status_minor('Removing receipt info') munkicommon.display_percent_done(0, 4) conn = sqlite3.connect(packagedb) curs = conn.cursor() os_version = munkicommon.getOsVersion(as_tuple=True) applepkgdb = '/Library/Receipts/db/a.receiptdb' if not noupdateapplepkgdb and os_version <= (10, 5): aconn = sqlite3.connect(applepkgdb) acurs = aconn.cursor() munkicommon.display_percent_done(1, 4) for pkgkey in pkgkeylist: pkgid = '' pkgkey_t = (pkgkey, ) row = curs.execute( 'SELECT pkgname, pkgid from pkgs where pkg_key = ?', pkgkey_t).fetchone() if row: pkgname = row[0] pkgid = row[1] receiptpath = None if os_version <= (10, 5): if pkgname.endswith('.pkg'): receiptpath = os.path.join('/Library/Receipts', pkgname) if pkgname.endswith('.bom'): receiptpath = os.path.join('/Library/Receipts/boms', pkgname) else: # clean up /Library/Receipts in case there's stuff left there receiptpath = findBundleReceiptFromID(pkgid) if receiptpath and os.path.exists(receiptpath): munkicommon.display_detail("Removing %s...", receiptpath) unused_retcode = subprocess.call( ["/bin/rm", "-rf", receiptpath]) # remove pkg info from our database munkicommon.display_detail( "Removing package data from internal database...") curs.execute('DELETE FROM pkgs_paths where pkg_key = ?', pkgkey_t) curs.execute('DELETE FROM pkgs where pkg_key = ?', pkgkey_t) # then remove pkg info from Apple's database unless option is passed if not noupdateapplepkgdb and pkgid: if os_version <= (10, 5): # Leopard pkgid_t = (pkgid, ) row = acurs.execute( 'SELECT pkg_key FROM pkgs where pkgid = ?', pkgid_t).fetchone() if row: munkicommon.display_detail( "Removing package data from Apple package "+ "database...") apple_pkg_key = row[0] pkgkey_t = (apple_pkg_key, ) acurs.execute( 'DELETE FROM pkgs where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM pkgs_paths where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM pkgs_groups where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM acls where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM taints where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM sha1s where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM oldpkgs where pkg_key = ?', pkgkey_t) else: # Snow Leopard or higher, must use pkgutil cmd = ['/usr/sbin/pkgutil', '--forget', pkgid] proc = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (output, unused_err) = proc.communicate() if output: munkicommon.display_detail( str(output).decode('UTF-8').rstrip('\n')) munkicommon.display_percent_done(2, 4) # now remove orphaned paths from paths table # first, Apple's database if option is passed if not noupdateapplepkgdb: if os_version <= (10, 5): munkicommon.display_detail( "Removing unused paths from Apple package database...") acurs.execute( '''DELETE FROM paths where path_key not in (select distinct path_key from pkgs_paths)''') aconn.commit() acurs.close() aconn.close() munkicommon.display_percent_done(3, 4) # we do our database last so its modtime is later than the modtime for the # Apple DB... munkicommon.display_detail("Removing unused paths from internal package " "database...") curs.execute( '''DELETE FROM paths where path_key not in (select distinct path_key from pkgs_paths)''') conn.commit() curs.close() conn.close() munkicommon.display_percent_done(4, 4)
# I was using rm instead of Python because I don't trust # handling of resource forks with Python #retcode = subprocess.call(['/bin/rm', pathtoremove]) # but man that's slow. # I think there's a lot of overhead with the # subprocess call. I'm going to use os.remove. # I hope I don't regret it. retcode = '' try: os.remove(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove item %s: %s" % (pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg munkicommon.display_percent_done(itemindex, itemcount) if removalerrors: munkicommon.display_info( "---------------------------------------------------") munkicommon.display_info( "There were problems removing some filesystem items.") munkicommon.display_info( "---------------------------------------------------") munkicommon.display_info(removalerrors) def removepackages(pkgnames, forcedeletebundles=False, listfiles=False, rebuildpkgdb=False, noremovereceipts=False, noupdateapplepkgdb=False): """
def get_url(url, destinationpath, custom_headers=None, message=None, onlyifnewer=False, resume=False, follow_redirects=False): """Gets an HTTP or HTTPS URL and stores it in destination path. Returns a dictionary of headers, which includes http_result_code and http_result_description. Will raise CurlError if Gurl returns an error. Will raise HTTPError if HTTP Result code is not 2xx or 304. If destinationpath already exists, you can set 'onlyifnewer' to true to indicate you only want to download the file only if it's newer on the server. If you set resume to True, Gurl will attempt to resume an interrupted download.""" tempdownloadpath = destinationpath + '.download' if os.path.exists(tempdownloadpath) and not resume: if resume and not os.path.exists(destinationpath): os.remove(tempdownloadpath) cache_data = None if onlyifnewer and os.path.exists(destinationpath): # create a temporary Gurl object so we can extract the # stored caching data so we can download only if the # file has changed on the server gurl_obj = Gurl.alloc().initWithOptions_({'file': destinationpath}) cache_data = gurl_obj.get_stored_headers() del gurl_obj options = { 'url': url, 'file': tempdownloadpath, 'follow_redirects': follow_redirects, 'can_resume': resume, 'additional_headers': header_dict_from_list(custom_headers), 'download_only_if_changed': onlyifnewer, 'cache_data': cache_data, 'logging_function': munkicommon.display_debug2 } munkicommon.display_debug2('Options: %s' % options) connection = Gurl.alloc().initWithOptions_(options) stored_percent_complete = -1 stored_bytes_received = 0 connection.start() try: while True: # if we did `while not connection.isDone()` we'd miss printing # messages and displaying percentages if we exit the loop first connection_done = connection.isDone() if message and connection.status and connection.status != 304: # log always, display if verbose is 1 or more # also display in MunkiStatus detail field munkicommon.display_status_minor(message) # now clear message so we don't display it again message = None if (str(connection.status).startswith('2') and connection.percentComplete != -1): if connection.percentComplete != stored_percent_complete: # display percent done if it has changed stored_percent_complete = connection.percentComplete munkicommon.display_percent_done(stored_percent_complete, 100) elif connection.bytesReceived != stored_bytes_received: # if we don't have percent done info, log bytes received stored_bytes_received = connection.bytesReceived munkicommon.display_detail('Bytes received: %s', stored_bytes_received) if connection_done: break except (KeyboardInterrupt, SystemExit): # safely kill the connection then re-raise connection.cancel() raise except Exception, err: # too general, I know # Let us out! ... Safely! Unexpectedly quit dialogs are annoying... connection.cancel() # Re-raise the error as a GurlError raise GurlError(-1, str(err))
if message and header.get('http_result_code') != '304': if message: # log always, display if verbose is 1 or more # also display in MunkiStatus detail field munkicommon.display_status_minor(message) elif targetsize and header.get('http_result_code').startswith('2'): # display progress if we get a 2xx result code if os.path.exists(tempdownloadpath): downloadedsize = os.path.getsize(tempdownloadpath) percent = int(float(downloadedsize) /float(targetsize)*100) if percent != downloadedpercent: # percent changed; update display downloadedpercent = percent munkicommon.display_percent_done(downloadedpercent, 100) time.sleep(0.1) else: # Headers have finished, but not targetsize or HTTP2xx. # It's possible that Content-Length was not in the headers. # so just sleep and loop again. We can't show progress. time.sleep(0.1) if (proc.poll() != None): # For small download files curl may exit before all headers # have been parsed, don't immediately exit. maxheaders -= 1 if donewithheaders or maxheaders <= 0: break retcode = proc.poll()
break else: # no data, but we're still running # sleep a bit before checking for more output time.sleep(1) continue output = output.decode('UTF-8').strip() # send the output to STDOUT or MunkiStatus as applicable if output.startswith('Progress: '): # Snow Leopard/Lion progress info with '-v' flag try: percent = int(output[10:].rstrip('%')) except ValueError: percent = -1 munkicommon.display_percent_done(percent, 100) elif output.startswith('Software Update Tool'): # don't display this pass elif output.startswith('Copyright 2'): # don't display this pass elif output.startswith('Installing ') and mode == 'install': item = output[11:] if item: if munkicommon.munkistatusoutput: munkistatus.message(output) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(output) else:
def get_url(url, destinationpath, custom_headers=None, message=None, onlyifnewer=False, resume=False, follow_redirects=False): """Gets an HTTP or HTTPS URL and stores it in destination path. Returns a dictionary of headers, which includes http_result_code and http_result_description. Will raise CurlError if Gurl returns an error. Will raise HTTPError if HTTP Result code is not 2xx or 304. If destinationpath already exists, you can set 'onlyifnewer' to true to indicate you only want to download the file only if it's newer on the server. If you set resume to True, Gurl will attempt to resume an interrupted download.""" tempdownloadpath = destinationpath + '.download' if os.path.exists(tempdownloadpath) and not resume: if resume and not os.path.exists(destinationpath): os.remove(tempdownloadpath) cache_data = None if onlyifnewer and os.path.exists(destinationpath): # create a temporary Gurl object so we can extract the # stored caching data so we can download only if the # file has changed on the server gurl_obj = Gurl.alloc().initWithOptions_({'file': destinationpath}) cache_data = gurl_obj.get_stored_headers() del gurl_obj options = {'url': url, 'file': tempdownloadpath, 'follow_redirects': follow_redirects, 'can_resume': resume, 'additional_headers': header_dict_from_list(custom_headers), 'download_only_if_changed': onlyifnewer, 'cache_data': cache_data, 'logging_function': munkicommon.display_debug2} munkicommon.display_debug2('Options: %s' % options) connection = Gurl.alloc().initWithOptions_(options) stored_percent_complete = -1 stored_bytes_received = 0 connection.start() try: while True: # if we did `while not connection.isDone()` we'd miss printing # messages and displaying percentages if we exit the loop first connection_done = connection.isDone() if message and connection.status and connection.status != 304: # log always, display if verbose is 1 or more # also display in MunkiStatus detail field munkicommon.display_status_minor(message) # now clear message so we don't display it again message = None if (str(connection.status).startswith('2') and connection.percentComplete != -1): if connection.percentComplete != stored_percent_complete: # display percent done if it has changed stored_percent_complete = connection.percentComplete munkicommon.display_percent_done( stored_percent_complete, 100) elif connection.bytesReceived != stored_bytes_received: # if we don't have percent done info, log bytes received stored_bytes_received = connection.bytesReceived munkicommon.display_detail( 'Bytes received: %s', stored_bytes_received) if connection_done: break except (KeyboardInterrupt, SystemExit): # safely kill the connection then re-raise connection.cancel() raise except Exception, err: # too general, I know # Let us out! ... Safely! Unexpectedly quit dialogs are annoying... connection.cancel() # Re-raise the error as a GurlError raise GurlError(-1, str(err))