#!/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;
use Cwd;
use Fcntl ':mode';

# 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:\Users\Geoff\Documents';
#my $in_folder = 'C:\Documents and Settings\Geoff McLane\My Documents';
my $tot_size = 0;
my @warnings = ();
my $file_count = 0;

# ==========================================
my @dir_sizes = ();
# @dir_sizes offsets
my $DL_SIZE = 0;
my $DL_DIR = 1;
my $DL_NN = 2;
my $DL_KS = 3;
my $DL_FC = 4;
my $DL_FCNN = 5;
# added
my $DL_DCNT = 6;
my $DL_ASIZ = 7;
# added more
my $DL_DCNN = 8;
my $DL_ASNN = 9;
my $DL_ASKS = 10;

# ============================================

my $verbosity = 0;
my %excluded = ();
my $loadlog = 0;
my $block = 4096;
my $add_dir_block = 0;
# options try alternatives
my $try_alternatives = 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 < 1024 ) {
      $div = 1;
      $oss = "KB";
   } elsif ( $ks < (1024 * 1024) ) {
	  $div = 1024;
      $oss = "MB";
   } elsif ( $ks < (1024 * 1024 * 1024) ) {
      $div = (1024 * 1024);
      $oss = "GB";
   } else {
      $div = (1024 * 1024 * 1024);
      $oss = "TB";
   }
   $kss = $ks / $div;
   $kss += 0.05;
   $kss *= 10;
   $lg = int($kss);
   return( ($lg / 10) . $oss );
}

sub bytes2ks_1000 {
	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;
}

# added more
#my $DL_DCNN = 8;
#my $DL_ASNN = 9;
#my $DL_ASKS = 10;
sub show_dir_sizes {
    my ($mdl, $msl, $mkl, $mcl, $dir, $siz, $max, $i, $nn, $ks, $fc, $fcnn);
    my ($dc, $asz, $aszk);
    $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;
    # added
    my $atot = 0;
    my $dtot = 0;
    my $maszl = 0;
    my $mdcl  = 0;
    my $maskl = 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];
        $dc = $dir_sizes[$i][$DL_DCNT];
        $asz = $dir_sizes[$i][$DL_ASIZ];
        $tot += $siz;
        $ftot += $fc;
        $dtot += $dc;
        $atot += $asz;
        $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);

        $nn = get_nn($asz);
        $ks = bytes2ks($asz);
        $fcnn = get_nn($dc);
        $dir_sizes[$i][$DL_ASNN] = $nn;
        $dir_sizes[$i][$DL_ASKS] = $ks;
        $dir_sizes[$i][$DL_DCNN] = $fcnn;
        $maszl = length($nn) if (length($nn) > $maszl);
        $maskl = length($ks) if (length($ks) > $maskl);
        $mdcl = length($fcnn) if (length($fcnn) > $mdcl);
    }

    # 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);

    $nn = get_nn($atot);
    $ks = bytes2ks($atot);
    $fcnn = get_nn($dtot);
    $maszl = length($nn) if (length($nn) > $maszl);
    $maskl = length($ks) if (length($ks) > $maskl);
    $mdcl = length($fcnn) if (length($fcnn) > $mdcl);

    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);
        $asz = $dir_sizes[$i][$DL_ASNN];
        $asz = ' '.$asz while (length($asz) < $maszl);
        $aszk = $dir_sizes[$i][$DL_ASKS];
        $aszk = ' '.$aszk while (length($aszk) < $maskl);
        prt( "dir: $dir $nn  $ks  $fcnn files ($asz $aszk)\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" );
}

#  0 dev      device number of filesystem
#  1 ino      inode number
#  2 mode     file mode  (type and permissions)
#  3 nlink    number of (hard) links to the file
#  4 uid      numeric user ID of file's owner
#  5 gid      numeric group ID of file's owner
#  6 rdev     the device identifier (special files only)
#  7 size     total size of file, in bytes
#  8 atime    last access time in seconds since the epoch
#  9 mtime    last modify time in seconds since the epoch
# 10 ctime    inode change time in seconds since the epoch (*)
# 11 blksize  preferred block size for file system I/O
# 12 blocks   actual number of blocks allocated

# mode
# File types.  Not necessarily all are available on your system.
# S_IFREG S_IFDIR S_IFLNK S_IFBLK S_IFCHR S_IFIFO S_IFSOCK S_IFWHT S_ENFMT
# # The operators -f, -d, -l, -b, -c, -p, and -S.
# S_ISREG($mode) S_ISDIR($mode) S_ISLNK($mode)
# S_ISBLK($mode) S_ISCHR($mode) S_ISFIFO($mode) S_ISSOCK($mode)
sub is_directory {
	my ($p) = shift;
	my $sb = stat($p);
	if ($sb) {
		my $mode = $sb->mode;
		if (S_ISDIR($mode)) {
			return 1;
		}
	}
	return 0;
}

