1#!/usr/bin/env python 2 3# 4# Copyright (c) 2009-2010 Evert Pot 5# All rights reserved. 6# http://www.rooftopsolutions.nl/ 7# 8# This utility is distributed along with SabreDAV 9# license: http://sabre.io/license/ Modified BSD License 10 11import os 12from optparse import OptionParser 13import time 14 15def getfreespace(path): 16 stat = os.statvfs(path) 17 return stat.f_frsize * stat.f_bavail 18 19def getbytesleft(path,threshold): 20 return getfreespace(path)-threshold 21 22def run(cacheDir, threshold, sleep=5, simulate=False, min_erase = 0): 23 24 bytes = getbytesleft(cacheDir,threshold) 25 if (bytes>0): 26 print "Bytes to go before we hit threshold:", bytes 27 else: 28 print "Threshold exceeded with:", -bytes, "bytes" 29 dir = os.listdir(cacheDir) 30 dir2 = [] 31 for file in dir: 32 path = cacheDir + '/' + file 33 dir2.append({ 34 "path" : path, 35 "atime": os.stat(path).st_atime, 36 "size" : os.stat(path).st_size 37 }) 38 39 dir2.sort(lambda x,y: int(x["atime"]-y["atime"])) 40 41 filesunlinked = 0 42 gainedspace = 0 43 44 # Left is the amount of bytes that need to be freed up 45 # The default is the 'min_erase setting' 46 left = min_erase 47 48 # If the min_erase setting is lower than the amount of bytes over 49 # the threshold, we use that number instead. 50 if left < -bytes : 51 left = -bytes 52 53 print "Need to delete at least:", left; 54 55 for file in dir2: 56 57 # Only deleting files if we're not simulating 58 if not simulate: os.unlink(file["path"]) 59 left = int(left - file["size"]) 60 gainedspace = gainedspace + file["size"] 61 filesunlinked = filesunlinked + 1 62 63 if(left<0): 64 break 65 66 print "%d files deleted (%d bytes)" % (filesunlinked, gainedspace) 67 68 69 time.sleep(sleep) 70 71 72 73def main(): 74 parser = OptionParser( 75 version="naturalselection v0.3", 76 description="Cache directory manager. Deletes cache entries based on accesstime and free space thresholds.\n" + 77 "This utility is distributed alongside SabreDAV.", 78 usage="usage: %prog [options] cacheDirectory", 79 ) 80 parser.add_option( 81 '-s', 82 dest="simulate", 83 action="store_true", 84 help="Don't actually make changes, but just simulate the behaviour", 85 ) 86 parser.add_option( 87 '-r','--runs', 88 help="How many times to check before exiting. -1 is infinite, which is the default", 89 type="int", 90 dest="runs", 91 default=-1 92 ) 93 parser.add_option( 94 '-n','--interval', 95 help="Sleep time in seconds (default = 5)", 96 type="int", 97 dest="sleep", 98 default=5 99 ) 100 parser.add_option( 101 '-l','--threshold', 102 help="Threshold in bytes (default = 10737418240, which is 10GB)", 103 type="int", 104 dest="threshold", 105 default=10737418240 106 ) 107 parser.add_option( 108 '-m', '--min-erase', 109 help="Minimum number of bytes to erase when the threshold is reached. " + 110 "Setting this option higher will reduce the amount of times the cache directory will need to be scanned. " + 111 "(the default is 1073741824, which is 1GB.)", 112 type="int", 113 dest="min_erase", 114 default=1073741824 115 ) 116 117 options,args = parser.parse_args() 118 if len(args)<1: 119 parser.error("This utility requires at least 1 argument") 120 cacheDir = args[0] 121 122 print "Natural Selection" 123 print "Cache directory:", cacheDir 124 free = getfreespace(cacheDir); 125 print "Current free disk space:", free 126 127 runs = options.runs; 128 while runs!=0 : 129 run( 130 cacheDir, 131 sleep=options.sleep, 132 simulate=options.simulate, 133 threshold=options.threshold, 134 min_erase=options.min_erase 135 ) 136 if runs>0: 137 runs = runs - 1 138 139if __name__ == '__main__' : 140 main() 141