home *** CD-ROM | disk | FTP | other *** search
- #!/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 string, re
- import time
- import getopt
- import fpformat
- import signal
- sys.path.insert(0,'/usr/lib/portage/pym')
- import portage
- 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 disfiles 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[string.capitalize(mymatch.group('unit'))]
- 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 = string.capitalize(mymatch.group('unit'))
- 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 != 2:
- eerror("Permission denied: you must be root.", \
- 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)
-
-