Sizes Sample
Blind Watch Maker 1
Download
Find Words
forth
Fractal Benchmark
Genetic Algorithm
Gtk Source Editor
Hex Dump
Notepad
Point
Shapes
Simple English Parser
Sizes
TPK
Word Count
"""
sizes.cobra

The most effective technique to free up disk space on a drive is to look in the
directories that consume the most space. You may find entire directories that
can be deleted or excessively large files that are no longer needed. The first
time you do this, you might downsize the space consumed by a large factor with
only a few minutes of effort. Credit goes to the old "Dark Forest" application
on the old NeXTstep platform for introducing me to this approach.

This command line utility scans the current directory and its files for their
sizes, including all subdirectories. It then reports the total size and shows
the largest directories and files first. You can use this report to look for
large things to delete and rerun it as you progress. To run:

> cd \to\path\of\interest
> cobra \path\to\sizes.cobra

Unix users get a similar result with something like "du -k | sort -nr"

As an example, I ran `sizes` on a directory and it reported:

203658 directories and files read
180.30 seconds to read
21,297.34 MB

After spending 20 minutes cleaning up the biggest directories and files
that it listed, it then reported this:

186345 directories and files read
49.95 seconds to read
8,809.15 MB
"""

class FileNode implements IComparable
    """
    One of these will be created for every directory and file, and put into a
    list that can be sorted.
    """

    test
        a = FileNode('a', 100)
        assert a.pathName == 'a'
        assert a.numBytes == 100
        assert a.compareTo(nil) == 1
        b = FileNode('b', 200)
        assert a.compareTo(b) < 0
        assert b.compareTo(a) > 0
        c = FileNode('c', 100)
        assert a.compareTo(c) == 0

    var _pathName as String
    var _numBytes as decimal

    cue init(pathName as String, numBytes as decimal)
        _pathName = pathName
        _numBytes = numBytes

    get pathName from _pathName

    get numBytes from _numBytes

    def compareTo(obj as Object?) as int
        require
            obj is nil  or  obj inherits FileNode
        body
            if obj is nil
                return 1
            else if obj inherits FileNode
                return _numBytes.compareTo(obj.numBytes)
            else
                throw FallThroughException(obj)


class Dir
    """
    Dir does the work of scanning the files and subdirectories.
    This results in a tree-like structure that matches the file system.
    """

    shared
        var _sep    as char   # Path.directorySeparatorChar

    var _di         as DirectoryInfo
    var _dir        as Dir?  # parent directory
    var _baseName   as String
    var _pathName   as String

    var _files      as List<of FileInfo>
    var _subdirs    as List<of Dir>

    var _count      as int
    var _numBytes   as decimal  # using 'decimal' because FileInfo.length returns int64

    cue init(pathName as String)
        """ Creates a Dir from a filesystem path string. Scans all subdirs. """
        _di = DirectoryInfo(pathName)
        _pathName = pathName
        _baseName = pathName
        _init(nil)

    cue init(di as DirectoryInfo, parentDir as Dir)
        """ Creates a Dir from a parent Dir. Scans all subdirs. """
        _di = di
        _baseName = di.name
        _pathName = di.fullName
        _init(parentDir)

    get count from _count

    get numBytes from _numBytes

    def _init(parentDir as Dir?)
        assert _baseName.length
        _sep = Path.directorySeparatorChar
        _dir = parentDir
        if _dir, _pathName = '[_dir._pathName][_sep][_baseName]'
        _files = List<of FileInfo>()
        _subdirs = List<of Dir>()
        .scan

    def scan
        assert _pathName.length

        try
            dirInfo = DirectoryInfo(_pathName)
        catch IOException
            print 'warning: Unreadable directory:', _pathName
            return

        try
            files = dirInfo.getFiles
        catch IOException
            print 'warning: Unreadable files in directory:', _pathName
            return

        for fi in files
            try
                _files.add(fi)
                _count += 1
                _numBytes += fi.length to decimal
            catch IOException
                print 'warning: Cannot scan file:', fi.fullName

        for di in dirInfo.getDirectories
            d = Dir(di, this)
            _subdirs.add(d)
            _count += d.count
            _numBytes += d.numBytes
        
    get toList as List<of FileNode>
        """
        Returns a flat list containing self, all files and all subdirs (recursively).
        """
        t = List<of FileNode>()
        .populateList(t)
        return t

    def populateList(t as List<of FileNode>) is protected
        ensure
            t.count > old t.count
        body
            t.add(FileNode(_pathName, _numBytes))
            for fi in _files
                try
                    t.add(FileNode(fi.fullName, fi.length))
                catch IOException
                    print 'warning: Cannot scan file:', fi.fullName
            for subdir in _subdirs
                subdir.populateList(t)


class Sizes

    test
        assert _topPercent > 0.0 and _topPercent <= 1.0
        assert _divisor > 0.0
        assert _minSize >= 0.0

    shared
        var _topPercent = 0.1
        var _divisor = 1024 * 1024.0  # megabyte
        var _minSize = 1.0  # expressed in terms of _divisor
        var _divisorLabel = 'MB'

        def main
            print 'Scanning...'
            Console.out.flush
            start = DateTime.now
            args = CobraCore.commandLineArgs
            dirName = if(args.count > 1, args[1], Directory.getCurrentDirectory)
            topDir = Dir(dirName)
            duration = DateTime.now.subtract(start)
            print
            print '[topDir.count] directories and files read'
            print '[duration.totalSeconds:N] seconds to read'
            print '[topDir.numBytes/_divisor:N] [_divisorLabel]'
            print
            files = topDir.toList
            files.sort
            files.reverse
            n = (files.count*_topPercent) to int
            for i in n
                f = files[i]
                size = f.numBytes / _divisor
                if size < _minSize, break  # small files are uninteresting
                #print '%4i %7.1f %s' % (i+1, size, f.pathname)
                istr = (i+1).toString.padLeft(4)
                sizestr = size.toString('0.0').padLeft(7)
                print '[istr] [sizestr] [f.pathName]'