#!/perl -w
# NAME: dirsizes.pl
# AIM: Given a PATH, show the directories existing, and the approx. size of each
# including the number of files found
# 1/2/2009 - Minor fix of file count ($fc), especially when no subdirectories.
# 20/12/2008 - Added an -x=excludes parameters
# 20/11/2008 geoff mclane http://geoffair.net/mperl
# ###############################################################################
use strict;
use warnings;
use File::stat;
# This needs to be adjusted for personal use ...
unshift(@INC, 'C:/GTools/perl');
require 'logfile.pl' or die "Unable to load logfile.pl ...\n";
# log file stuff
my ($LF);
my $pgmname = $0;
if ($pgmname =~ /\w{1}:\\.*/) {
    my @tmpsp = split(/\\/,$pgmname);
    $pgmname = $tmpsp[-1];
}
my $outfile = "temp.$pgmname.txt";
open_log($outfile);

my $in_folder = 'C:\Documents and Settings\Geoff McLane\My Documents';
my @dir_sizes = ();
my $tot_size = 0;
my @warnings = ();
my $file_count = 0;
my $DL_SIZE = 0;
my $DL_DIR = 1;
my $DL_NN = 2;
my $DL_KS = 3;
my $DL_FC = 4;
my $DL_FCNN = 5;
my $verbosity = 0;
my %excluded = ();
my $loadlog = 0;

# debug
my $dbg_s01 = 0;    # show prt("dir: $file $cds bytes ... as they are processed ...

parse_args(@ARGV);

prt( "$pgmname ... Hello, processing [$in_folder] directory ...\n" ) if ($verbosity > 0);

process_folder ( $in_folder, 0 );

show_dir_sizes();

show_warnings( 0 );

close_log($outfile,$loadlog);
unlink($outfile);
exit(0);

#################################################################################
#### SUBS ONLY

#string dirghtml::b2ks1(double d) // b2ks1(double d)
sub bytes2ks {
	my ($d) = @_;
	my $oss;
	my $kss;
	my $lg = 0;
	my $ks = ($d / 1024); #// get Ks
	my $div = 1;
   if( $ks < 1000 ) {
      $div = 1;
      $oss = "KB";
   } elsif ( $ks < 1000000 ) {
	  $div = 1000;
      $oss = "MB";
   } elsif ( $ks < 1000000000 ) {
      $div = 1000000;
      $oss = "GB";
   } else {
      $div = 1000000000;
      $oss = "TB";
   }
   $kss = $ks / $div;
   $kss += 0.05;
   $kss *= 10;
   $lg = int($kss);
   return( ($lg / 10) . $oss );
}

sub mycmp_decend {
   if (${$a}[0] < ${$b}[0]) {
      #prt( "+[".${$a}[0]."] < [".${$b}[0]."]\n" ) if $verb3;
      return 1;
   }
   if (${$a}[0] > ${$b}[0]) {
      #prt( "-[".${$a}[0]."] < [".${$b}[0]."]\n" ) if $verb3;
      return -1;
   }
   #prt( "=[".${$a}[0]."] < [".${$b}[0]."]\n" ) if $verb3;
   return 0;
}

sub get_nn { # perl nice number nicenum add commas
	my ($n) = shift;
	if (length($n) > 3) {
		my $mod = length($n) % 3;
		my $ret = (($mod > 0) ? substr( $n, 0, $mod ) : '');
		my $mx = int( length($n) / 3 );
		for (my $i = 0; $i < $mx; $i++ ) {
			if (($mod == 0) && ($i == 0)) {
				$ret .= substr( $n, ($mod+(3*$i)), 3 );
			} else {
				$ret .= ',' . substr( $n, ($mod+(3*$i)), 3 );
			}
		}
		return $ret;
	}
	return $n;
}

sub show_dir_sizes {
    my ($mdl, $msl, $mkl, $mcl, $dir, $siz, $max, $i, $nn, $ks, $fc, $fcnn);
    $max = scalar @dir_sizes;
    $mdl = 0;
    $msl = 0;
    $mkl = 0;
    $mcl = 0;
    @dir_sizes = sort mycmp_decend @dir_sizes;
    my $tot = 0;
    my $ftot = 0;
    for ($i = 0; $i < $max; $i++) {
        $dir = $dir_sizes[$i][$DL_DIR];
        $siz = $dir_sizes[$i][$DL_SIZE];
        $fc = $dir_sizes[$i][$DL_FC];
        $tot += $siz;
        $ftot += $fc;
        $nn = get_nn($siz);
        $ks = bytes2ks($siz);
        $fcnn = get_nn($fc);
        $dir_sizes[$i][$DL_NN] = $nn;
        $dir_sizes[$i][$DL_KS] = $ks;
        $dir_sizes[$i][$DL_FCNN] = $fcnn;
        $mdl = length($dir) if (length($dir) > $mdl);
        $msl = length($nn) if (length($nn) > $msl);
        $mkl = length($ks) if (length($ks) > $mkl);
        $mcl = length($fcnn) if (length($fcnn) > $mcl);
    }

    # get total lengths
    $nn = get_nn($tot);
    $ks = bytes2ks($tot);
    $fcnn = get_nn($ftot);
    $msl = length($nn) if (length($nn) > $msl);
    $mkl = length($ks) if (length($ks) > $mkl);
    $mcl = length($fcnn) if (length($fcnn) > $mcl);

    for ($i = 0; $i < $max; $i++) {
        $dir = $dir_sizes[$i][$DL_DIR];
        $siz = $dir_sizes[$i][$DL_SIZE];
        $nn =  $dir_sizes[$i][$DL_NN];
        $ks =  $dir_sizes[$i][$DL_KS];
        $fcnn = $dir_sizes[$i][$DL_FCNN];
        $dir .= ' ' while (length($dir) < $mdl);
        $nn = ' '.$nn while (length($nn) < $msl);
        $ks = ' '.$ks while (length($ks) < $mkl);
        $fcnn = ' '.$fcnn while (length($fcnn) < $mcl);
        prt( "dir: $dir $nn  $ks  $fcnn files\n" );
    }
    $dir = "TOTAL";
    $dir .= ' ' while (length($dir) < $mdl);
    $siz = $tot;
    $nn = get_nn($tot);
    $nn = ' '.$nn while (length($nn) < $msl);
    $ks = bytes2ks($tot);
    $ks = ' '.$ks while (length($ks) < $mkl);
    $fcnn = get_nn($ftot);
    $fcnn = ' '.$fcnn while (length($fcnn) < $mcl);
    prt( "dir: $dir $nn  $ks  $fcnn\n" );
}

