All pastes #1278441 Raw Edit

clean_dist_cache

public python v1 · immutable
#1278441 ·published 2008-12-07 01:52 UTC
rendered paste body
#!/usr/bin/pythonimport portage, portage_dep, os, sys, getoptfrom portage_versions import *VERSION = "0.1"def get_keyword_cpvs(cp, keywords):	""" return a list of package versions marked with keywords. """	matched_cpvs = []	for cpv in portage.portdb.cp_list(cp):		cpv_keywords = portage.portdb.aux_get(cpv, ["KEYWORDS"])[0].split(' ')		for keyword in keywords:			if keyword in cpv_keywords:				matched_cpvs.append(cpv)				break	return matched_cpvs			def order_cpvs(cpvs):	""" compare package cpvs and order low to high """	low_to_high = {}	index = 0	index_len = len(cpvs) - 1		ct = 0	while len(cpvs) != 0:		tmp_lowest = cpvs[ct]		while ct < len(cpvs):			if pkgcmp(pkgsplit(tmp_lowest), pkgsplit(cpvs[ct])) == 1:				tmp_lowest = cpvs[ct]			ct = ct + 1		low_to_high[index] = tmp_lowest		cpvs.remove(tmp_lowest)		index = index + 1		ct = 0	return low_to_highdef get_distfiles(cpv, keywords):	""" return a list of distfiles used by the package version according	to it's keywords. """	distfiles = []	paths = portage.portdb.aux_get(cpv, ["SRC_URI"])	for path in paths:		parsed = portage_dep.paren_reduce(path)		new_paths = portage_dep.use_reduce(parsed, keywords)		# SRC_URI can return several unpredictable nested lists hence		# the numerous and ugly for and while loops.		for new_path in new_paths:			if type(new_path) == list:				for n in new_path:					if type(n) == str:						distfiles.append(os.path.basename(n))					else:						while type(n) == list:							n = n[-1]						distfiles.append(os.path.basename(n))			else:				distfiles.append(os.path.basename(new_path))	return distfilesdef usage():	myprog = os.path.basename(sys.argv[0])	print myprog+": Selectively clean old or unused distfiles on gentoo systems to free up disk space."	print ""	print "Usage: "+myprog+" --below-stable=[value] --above-stable=[value] [--keywords=[keywords]] [--destdir=[path]] [--yes]"	print ""	print "Options:"	print " -h, --help				Display this help message and exit."	print " -b [value], --below-stable=[value]	Number of package versions below the current stable to consider. 'all' may be used."	print " -a [value], --above-stable=[value]	Number of package versions above the current stable to consider. 'all' may be used."	print " -k [keywords], --keywords=[keywords]	Consider keywords. Defaults to ACCEPT_KEYWORDS portage setting."	print " -d [path], --distdir=[path]		Override DISTDIR portage setting with [path]."	print " -y, --yes				Automatically answer yes to all prompts."	print " -v, --version				Display version information and exit."	print ""class Options:	def __init__(self):		self.long_opts = ["help", "below-stable=", "above-stable=", "keywords=", "distdir=", "yes", "version"]		self.short_opts = "hb:a:k:d:yv"	def parse(self):		try:			self.opts, self.args = getopt.getopt(sys.argv[1:], self.short_opts, self.long_opts)		except getopt.GetoptError, e:			sys.stderr.write("Error: "+str(e)+"\n\n")			usage()			sys.exit(1)		self.help		= 0		self.below_stable	= ""		self.above_stable	= ""		self.keywords		= []		self.distdir		= ""		self.yes 		= 0		self.version		= 0		for o, a in self.opts:			if o in ("-h", "--help"):				usage()				sys.exit(0)			if o in ("-b", "--below-stable"):				self.below_stable = a			if o in ("-a", "--above-stable"):				self.above_stable = a			if o in ("-k", "--keywords"):				self.keywords.extend(a.split(' '))			if o in ("-d", "--distdir"):				self.distdir = a			if o in ("-y", "--yes"):				self.yes = 1			if o in ("-v", "--version"):				self.version = VERSION				print self.version				sys.exit(0)	def validate(self):		if self.below_stable == "" or self.above_stable == "":			sys.stderr.write("Error: You must specify --below-stable= and --above-stable= options!\n\n")			usage()			sys.exit(1)		if self.below_stable != "all":			try:				self.below_stable = int(self.below_stable)			except:				sys.stderr.write("Error: '"+self.below+"' is not an integer!\n")				sys.exit(1)		if self.above_stable != "all":			try:				self.above_stable = int(self.above_stable)			except:				sys.stderr.write("Error: '"+self.above_stable+"' is not an integer!\n")				sys.exit(1)		if self.distdir != "":			if not os.path.isdir(self.distdir):				sys.stderr.write("Error: distdir '"+self.distdir+"' does not exist!\n")				sys.exit(1)if __name__ == "__main__":	if os.getuid() != 0:		sys.stderr.write("You must be root to run this program.\n")		sys.exit(1)		# get and validate options	options = Options()	options.parse()	options.validate()	# assign options	if options.keywords == []: options.keywords.extend(portage.settings["ACCEPT_KEYWORDS"].split(' '))	if options.distdir == "": options.distdir = portage.settings["DISTDIR"]	# local variables	total_freed_space = 0	remove_list = []	M = 1048576 # Megabytes	# iterate one package at a time. cp: package category, cpv: package version.	for cp in portage.portdb.cp_all():		excluded_cpvs = []		cpvs = portage.portdb.cp_list(cp)		stable_cpvs = get_keyword_cpvs(cp, options.keywords)		if len(stable_cpvs) == 0: continue		# rearrange packages from lowest version to highest in a dict so we		# can easily reference which versions to keep and delete.		ordered_cpvs = order_cpvs(cpvs)		del(cpvs)		current_stable_cpv = stable_cpvs[-1]		for i in range(len(ordered_cpvs)):			if ordered_cpvs.get(i) == current_stable_cpv:				current_stable_cpv_index = i				if options.below_stable == "all":			below_stable_index = 0		else:			below_stable_index = (current_stable_cpv_index - options.below_stable)		if options.above_stable == "all":			above_stable_index = (len(ordered_cpvs) - 1)		else:			above_stable_index = (current_stable_cpv_index + options.above_stable)		ct = below_stable_index		while ct <= above_stable_index:			try: 				if ordered_cpvs[ct] not in excluded_cpvs:					excluded_cpvs.append(ordered_cpvs[ct])			except KeyError: pass			ct = ct + 1				# single out versions not satisfied above and add to list for removal		for key in ordered_cpvs:			cpv = ordered_cpvs.get(key)			if cpv not in excluded_cpvs:				distfiles = get_distfiles(cpv, options.keywords)				for distfile in distfiles:					if distfile != '':						distfile_path = os.path.join(options.distdir, distfile)						if os.path.isfile(distfile_path):							if distfile_path not in remove_list:								remove_list.append(distfile_path)	if len(remove_list) == 0:		print "Nothing to clean."		sys.exit(0)	# report our findings	max_columns = 70	for distfile_path in remove_list:			whole = (os.stat(distfile_path)[6] / M)		remain = (os.stat(distfile_path)[6] % M)		size = str(round((whole + float("."+str(remain))), 1))		format_distfile_path = distfile_path+":"		format_size = "["+size+"M]"		fill_spaces = " "*(max_columns - len(format_distfile_path))		print format_distfile_path+fill_spaces+format_size		total_freed_space = total_freed_space + float(size)	print "\nTotal Size: "+str(round(total_freed_space, 1))+"M\n"	# execute removal if desired.	sys.stdout.write("Remove files? [Y/n]")	if options.yes:		response = 'y'	else:		response = raw_input()		response = response.lower()	if response == 'y' or response == '':		for distfile_path in remove_list:			print "Removing: "+distfile_path			try:				os.remove(distfile_path)			except Exception, e:				sys.stderr.write("Could not remove '"+distfile_path+"': "+str(e)+"\n")	sys.exit(0)