sub show_stat {
	my ($p) = shift;
	my $sb = stat($p);
	if ($sb) {
		my $mode = $sb->mode;
		my $size = $sb->size;
		my $type = S_IFMT($mode);
		my $perm = S_IMODE($mode);
		my $sperm = sprintf("%04o", ($mode & 0777));
		my $tstg = "";
		if (S_ISREG($mode)) {
			$tstg = '-f';
		} elsif (S_ISDIR($mode)) {
			$tstg = '-d';
		} elsif (S_ISLNK($mode)) {
			$tstg = '-l';
		} elsif (S_ISBLK($mode)) {
			$tstg = '-b';
		} elsif (S_ISCHR($mode)) {
			$tstg = '-c';
		} elsif (S_ISFIFO($mode)) {
			$tstg = '-p';
		} elsif (S_ISSOCK($mode)) {
			$tstg = '-S';
		} else {
			$tstg = '-?';
		}
		prt("stat $p [$tstg] $size - mode=$mode, type=$type, perm=$perm [$sperm]\n");
	} else {
		prt("stat $p failed\n");
	}
}

sub process_folder {
    my ($in,$lev) = @_;
	my $inf = dir2unix($in);
    my (@files, $file, $ff, $sb, $cds, $sz, $fc, $lfc);
    my $dsize = 0;
    my $rsize = 0;
    my $asize = 0;  # adjusted size
    my $rasiz = 0;
    my $rdcnt = 0;
    my $blks = 0;
    my $fcnt = 0;
    my $dcnt = 0;
	my ($DIR,$as,$dc);
    $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 {
                    $dcnt++;
                    $rdcnt++;
                    $asize += $block if ($add_dir_block);
                    $rasiz += $block if ($add_dir_block);
                    ($cds,$fc,$as,$dc) = process_folder($ff,($lev + 1));
                    $dsize += $cds;
                    $fcnt += $fc;
                    $asize += $as;
                    $dcnt += $dc;
                    if ($lev == 0) {
                        prt("dir: $file $cds bytes, $fc files ...\n") if ($dbg_s01);
                        $tot_size += $cds;
                        #                 0     1       2  3   4    5   6      7
                        push(@dir_sizes, [$cds, $file, '', '', $fc, '', $dcnt, $asize]);
                    }
                }
            } else {
                $fcnt++;
                $lfc++;
                $sb = stat($ff);
                if ($sb) {
                    $sz = $sb->size;    # get file SIZE
                    $dsize += $sz;
                    $rsize += $sz;
                    if ($sz == 0) {
                        $blks = 1;
                    } else {
                        $blks = $sz / $block;
                        $blks++ if ($sz % $block);
                    }
                    $asize += ($blks * $block);
                    $rasiz += ($blks * $block);
                } 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" );
            #                 0       1       2   3   4     5  6      7
            push(@dir_sizes, [$rsize, 'root', '', '', $lfc, '',$rdcnt, $rasiz]); # add in this ROOT size to list
        }
    } else {
		prtw("WARNING: Unable to open [$inf] ... $! ...\n");
		if ($try_alternatives) {
			show_stat($inf);
			if (is_directory($inf)) {
				my $cd = cwd();
				my $p = $inf;
				chdir $p;
				prt( "In directory ".cwd()."\n" );
				if (opendir($DIR, ".")) {
					prt( "And openned...\n" );
					close $DIR;
				} else {
					prt("WARNING: Unable to open [.] ... $! ...\n");
				}
				chdir $cd;
			}
			my ($i);
			my $cd = cwd();
			my @arr = split('/', $inf);
			my $cnt = scalar @arr;
			my $ncd = '';
			for ($i = 0; $i < ($cnt - 1); $i++) {
				if ($i == 0) {
					$ncd = $arr[$i];
				} else {
					$ncd .= '/';
					$ncd .= $arr[$i];
				}
			}
			my $last = $arr[$i];
			chdir $ncd;
			prt( "Now in [".cwd()."]...\n" );
			if (opendir( $DIR, $last)) {
				close $DIR;
				prt( "Could open $last...\n" );
			} else {
				prt("WARNING: Unable to open [$last] ... $! ...\n");
			}
			chdir $cd;
			my $inf2 = "\"".$inf."\"";
			if (opendir( $DIR, $inf2)) {
				prt( "Could open $inf2...\n" );
			} else {
				prt("WARNING: Unable to open [$inf2] ... $! ...\n");
				#my $inf3 = quotemeta($inf);
				my $inf3 = $inf;
				$inf3 =~ s/ /\\ /g;
				if (opendir( $DIR, $inf3)) {
					prt( "Could open $inf3...\n" );
				} else {
					prt("WARNING: Unable to open [$inf3] ... $! ...\n");
					my $inf4 = $inf;
					$inf4 .= '/';
					if (opendir( $DIR, $inf4)) {
						prt( "Could open $inf4...\n" );
					} else {
						prt("WARNING: Unable to open [$inf4] ... $! ...\n");
						$inf4 =~ s/ /\\ /g;
						if (opendir( $DIR, $inf4)) {
							prt( "Could open $inf4...\n" );
						} else {
							prt("WARNING: Unable to open [$inf4] ... $! ...\n");
							if (opendir( $DIR, "$inf4")) {
								prt( "Could open $inf4...\n" );
							} else {
								prt("WARNING: Unable to open [$inf4] ... $! ...\n");
							}
						}
					}
				}
			}
		}
    }
    return $dsize,$fcnt,$asize,$dcnt;
}

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");
    }
}

sub dir2unix {
	my ($d) = shift;
	$d =~ s/\\/\//g;
	return $d;
}



# eof - dirsizes.pl

