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