diff options
Diffstat (limited to 'src/eclean/eclean')
-rw-r--r-- | src/eclean/eclean | 807 |
1 files changed, 807 insertions, 0 deletions
diff --git a/src/eclean/eclean b/src/eclean/eclean new file mode 100644 index 0000000..4fdec10 --- /dev/null +++ b/src/eclean/eclean @@ -0,0 +1,807 @@ +#!/usr/bin/env python +# Copyright 2003-2005 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: $ + + +############################################################################### +# Meta: +__author__ = "Thomas de Grenier de Latour (tgl)" +__email__ = "degrenier@easyconnect.fr" +__version__ = "0.4.1" +__productname__ = "eclean" +__description__ = "A cleaning tool for Gentoo distfiles and binaries." + + +############################################################################### +# Python imports: +import sys +import os, stat +import re +import time +import getopt +import fpformat +import signal +try: + import portage +except ImportError: + sys.path.insert(0, "/usr/lib/portage/pym") + import portage +try: + from portage.output import * +except ImportError: + from output import * + +listdir = portage.listdir + +############################################################################### +# Misc. shortcuts to some portage stuff: +port_settings = portage.settings +distdir = port_settings["DISTDIR"] +pkgdir = port_settings["PKGDIR"] + + +############################################################################### +# printVersion: +def printVersion(): + print "%s (version %s) - %s" \ + % (__productname__, __version__, __description__) + print "Author: %s <%s>" % (__author__,__email__) + print "Copyright 2003-2005 Gentoo Foundation" + print "Distributed under the terms of the GNU General Public License v2" + + +############################################################################### +# printUsage: print help message. May also print partial help to stderr if an +# error from {'options','actions'} is specified. +def printUsage(error=None,help=None): + out = sys.stdout + if error: out = sys.stderr + if not error in ('actions', 'global-options', \ + 'packages-options', 'distfiles-options', \ + 'merged-packages-options', 'merged-distfiles-options', \ + 'time', 'size'): + error = None + if not error and not help: help = 'all' + if error == 'time': + eerror("Wrong time specification") + print >>out, "Time specification should be an integer followed by a"+ \ + " single letter unit." + print >>out, "Available units are: y (years), m (months), w (weeks), "+ \ + "d (days) and h (hours)." + print >>out, "For instance: \"1y\" is \"one year\", \"2w\" is \"two"+ \ + " weeks\", etc. " + return + if error == 'size': + eerror("Wrong size specification") + print >>out, "Size specification should be an integer followed by a"+ \ + " single letter unit." + print >>out, "Available units are: G, M, K and B." + print >>out, "For instance: \"10M\" is \"ten megabytes\", \"200K\" "+ \ + "is \"two hundreds kilobytes\", etc." + return + if error in ('global-options', 'packages-options', 'distfiles-options', \ + 'merged-packages-options', 'merged-distfiles-options',): + eerror("Wrong option on command line.") + print >>out + elif error == 'actions': + eerror("Wrong or missing action name on command line.") + print >>out + print >>out, white("Usage:") + if error in ('actions','global-options', 'packages-options', \ + 'distfiles-options') or help == 'all': + print >>out, " "+turquoise(__productname__), \ + yellow("[global-option] ..."), \ + green("<action>"), \ + yellow("[action-option] ...") + if error == 'merged-distfiles-options' or help in ('all','distfiles'): + print >>out, " "+turquoise(__productname__+'-dist'), \ + yellow("[global-option, distfiles-option] ...") + if error == 'merged-packages-options' or help in ('all','packages'): + print >>out, " "+turquoise(__productname__+'-pkg'), \ + yellow("[global-option, packages-option] ...") + if error in ('global-options', 'actions'): + print >>out, " "+turquoise(__productname__), \ + yellow("[--help, --version]") + if help == 'all': + print >>out, " "+turquoise(__productname__+"(-dist,-pkg)"), \ + yellow("[--help, --version]") + if error == 'merged-packages-options' or help == 'packages': + print >>out, " "+turquoise(__productname__+'-pkg'), \ + yellow("[--help, --version]") + if error == 'merged-distfiles-options' or help == 'distfiles': + print >>out, " "+turquoise(__productname__+'-dist'), \ + yellow("[--help, --version]") + print >>out + if error in ('global-options', 'merged-packages-options', \ + 'merged-distfiles-options') or help: + print >>out, "Available global", yellow("options")+":" + print >>out, yellow(" -C, --nocolor")+ \ + " - turn off colors on output" + print >>out, yellow(" -d, --destructive")+ \ + " - only keep the minimum for a reinstallation" + print >>out, yellow(" -e, --exclude-file=<path>")+ \ + " - path to the exclusion file" + print >>out, yellow(" -i, --interactive")+ \ + " - ask confirmation before deletions" + print >>out, yellow(" -n, --package-names")+ \ + " - protect all versions (when --destructive)" + print >>out, yellow(" -p, --pretend")+ \ + " - only display what would be cleaned" + print >>out, yellow(" -q, --quiet")+ \ + " - be as quiet as possible" + print >>out, yellow(" -t, --time-limit=<time>")+ \ + " - don't delete files modified since "+yellow("<time>") + print >>out, " "+yellow("<time>"), "is a duration: \"1y\" is"+ \ + " \"one year\", \"2w\" is \"two weeks\", etc. " + print >>out, " "+"Units are: y (years), m (months), w (weeks), "+ \ + "d (days) and h (hours)." + print >>out, yellow(" -h, --help")+ \ + " - display the help screen" + print >>out, yellow(" -V, --version")+ \ + " - display version info" + print >>out + if error == 'actions' or help == 'all': + print >>out, "Available", green("actions")+":" + print >>out, green(" packages")+ \ + " - clean outdated binary packages from:" + print >>out, " ",teal(pkgdir) + print >>out, green(" distfiles")+ \ + " - clean outdated packages sources files from:" + print >>out, " ",teal(distdir) + print >>out + if error in ('packages-options','merged-packages-options') \ + or help in ('all','packages'): + print >>out, "Available", yellow("options"),"for the", \ + green("packages"),"action:" + print >>out, yellow(" NONE :)") + print >>out + if error in ('distfiles-options', 'merged-distfiles-options') \ + or help in ('all','distfiles'): + print >>out, "Available", yellow("options"),"for the", \ + green("distfiles"),"action:" + print >>out, yellow(" -f, --fetch-restricted")+ \ + " - protect fetch-restricted files (when --destructive)" + print >>out, yellow(" -s, --size-limit=<size>")+ \ + " - don't delete distfiles bigger than "+yellow("<size>") + print >>out, " "+yellow("<size>"), "is a size specification: "+ \ + "\"10M\" is \"ten megabytes\", \"200K\" is" + print >>out, " "+"\"two hundreds kilobytes\", etc. Units are: "+ \ + "G, M, K and B." + print >>out + print >>out, "More detailed instruction can be found in", \ + turquoise("`man %s`" % __productname__) + + +############################################################################### +# einfo: display an info message depending on a color mode +def einfo(message="", nocolor=False): + if not nocolor: prefix = " "+green('*') + else: prefix = ">>>" + print prefix,message + + +############################################################################### +# eerror: display an error depending on a color mode +def eerror(message="", nocolor=False): + if not nocolor: prefix = " "+red('*') + else: prefix = "!!!" + print >>sys.stderr,prefix,message + + +############################################################################### +# eprompt: display a user question depending on a color mode. +def eprompt(message, nocolor=False): + if not nocolor: prefix = " "+red('>')+" " + else: prefix = "??? " + sys.stdout.write(prefix+message) + sys.stdout.flush() + + +############################################################################### +# prettySize: integer -> byte/kilo/mega/giga converter. Optionnally justify the +# result. Output is a string. +def prettySize(size,justify=False): + units = [" G"," M"," K"," B"] + approx = 0 + while len(units) and size >= 1000: + approx = 1 + size = size / 1024. + units.pop() + sizestr = fpformat.fix(size,approx)+units[-1] + if justify: + sizestr = " " + blue("[ ") + " "*(7-len(sizestr)) \ + + green(sizestr) + blue(" ]") + return sizestr + + +############################################################################### +# yesNoAllPrompt: print a prompt until user answer in yes/no/all. Return a +# boolean for answer, and also may affect the 'accept_all' option. +# Note: i gave up with getch-like functions, to much bugs in case of escape +# sequences. Back to raw_input. +def yesNoAllPrompt(myoptions,message="Do you want to proceed?"): + user_string="xxx" + while not user_string.lower() in ["","y","n","a","yes","no","all"]: + eprompt(message+" [Y/n/a]: ", myoptions['nocolor']) + user_string = raw_input() + if user_string.lower() in ["a","all"]: + myoptions['accept_all'] = True + myanswer = user_string.lower() in ["","y","a","yes","all"] + return myanswer + + +############################################################################### +# ParseArgsException: for parseArgs() -> main() communication +class ParseArgsException(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + + +############################################################################### +# parseSize: convert a file size "Xu" ("X" is an integer, and "u" in [G,M,K,B]) +# into an integer (file size in Bytes). Raises ParseArgsException('size') in +# case of failure. +def parseSize(size): + myunits = { \ + 'G': (1024**3), \ + 'M': (1024**2), \ + 'K': 1024, \ + 'B': 1 \ + } + try: + mymatch = re.match(r"^(?P<value>\d+)(?P<unit>[GMKBgmkb])?$",size) + mysize = int(mymatch.group('value')) + if mymatch.group('unit'): + mysize *= myunits[mymatch.group('unit').capitalize()] + except: + raise ParseArgsException('size') + return mysize + + +############################################################################### +# parseTime: convert a duration "Xu" ("X" is an int, and "u" a time unit in +# [Y,M,W,D,H]) into an integer which is a past EPOCH date. +# Raises ParseArgsException('time') in case of failure. +# (yep, big approximations inside... who cares?) +def parseTime(timespec): + myunits = {'H' : (60 * 60)} + myunits['D'] = myunits['H'] * 24 + myunits['W'] = myunits['D'] * 7 + myunits['M'] = myunits['D'] * 30 + myunits['Y'] = myunits['D'] * 365 + try: + # parse the time specification + mymatch = re.match(r"^(?P<value>\d+)(?P<unit>[YMWDHymwdh])?$",timespec) + myvalue = int(mymatch.group('value')) + if not mymatch.group('unit'): myunit = 'D' + else: myunit = mymatch.group('unit').capitalize() + except: raise ParseArgsException('time') + # calculate the limit EPOCH date + mytime = time.time() - (myvalue * myunits[myunit]) + return mytime + + +############################################################################### +# parseCmdLine: parse the command line arguments. Raise exceptions on errors or +# non-action modes (help/version). Returns an action, and affect the options +# dict. +def parseArgs(myoptions={}): + + # local function for interpreting command line options + # and setting myoptions accordingly + def optionSwitch(myoption,opts,action=None): + return_code = True + for o, a in opts: + if o in ("-h", "--help"): + if action: raise ParseArgsException('help-'+action) + else: raise ParseArgsException('help') + elif o in ("-V", "--version"): + raise ParseArgsException('version') + elif o in ("-C", "--nocolor"): + myoptions['nocolor'] = True + nocolor() + elif o in ("-d", "--destructive"): + myoptions['destructive'] = True + elif o in ("-i", "--interactive") and not myoptions['pretend']: + myoptions['interactive'] = True + elif o in ("-p", "--pretend"): + myoptions['pretend'] = True + myoptions['interactive'] = False + elif o in ("-q", "--quiet"): + myoptions['quiet'] = True + elif o in ("-t", "--time-limit"): + myoptions['time-limit'] = parseTime(a) + elif o in ("-e", "--exclude-file"): + myoptions['exclude-file'] = a + elif o in ("-n", "--package-names"): + myoptions['package-names'] = True + elif o in ("-f", "--fetch-restricted"): + myoptions['fetch-restricted'] = True + elif o in ("-s", "--size-limit"): + myoptions['size-limit'] = parseSize(a) + else: return_code = False + # sanity check of --destructive only options: + for myopt in ('fetch-restricted', 'package-names'): + if (not myoptions['destructive']) and myoptions[myopt]: + if not myoptions['quiet']: + eerror("--%s only makes sense in --destructive mode." \ + % myopt, myoptions['nocolor']) + myoptions[myopt] = False + return return_code + + # here are the different allowed command line options (getopt args) + getopt_options = {'short':{}, 'long':{}} + getopt_options['short']['global'] = "Cdipqe:t:nhV" + getopt_options['long']['global'] = ["nocolor", "destructive", \ + "interactive", "pretend", "quiet", "exclude-file=", "time-limit=", \ + "package-names", "help", "version"] + getopt_options['short']['distfiles'] = "fs:" + getopt_options['long']['distfiles'] = ["fetch-restricted", "size-limit="] + getopt_options['short']['packages'] = "" + getopt_options['long']['packages'] = [""] + # set default options, except 'nocolor', which is set in main() + myoptions['interactive'] = False + myoptions['pretend'] = False + myoptions['quiet'] = False + myoptions['accept_all'] = False + myoptions['destructive'] = False + myoptions['time-limit'] = 0 + myoptions['package-names'] = False + myoptions['fetch-restricted'] = False + myoptions['size-limit'] = 0 + # if called by a well-named symlink, set the acction accordingly: + myaction = None + if os.path.basename(sys.argv[0]) in \ + (__productname__+'-pkg', __productname__+'-packages'): + myaction = 'packages' + elif os.path.basename(sys.argv[0]) in \ + (__productname__+'-dist', __productname__+'-distfiles'): + myaction = 'distfiles' + # prepare for the first getopt + if myaction: + short_opts = getopt_options['short']['global'] \ + + getopt_options['short'][myaction] + long_opts = getopt_options['long']['global'] \ + + getopt_options['long'][myaction] + opts_mode = 'merged-'+myaction + else: + short_opts = getopt_options['short']['global'] + long_opts = getopt_options['long']['global'] + opts_mode = 'global' + # apply getopts to command line, show partial help on failure + try: opts, args = getopt.getopt(sys.argv[1:], short_opts, long_opts) + except: raise ParseArgsException(opts_mode+'-options') + # set myoptions accordingly + optionSwitch(myoptions,opts,action=myaction) + # if action was already set, there should be no more args + if myaction and len(args): raise ParseArgsException(opts_mode+'-options') + # if action was set, there is nothing left to do + if myaction: return myaction + # So, we are in "eclean --foo action --bar" mode. Parse remaining args... + # Only two actions are allowed: 'packages' and 'distfiles'. + if not len(args) or not args[0] in ('packages','distfiles'): + raise ParseArgsException('actions') + myaction = args.pop(0) + # parse the action specific options + try: opts, args = getopt.getopt(args, \ + getopt_options['short'][myaction], \ + getopt_options['long'][myaction]) + except: raise ParseArgsException(myaction+'-options') + # set myoptions again, for action-specific options + optionSwitch(myoptions,opts,action=myaction) + # any remaning args? Then die! + if len(args): raise ParseArgsException(myaction+'-options') + # returns the action. Options dictionary is modified by side-effect. + return myaction + +############################################################################### +# isValidCP: check wether a string is a valid cat/pkg-name +# This is for 2.0.51 vs. CVS HEAD compatibility, i've not found any function +# for that which would exists in both. Weird... +def isValidCP(cp): + if not '/' in cp: return False + try: portage.cpv_getkey(cp+"-0") + except: return False + else: return True + + +############################################################################### +# ParseExcludeFileException: for parseExcludeFile() -> main() communication +class ParseExcludeFileException(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + + +############################################################################### +# parseExcludeFile: parses an exclusion file, returns an exclusion dictionnary +# Raises ParseExcludeFileException in case of fatal error. +def parseExcludeFile(filepath): + excl_dict = { \ + 'categories':{}, \ + 'packages':{}, \ + 'anti-packages':{}, \ + 'garbage':{} } + try: file = open(filepath,"r") + except IOError: + raise ParseExcludeFileException("Could not open exclusion file.") + filecontents = file.readlines() + file.close() + cat_re = re.compile('^(?P<cat>[a-zA-Z0-9]+-[a-zA-Z0-9]+)(/\*)?$') + cp_re = re.compile('^(?P<cp>[-a-zA-Z0-9_]+/[-a-zA-Z0-9_]+)$') + for line in filecontents: + line = line.strip() + if not len(line): continue + if line[0] == '#': continue + try: mycat = cat_re.match(line).group('cat') + except: pass + else: + if not mycat in portage.settings.categories: + raise ParseExcludeFileException("Invalid category: "+mycat) + excl_dict['categories'][mycat] = None + continue + dict_key = 'packages' + if line[0] == '!': + dict_key = 'anti-packages' + line = line[1:] + try: + mycp = cp_re.match(line).group('cp') + if isValidCP(mycp): + excl_dict[dict_key][mycp] = None + continue + else: raise ParseExcludeFileException("Invalid cat/pkg: "+mycp) + except: pass + #raise ParseExcludeFileException("Invalid line: "+line) + try: + excl_dict['garbage'][line] = re.compile(line) + except: + try: + excl_dict['garbage'][line] = re.compile(re.escape(line)) + except: + raise ParseExcludeFileException("Invalid file name/regular expression: "+line) + return excl_dict + + +############################################################################### +# exclDictExpand: returns a dictionary of all CP from porttree which match +# the exclusion dictionary +def exclDictExpand(excl_dict): + mydict = {} + if 'categories' in excl_dict: + # XXX: i smell an access to something which is really out of API... + for mytree in portage.portdb.porttrees: + for mycat in excl_dict['categories']: + for mypkg in listdir(os.path.join(mytree,mycat),ignorecvs=1): + mydict[mycat+'/'+mypkg] = None + if 'packages' in excl_dict: + for mycp in excl_dict['packages']: + mydict[mycp] = None + if 'anti-packages' in excl_dict: + for mycp in excl_dict['anti-packages']: + if mycp in mydict: + del mydict[mycp] + return mydict + + +############################################################################### +# exclDictMatch: checks whether a CP matches the exclusion rules +def exclDictMatch(excl_dict,pkg): + if 'anti-packages' in excl_dict \ + and pkg in excl_dict['anti-packages']: + return False + if 'packages' in excl_dict \ + and pkg in excl_dict['packages']: + return True + mycat = pkg.split('/')[0] + if 'categories' in excl_dict \ + and mycat in excl_dict['categories']: + return True + return False + + +############################################################################### +# findDistfiles: find all obsolete distfiles. +# XXX: what about cvs ebuilds? i should install some to see where it goes... +def findDistfiles( \ + exclude_dict={}, \ + destructive=False,\ + fetch_restricted=False, \ + package_names=False, \ + time_limit=0, \ + size_limit=0): + # this regexp extracts files names from SRC_URI. It is not very precise, + # but we don't care (may return empty strings, etc.), since it is fast. + file_regexp = re.compile('([a-zA-Z0-9_,\.\-\+\~]*)[\s\)]') + clean_dict = {} + keep = [] + pkg_dict = {} + + # create a big CPV->SRC_URI dict of packages whose distfiles should be kept + if (not destructive) or fetch_restricted: + # list all CPV from portree (yeah, that takes time...) + for package in portage.portdb.cp_all(): + for my_cpv in portage.portdb.cp_list(package): + # get SRC_URI and RESTRICT from aux_get + try: (src_uri,restrict) = \ + portage.portdb.aux_get(my_cpv,["SRC_URI","RESTRICT"]) + except KeyError: continue + # keep either all or fetch-restricted only + if (not destructive) or ('fetch' in restrict): + pkg_dict[my_cpv] = src_uri + if destructive: + if not package_names: + # list all CPV from vartree + pkg_list = portage.db[portage.root]["vartree"].dbapi.cpv_all() + else: + # list all CPV from portree for CP in vartree + pkg_list = [] + for package in portage.db[portage.root]["vartree"].dbapi.cp_all(): + pkg_list += portage.portdb.cp_list(package) + for my_cp in exclDictExpand(exclude_dict): + # add packages from the exclude file + pkg_list += portage.portdb.cp_list(my_cp) + for my_cpv in pkg_list: + # skip non-existing CPV (avoids ugly aux_get messages) + if not portage.portdb.cpv_exists(my_cpv): continue + # get SRC_URI from aux_get + try: pkg_dict[my_cpv] = \ + portage.portdb.aux_get(my_cpv,["SRC_URI"])[0] + except KeyError: continue + del pkg_list + + # create a dictionary of files which should be deleted + for file in os.listdir(distdir): + filepath = os.path.join(distdir, file) + try: file_stat = os.stat(filepath) + except: continue + if not stat.S_ISREG(file_stat[stat.ST_MODE]): continue + if size_limit and (file_stat[stat.ST_SIZE] >= size_limit): + continue + if time_limit and (file_stat[stat.ST_MTIME] >= time_limit): + continue + if 'garbage' in exclude_dict: + # Try to match file name directly + if file in exclude_dict['garbage']: + file_match = True + # See if file matches via regular expression matching + else: + file_match = False + for file_entry in exclude_dict['garbage']: + if exclude_dict['garbage'][file_entry].match(file): + file_match = True + break + + if file_match: + continue + # this is a candidate for cleaning + clean_dict[file]=[filepath] + # remove files owned by some protected packages + for my_cpv in pkg_dict: + for file in file_regexp.findall(pkg_dict[my_cpv]+"\n"): + if file in clean_dict: + del clean_dict[file] + # no need to waste IO time if there is nothing left to clean + if not len(clean_dict): return clean_dict + return clean_dict + + +############################################################################### +# findPackages: find all obsolete binary packages. +# XXX: packages are found only by symlinks. Maybe i should also return .tbz2 +# files from All/ that have no corresponding symlinks. +def findPackages( \ + exclude_dict={}, \ + destructive=False, \ + time_limit=0, \ + package_names=False): + clean_dict = {} + # create a full package dictionnary + for root, dirs, files in os.walk(pkgdir): + if root[-3:] == 'All': continue + for file in files: + if not file[-5:] == ".tbz2": + # ignore non-tbz2 files + continue + path = os.path.join(root, file) + category = os.path.split(root)[-1] + cpv = category+"/"+file[:-5] + mystat = os.lstat(path) + if time_limit and (mystat[stat.ST_MTIME] >= time_limit): + # time-limit exclusion + continue + # dict is cpv->[files] (2 files in general, because of symlink) + clean_dict[cpv] = [path] + #if os.path.islink(path): + if stat.S_ISLNK(mystat[stat.ST_MODE]): + clean_dict[cpv].append(os.path.realpath(path)) + # keep only obsolete ones + if destructive: + mydbapi = portage.db[portage.root]["vartree"].dbapi + if package_names: cp_all = dict.fromkeys(mydbapi.cp_all()) + else: cp_all = {} + else: + mydbapi = portage.db[portage.root]["porttree"].dbapi + cp_all = {} + for mycpv in clean_dict.keys(): + if exclDictMatch(exclude_dict,portage.cpv_getkey(mycpv)): + # exclusion because of the exclude file + del clean_dict[mycpv] + continue + if mydbapi.cpv_exists(mycpv): + # exclusion because pkg still exists (in porttree or vartree) + del clean_dict[mycpv] + continue + if portage.cpv_getkey(mycpv) in cp_all: + # exlusion because of --package-names + del clean_dict[mycpv] + + return clean_dict + + +############################################################################### +# doCleanup: takes a dictionnary {'display name':[list of files]}. Calculate +# size of each entry for display, prompt user if needed, delete files if needed +# and return the total size of files that [have been / would be] deleted. +def doCleanup(clean_dict,action,myoptions): + # define vocabulary of this action + if action == 'distfiles': file_type = 'file' + else: file_type = 'binary package' + # sorting helps reading + clean_keys = clean_dict.keys() + clean_keys.sort() + clean_size = 0 + # clean all entries one by one + for mykey in clean_keys: + key_size = 0 + for file in clean_dict[mykey]: + # get total size for an entry (may be several files, and + # symlinks count zero) + if os.path.islink(file): continue + try: key_size += os.path.getsize(file) + except: eerror("Could not read size of "+file, \ + myoptions['nocolor']) + if not myoptions['quiet']: + # pretty print mode + print prettySize(key_size,True),teal(mykey) + elif myoptions['pretend'] or myoptions['interactive']: + # file list mode + for file in clean_dict[mykey]: print file + #else: actually delete stuff, but don't print anything + if myoptions['pretend']: clean_size += key_size + elif not myoptions['interactive'] \ + or myoptions['accept_all'] \ + or yesNoAllPrompt(myoptions, \ + "Do you want to delete this " \ + + file_type+"?"): + # non-interactive mode or positive answer. + # For each file,... + for file in clean_dict[mykey]: + # ...get its size... + filesize = 0 + if not os.path.exists(file): continue + if not os.path.islink(file): + try: filesize = os.path.getsize(file) + except: eerror("Could not read size of "\ + +file, myoptions['nocolor']) + # ...and try to delete it. + try: os.unlink(file) + except: eerror("Could not delete "+file, \ + myoptions['nocolor']) + # only count size if successfully deleted + else: clean_size += filesize + # return total size of deleted or to delete files + return clean_size + + +############################################################################### +# doAction: execute one action, ie display a few message, call the right find* +# function, and then call doCleanup with its result. +def doAction(action,myoptions,exclude_dict={}): + # define vocabulary for the output + if action == 'packages': files_type = "binary packages" + else: files_type = "distfiles" + # find files to delete, depending on the action + if not myoptions['quiet']: + einfo("Building file list for "+action+" cleaning...", \ + myoptions['nocolor']) + if action == 'packages': + clean_dict = findPackages( \ + exclude_dict=exclude_dict, \ + destructive=myoptions['destructive'], \ + package_names=myoptions['package-names'], \ + time_limit=myoptions['time-limit']) + else: + clean_dict = findDistfiles( \ + exclude_dict=exclude_dict, \ + destructive=myoptions['destructive'], \ + fetch_restricted=myoptions['fetch-restricted'], \ + package_names=myoptions['package-names'], \ + time_limit=myoptions['time-limit'], \ + size_limit=myoptions['size-limit']) + # actually clean files if something was found + if len(clean_dict.keys()): + # verbose pretend message + if myoptions['pretend'] and not myoptions['quiet']: + einfo("Here are "+files_type+" that would be deleted:", \ + myoptions['nocolor']) + # verbose non-pretend message + elif not myoptions['quiet']: + einfo("Cleaning "+files_type+"...",myoptions['nocolor']) + # do the cleanup, and get size of deleted files + clean_size = doCleanup(clean_dict,action,myoptions) + # vocabulary for final message + if myoptions['pretend']: verb = "would be" + else: verb = "has been" + # display freed space + if not myoptions['quiet']: + einfo("Total space that "+verb+" freed in " \ + + action + " directory: " \ + + red(prettySize(clean_size)), \ + myoptions['nocolor']) + # nothing was found, return + elif not myoptions['quiet']: + einfo("Your "+action+" directory was already clean.", \ + myoptions['nocolor']) + + +############################################################################### +# main: parse command line and execute all actions +def main(): + # set default options + myoptions = {} + myoptions['nocolor'] = port_settings["NOCOLOR"] in ('yes','true') \ + and sys.stdout.isatty() + if myoptions['nocolor']: nocolor() + # parse command line options and actions + try: myaction = parseArgs(myoptions) + # filter exception to know what message to display + except ParseArgsException, e: + if e.value == 'help': + printUsage(help='all') + sys.exit(0) + elif e.value[:5] == 'help-': + printUsage(help=e.value[5:]) + sys.exit(0) + elif e.value == 'version': + printVersion() + sys.exit(0) + else: + printUsage(e.value) + sys.exit(2) + # parse the exclusion file + if not 'exclude-file' in myoptions: + my_exclude_file = "/etc/%s/%s.exclude" % (__productname__ , myaction) + if os.path.isfile(my_exclude_file): + myoptions['exclude-file'] = my_exclude_file + if 'exclude-file' in myoptions: + try: exclude_dict = parseExcludeFile(myoptions['exclude-file']) + except ParseExcludeFileException, e: + eerror(e, myoptions['nocolor']) + eerror("Invalid exclusion file: %s" % myoptions['exclude-file'], \ + myoptions['nocolor']) + eerror("See format of this file in `man %s`" % __productname__, \ + myoptions['nocolor']) + sys.exit(1) + else: exclude_dict={} + # security check for non-pretend mode + if not myoptions['pretend'] and portage.secpass == 0: + eerror("Permission denied: you must be root or belong to the portage group.", \ + myoptions['nocolor']) + sys.exit(1) + # execute action + doAction(myaction, myoptions, exclude_dict=exclude_dict) + + +############################################################################### +# actually call main() if launched as a script +if __name__ == "__main__": + try: main() + except KeyboardInterrupt: + print "Aborted." + sys.exit(130) + sys.exit(0) + |