
(********************************************************************)
(*                                                                  *)
(*  bigfiles.sd7  Utility to search for big files.                  *)
(*  Copyright (C) 2016  Thomas Mertes                               *)
(*                                                                  *)
(*  This program is free software; you can redistribute it and/or   *)
(*  modify it under the terms of the GNU General Public License as  *)
(*  published by the Free Software Foundation; either version 2 of  *)
(*  the License, or (at your option) any later version.             *)
(*                                                                  *)
(*  This program is distributed in the hope that it will be useful, *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of  *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *)
(*  GNU General Public License for more details.                    *)
(*                                                                  *)
(*  You should have received a copy of the GNU General Public       *)
(*  License along with this program; if not, write to the           *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


$ include "seed7_05.s7i";
  include "osfiles.s7i";
  include "console.s7i";

const type: sizeMapType is hash [string] bigInteger;
const type: pathsWithLengthHash is hash [bigInteger] array string;


const func bigInteger: searchDir (in string: dirPath, inout sizeMapType: fileSizeMap,
    inout sizeMapType: dirSizeMap) is func
  result
    var bigInteger: dirSize is 0_;
  local
    var array string: dirElements is 0 times "";
    var string: fileName is "";
    var string: filePath is "";
    var bigInteger: fileSize is 0_;
  begin
    block
      dirElements := readDir(dirPath);
    exception
      catch FILE_ERROR:
        writeln(" *** Cannot read directory " <& dirPath);
    end block;
    for fileName range dirElements do
      if dirPath = "/" then
        filePath := "/" & fileName;
      else
        filePath := dirPath & "/" & fileName;
      end if;
      case fileTypeSL(filePath) of
        when {FILE_REGULAR}:
          fileSize := bigFileSize(filePath);
          dirSize +:= fileSize;
          fileSizeMap @:= [filePath] fileSize;
        when {FILE_DIR}:
          dirSize +:= searchDir(filePath, fileSizeMap, dirSizeMap);
        when {FILE_SYMLINK}:
          noop; # Ignore symbolic link.
      end case;
    end for;
    dirSizeMap @:= [dirPath] dirSize;
  end func;


const proc: showResults (in sizeMapType: sizeMap, in integer: lines) is func
  local
    var pathsWithLengthHash: pathsWithLength is pathsWithLengthHash.value;
    var array bigInteger: lengthList is 0 times 0_;
    var integer: lengthIndex is 0;
    var bigInteger: length is 0_;
    var string: path is "";
    var integer: count is 0;
  begin
    pathsWithLength := flip(sizeMap);
    lengthList := sort(keys(pathsWithLength));
    for lengthIndex range maxIdx(lengthList) downto minIdx(lengthList) until count >= lines do
      length := lengthList[lengthIndex];
      if length in pathsWithLength then
        for path range pathsWithLength[length] do
          writeln(length lpad 14 <& ": " <& path);
          incr(count);
        end for;
      end if;
    end for;
  end func;


const proc: main is func
  local
    var string: dirPath is "";
    var integer: lines is 10;
    var sizeMapType: fileSizeMap is sizeMapType.value;
    var sizeMapType: dirSizeMap is sizeMapType.value;
  begin
    OUT := STD_CONSOLE;
    if length(argv(PROGRAM)) < 1 then
      writeln("Bigfiles Version 1.0 - Utility to search for big files.");
      writeln("Copyright (C) 2016 Thomas Mertes");
      writeln("This is free software; see the source for copying conditions.  There is NO");
      writeln("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.");
      writeln("Bigfiles is written in the Seed7 programming language");
      writeln("Homepage: http://seed7.sourceforge.net");
      writeln;
      writeln("usage: bigfiles directory [lines]");
      writeln;
    else
      dirPath := convDosPath(argv(PROGRAM)[1]);
      if length(argv(PROGRAM)) >= 2 then
        block
          lines := integer(argv(PROGRAM)[2]);
        exception
          catch RANGE_ERROR:
            writeln(" ***** Number of lines not numeric. " <& lines <& " used instead.");
        end block;
      end if;
      ignore(searchDir(dirPath, fileSizeMap, dirSizeMap));
      writeln("Big files:");
      showResults(fileSizeMap, lines);
      writeln;
      writeln("Big directories:");
      showResults(dirSizeMap, lines);
    end if;
  end func;