sub process_folder {
    my ($inf,$lev) = @_;
    my (@files, $file, $ff, $sb, $cds, $sz, $fc, $lfc);
    my $dsize = 0;
    my $rsize = 0;
    my $fcnt = 0;
    $lfc = 0;
	$fc = 0;
    if (opendir( DIR, $inf)) {
        local $| = 1;
        @files = readdir(DIR);
        closedir(DIR);
        foreach $file (@files) {
            next if (($file eq '.')||($file eq '..'));
            $ff = $inf."\\".$file;
            if (-d $ff) {
                if (defined $excluded{$file}) {
                    prt( "Skipping folder [$file].\n" ) if ($verbosity > 1);
                } else {
                    ($cds,$fc) = process_folder($ff,($lev + 1));
                    $dsize += $cds;
                    $fcnt += $fc;
                    if ($lev == 0) {
                        prt("dir: $file $cds bytes, $fc files ...\n") if ($dbg_s01);
                        $tot_size += $cds;
                        push(@dir_sizes, [$cds, $file, '', '', $fc, '']);
                    }
                }
            } else {
                $fcnt++;
                $lfc++;
                $sb = stat($ff);
                if ($sb) {
                    $sz = $sb->size;
                    $dsize += $sz;
                    $rsize += $sz;
                } else {
                    prtw("WARNING: stat of $ff FAILED!\n");
                }
                $file_count++;
                prt( "." ) if (($file_count % 1000) == 0);
            }
        }
        if ($lev == 0) {
            $tot_size += $rsize;
            prt( "\nroot: $inf ".get_nn($rsize).", total ".get_nn($tot_size)." $fcnt files...\n" );
            push(@dir_sizes, [$rsize, 'root', '', '', $lfc, '']); # add in this ROOT size to list
        }
    } else {
        prtw("WARNING: Unable to open $inf ... $! ...\n");
    }
    return $dsize,$fcnt;
}

sub give_help {
    prt( "$pgmname [Options] folder\n" );
    prt( "Options:\n" );
    prt( " -? -h -help = This brief HELP, and exit.\n" );
    prt( " -l          = Load log into Wordpad\n" );
    prt( " -v[vvvv]    = Set verbosity. (def=$verbosity).\n" );
    prt( " -x=folder   = Exclude this folder.\n" );
    exit(1);
}

# Ensure argument exists, or die.
sub require_arg {
    my ($arg, @arglist) = @_;
    mydie( "ERROR: no argument given for option '$arg' ...\n" ) if ! @arglist;
}

sub set_verbosity {
    my (@av) = @_;
    my ($arg, $ex);
    while(@av) {
        $arg = $av[0];
        if ($arg =~ /^-/) {
            $arg =~ s/^-// while ($arg =~ /^-/);
            if ($arg =~ /^v/) {
                $verbosity += length($arg);
                prt( "Set verbosity to [$verbosity].\n" );
            }
        }
        shift @av;
    }

}

sub parse_args {
    my (@av) = @_;
    my ($arg, $ex);
    set_verbosity(@av);
    while(@av) {
        $arg = $av[0];
        if ($arg =~ /^-/) {
            $arg =~ s/^-// while ($arg =~ /^-/);
            if (($arg eq '?')||($arg eq 'h')||($arg eq 'help')) {
                give_help();
            } elsif ($arg =~ /^x=(.+)/) {
                $ex = $1;
                $excluded{$ex} = 1;
                prt( "Excluding folder [$ex] ...\n" ) if ($verbosity > 0);
            } elsif ($arg =~ /^x$/) {
                require_arg(@av);
                shift @av;
                $ex = $av[0];
                $excluded{$ex} = 1;
                prt( "Excluding folder [$ex] ...\n" ) if ($verbosity > 0);
            } elsif ($arg =~ /^l/) {
                $loadlog = 1;
                prt( "Set load log into Wordpad ...\n" ) if ($verbosity > 0);
            } elsif ($arg =~ /^v/) {
                # done first
            } else {
                prt( "ERROR: Unknown argument [".$av[0]."]! Use -? for HELP.\n" );
                exit(1);
            }
        } else {
            $in_folder = $arg;
            prt( "Set IN folder to [$in_folder] ...\n" ) if ($verbosity > 0);
        }
        shift @av;
    }
}


sub prtw {
    my ($tx) = shift;
    $tx =~ s/\n$// if ($tx =~ /\n$/);
    prt("$tx\n");
    push(@warnings,$tx);
}

sub show_warnings {
    my ($dbg) = shift;
    if (@warnings) {
        prt( "\nGot ".scalar @warnings." WARNINGS ...\n" );
        foreach my $line (@warnings) {
            prt("$line\n" );
        }
        prt("\n");
    } elsif ($dbg) {
        prt("\nNo warnings issued.\n\n");
    }
}


# eof - dirsizes.pl

