| 1 | """ |
|---|
| 2 | sizes.cobra |
|---|
| 3 | |
|---|
| 4 | The most effective technique to free up disk space on a drive is to look in the |
|---|
| 5 | directories that consume the most space. You may find entire directories that |
|---|
| 6 | can be deleted or excessively large files that are no longer needed. The first |
|---|
| 7 | time you do this, you might downsize the space consumed by a large factor with |
|---|
| 8 | only a few minutes of effort. Credit goes to the old "Dark Forest" application |
|---|
| 9 | on the old NeXTstep platform for introducing me to this approach. |
|---|
| 10 | |
|---|
| 11 | This command line utility scans the current directory and its files for their |
|---|
| 12 | sizes, including all subdirectories. It then reports the total size and shows |
|---|
| 13 | the largest directories and files first. You can use this report to look for |
|---|
| 14 | large things to delete and rerun it as you progress. To run: |
|---|
| 15 | |
|---|
| 16 | > cd \to\path\of\interest |
|---|
| 17 | > cobra \path\to\sizes.cobra |
|---|
| 18 | |
|---|
| 19 | Unix users get a similar result with something like "du -k | sort -nr" |
|---|
| 20 | |
|---|
| 21 | As an example, I ran `sizes` on a directory and it reported: |
|---|
| 22 | |
|---|
| 23 | 203658 directories and files read |
|---|
| 24 | 180.30 seconds to read |
|---|
| 25 | 21,297.34 MB |
|---|
| 26 | |
|---|
| 27 | After spending 20 minutes cleaning up the biggest directories and files |
|---|
| 28 | that it listed, it then reported this: |
|---|
| 29 | |
|---|
| 30 | 186345 directories and files read |
|---|
| 31 | 49.95 seconds to read |
|---|
| 32 | 8,809.15 MB |
|---|
| 33 | """ |
|---|
| 34 | |
|---|
| 35 | class FileNode implements IComparable |
|---|
| 36 | """ |
|---|
| 37 | One of these will be created for every directory and file, and put into a |
|---|
| 38 | list that can be sorted. |
|---|
| 39 | """ |
|---|
| 40 | |
|---|
| 41 | test |
|---|
| 42 | a = FileNode('a', 100) |
|---|
| 43 | assert a.pathName == 'a' |
|---|
| 44 | assert a.numBytes == 100 |
|---|
| 45 | assert a.compareTo(nil) == 1 |
|---|
| 46 | b = FileNode('b', 200) |
|---|
| 47 | assert a.compareTo(b) < 0 |
|---|
| 48 | assert b.compareTo(a) > 0 |
|---|
| 49 | c = FileNode('c', 100) |
|---|
| 50 | assert a.compareTo(c) == 0 |
|---|
| 51 | |
|---|
| 52 | var _pathName as String |
|---|
| 53 | var _numBytes as decimal |
|---|
| 54 | |
|---|
| 55 | cue init(pathName as String, numBytes as decimal) |
|---|
| 56 | _pathName = pathName |
|---|
| 57 | _numBytes = numBytes |
|---|
| 58 | |
|---|
| 59 | get pathName from _pathName |
|---|
| 60 | |
|---|
| 61 | get numBytes from _numBytes |
|---|
| 62 | |
|---|
| 63 | def compareTo(obj as Object?) as int |
|---|
| 64 | require |
|---|
| 65 | obj is nil or obj inherits FileNode |
|---|
| 66 | body |
|---|
| 67 | if obj is nil |
|---|
| 68 | return 1 |
|---|
| 69 | else if obj inherits FileNode |
|---|
| 70 | return _numBytes.compareTo(obj.numBytes) |
|---|
| 71 | else |
|---|
| 72 | throw FallThroughException(obj) |
|---|
| 73 | |
|---|
| 74 | |
|---|
| 75 | class Dir |
|---|
| 76 | """ |
|---|
| 77 | Dir does the work of scanning the files and subdirectories. |
|---|
| 78 | This results in a tree-like structure that matches the file system. |
|---|
| 79 | """ |
|---|
| 80 | |
|---|
| 81 | shared |
|---|
| 82 | var _sep as char # Path.directorySeparatorChar |
|---|
| 83 | |
|---|
| 84 | var _di as DirectoryInfo |
|---|
| 85 | var _dir as Dir? # parent directory |
|---|
| 86 | var _baseName as String |
|---|
| 87 | var _pathName as String |
|---|
| 88 | |
|---|
| 89 | var _files as List<of FileInfo> |
|---|
| 90 | var _subdirs as List<of Dir> |
|---|
| 91 | |
|---|
| 92 | var _count as int |
|---|
| 93 | var _numBytes as decimal # using 'decimal' because FileInfo.length returns int64 |
|---|
| 94 | |
|---|
| 95 | cue init(pathName as String) |
|---|
| 96 | """ Creates a Dir from a filesystem path string. Scans all subdirs. """ |
|---|
| 97 | _di = DirectoryInfo(pathName) |
|---|
| 98 | _pathName = pathName |
|---|
| 99 | _baseName = pathName |
|---|
| 100 | _init(nil) |
|---|
| 101 | |
|---|
| 102 | cue init(di as DirectoryInfo, parentDir as Dir) |
|---|
| 103 | """ Creates a Dir from a parent Dir. Scans all subdirs. """ |
|---|
| 104 | _di = di |
|---|
| 105 | _baseName = di.name |
|---|
| 106 | _pathName = di.fullName |
|---|
| 107 | _init(parentDir) |
|---|
| 108 | |
|---|
| 109 | get count from _count |
|---|
| 110 | |
|---|
| 111 | get numBytes from _numBytes |
|---|
| 112 | |
|---|
| 113 | def _init(parentDir as Dir?) |
|---|
| 114 | assert _baseName.length |
|---|
| 115 | _sep = Path.directorySeparatorChar |
|---|
| 116 | _dir = parentDir |
|---|
| 117 | if _dir, _pathName = '[_dir._pathName][_sep][_baseName]' |
|---|
| 118 | _files = List<of FileInfo>() |
|---|
| 119 | _subdirs = List<of Dir>() |
|---|
| 120 | .scan |
|---|
| 121 | |
|---|
| 122 | def scan |
|---|
| 123 | assert _pathName.length |
|---|
| 124 | |
|---|
| 125 | try |
|---|
| 126 | dirInfo = DirectoryInfo(_pathName) |
|---|
| 127 | catch IOException |
|---|
| 128 | print 'warning: Unreadable directory:', _pathName |
|---|
| 129 | return |
|---|
| 130 | |
|---|
| 131 | try |
|---|
| 132 | files = dirInfo.getFiles |
|---|
| 133 | catch IOException |
|---|
| 134 | print 'warning: Unreadable files in directory:', _pathName |
|---|
| 135 | return |
|---|
| 136 | |
|---|
| 137 | for fi in files |
|---|
| 138 | try |
|---|
| 139 | _files.add(fi) |
|---|
| 140 | _count += 1 |
|---|
| 141 | _numBytes += fi.length to decimal |
|---|
| 142 | catch IOException |
|---|
| 143 | print 'warning: Cannot scan file:', fi.fullName |
|---|
| 144 | |
|---|
| 145 | for di in dirInfo.getDirectories |
|---|
| 146 | d = Dir(di, this) |
|---|
| 147 | _subdirs.add(d) |
|---|
| 148 | _count += d.count |
|---|
| 149 | _numBytes += d.numBytes |
|---|
| 150 | |
|---|
| 151 | get toList as List<of FileNode> |
|---|
| 152 | """ |
|---|
| 153 | Returns a flat list containing self, all files and all subdirs (recursively). |
|---|
| 154 | """ |
|---|
| 155 | t = List<of FileNode>() |
|---|
| 156 | .populateList(t) |
|---|
| 157 | return t |
|---|
| 158 | |
|---|
| 159 | def populateList(t as List<of FileNode>) is protected |
|---|
| 160 | ensure |
|---|
| 161 | t.count > old t.count |
|---|
| 162 | body |
|---|
| 163 | t.add(FileNode(_pathName, _numBytes)) |
|---|
| 164 | for fi in _files |
|---|
| 165 | try |
|---|
| 166 | t.add(FileNode(fi.fullName, fi.length)) |
|---|
| 167 | catch IOException |
|---|
| 168 | print 'warning: Cannot scan file:', fi.fullName |
|---|
| 169 | for subdir in _subdirs |
|---|
| 170 | subdir.populateList(t) |
|---|
| 171 | |
|---|
| 172 | |
|---|
| 173 | class Sizes |
|---|
| 174 | |
|---|
| 175 | test |
|---|
| 176 | assert _topPercent > 0.0 and _topPercent <= 1.0 |
|---|
| 177 | assert _divisor > 0.0 |
|---|
| 178 | assert _minSize >= 0.0 |
|---|
| 179 | |
|---|
| 180 | shared |
|---|
| 181 | var _topPercent = 0.1 |
|---|
| 182 | var _divisor = 1024 * 1024.0 # megabyte |
|---|
| 183 | var _minSize = 1.0 # expressed in terms of _divisor |
|---|
| 184 | var _divisorLabel = 'MB' |
|---|
| 185 | |
|---|
| 186 | def main |
|---|
| 187 | print 'Scanning...' |
|---|
| 188 | Console.out.flush |
|---|
| 189 | start = DateTime.now |
|---|
| 190 | args = CobraCore.commandLineArgs |
|---|
| 191 | dirName = if(args.count > 1, args[1], Directory.getCurrentDirectory) |
|---|
| 192 | topDir = Dir(dirName) |
|---|
| 193 | duration = DateTime.now.subtract(start) |
|---|
| 194 | print |
|---|
| 195 | print '[topDir.count] directories and files read' |
|---|
| 196 | print '[duration.totalSeconds:N] seconds to read' |
|---|
| 197 | print '[topDir.numBytes/_divisor:N] [_divisorLabel]' |
|---|
| 198 | print |
|---|
| 199 | files = topDir.toList |
|---|
| 200 | files.sort |
|---|
| 201 | files.reverse |
|---|
| 202 | n = (files.count*_topPercent) to int |
|---|
| 203 | for i in n |
|---|
| 204 | f = files[i] |
|---|
| 205 | size = f.numBytes / _divisor |
|---|
| 206 | if size < _minSize, break # small files are uninteresting |
|---|
| 207 | #print '%4i %7.1f %s' % (i+1, size, f.pathname) |
|---|
| 208 | istr = (i+1).toString.padLeft(4) |
|---|
| 209 | sizestr = size.toString('0.0').padLeft(7) |
|---|
| 210 | print '[istr] [sizestr] [f.pathName]' |
|---|