{
  CP/M-80 directory program written in Turbo Pascal 2.0.
  Based loosely on wildcard.pas, author and compiler unknown.
  Accepts ambiguous file names and displays sorted directory.
    File sizes rounded to next 1k increment.
  Steve Fox - Albuquerque RCP/M  (505)299-5974
  Version 1.0     29 Mar 1985

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

  Revised 23 Apr 85 by : William L. Mabee, CRNA Followin attributes added
  Functions  :
   Centered
   Dash
   ConstStr
  Procedures :
   PutItUp

  Changed Code to allow automatic display of logged DU directory will allow
  code to be included in Turbo Pascal Program or chaining from main turbo
  routine.

  Added prompt for which drive change source for your own system
  if you have more than two drives add something like ['A..P']; and
  change appropriate prompt.

  Added code to display total amount disk space used.

  Added header.

}

Program dir;
label start;
const
  columns   = 4;
  fence     = ' | ';
  header    = 'File     Ext Size   File     Ext Size   File     Ext Size   File     Ext Size';
type
  CharSet   = set of char;
  FileName  = string[14];                   { d:filename.ext }
  str80     = string[80];
  StrStd    = string[127];
  FilePtr   = ^FileDescr;
  FileDescr =
    record
      fname: FileName;                      { Name of a matching file }
      fsize: integer;                       { Size of file }
      Next: FilePtr;                        { Points to next name on linked list }
    end;
  FileBlock =
    record
      case boolean of
        true:
          (drive: byte;                     { Byte code }
           fname: array[1..11] of char;     { File name }
           extent,                          { Current extent }
           s1, s2, reccount: byte;          { Used to compute file size }
           dn: array[16..31] of byte);
        false:
          (init: array[1..32] of byte);
    end;

var
  CH : Char;
  entries: integer;                         { Count of directory entries }
  prototype: FileName;                      { Directory mask }
  first: FilePtr;                           { Start of linked list }
  searchblk: FileBlock;                     { Block for search }
  CtrPrg: File;

Function ConstStr(C : Char; N : Integer) : Str80;
var
  S : string[80];
begin
  if N < 0 then
    N := 0;
  S[0] := Chr(N);
  FillChar(S[1],N,C);
  ConstStr := S;
end;

Function Centered(TheString:Str80):Str80;
begin
  Centered := ConstStr(' ',((80 - Length(TheString)) Div 2)) +
  TheString;
end;

Function Dash(Spaces : Integer) : Str80;
var
  Column : Integer;
  Temp   : Str80;
begin
  Temp :='';
  For Column := 1 to Spaces do
  begin
    Temp := Temp + '-';
    Dash := Temp;
  end;
end;

Function Tab(Spaces : Integer) : Str80;
var
  Column : Integer;
  Temp   : Str80;
begin
  Temp :='';
  For Column := 1 to Spaces do
  begin
    Temp := Temp + '-';
    Dash := Temp;
  end;
end;

Procedure Choice(    Prompt : Str80;
                     Term   : CharSet;
                 var TC     : Char    );
var
  Ch : Char;
begin
  GotoXY(1,23); Write(Prompt); ClrEol;
  repeat
    Read(Kbd,Ch);
    TC := Upcase(Ch);
    if not (TC in Term) then
      write(^G);
  until TC in Term;
  Write(Ch);
end;

Procedure ClearFrame;
var
  I : Integer;
begin
  for I := 20 downto 3  do
  begin
    GotoXY(1,I + 1); ClrEol ;
  end;
end;

  procedure GetMask(var prototype: FileName);
  { Get ambiguous file name and expand into directory mask (prototype) }
    var
      i, j: integer;
      line: StrStd;

    function trim(st: StrStd): StrStd;
    { Trim leading and trailing blanks }
      var
       i, j: integer;
      begin
        i := 1;
        j := length(st);
        while (st[i] = ' ') and (i <= j) do
          i := succ(i);
        while (st[j] = ' ') and (j >= i) do
          j := pred(j);
        trim := copy(st, i, j - i + 1)
      end;

    function pad(line: StrStd; i: integer): StrStd;
    { Pad line with spaces to length of i }
      begin
        while length(line) < i do
          line := line + ' ';
        pad := line
      end;

    begin
      repeat
        Choice('Directory for which drive ( A or B ) ? ',['A','B'],Ch);
      until Ch <> '';
      ClearFrame;
      line := Ch+':*.*';
      line := trim(line);
      for i := 1 to length(line) do
        line[i] := UpCase(line[i]);
      if line = ''
        then line := '*.*';
      line := pad(line, 14);
      prototype := copy(line, 1, 14);
      FillChar(searchblk.init, 32, 0);
      with searchblk do
        begin
          if prototype[2] = ':'
            then
              begin
                drive := succ(ord(prototype[1]) - ord('A'));
                i := 3
              end
            else
              begin
                drive := 0;
                i := 1
              end;
          fname := '           ';
          j := 1;
          repeat
            begin
              if prototype[i] = '*'
                then while j <= 8 do
                  begin
                    fname[j] := '?';
                    j := succ(j)
                  end
                else
                  begin
                    fname[j] := prototype[i];
                    j := succ(j)
                  end
            end;
            i := succ(i)
          until (j > 8) or (prototype[i] = '.');

          while (prototype[i] <> '.') and (prototype[i] <> ' ') do
            i := succ(i);

          i := succ(i);
          j := 9;
          repeat
            begin
              if prototype[i] = '*'
                then while j <= 11 do
                  begin
                    fname[j] := '?';
                    j := succ(j)
                  end
                else
                  begin
                    fname[j] := prototype[i];
                    j := succ(j)
                  end
            end;
            i := succ(i)
          until (j > 11) or (prototype[i] = '.');
          extent := ord('?');
          s1     := ord('?');
          s2     := ord('?')
        end
    end;

  procedure ReadDir(prototype: filename; var entries: integer; var first: FilePtr);
  { Create an alphabetized list of files which match the prototype }
    const
      findfirst = 17;                       { BDOS function - search for first file }
      findnext  = 18;                       { BDOS function - search for next file}
      setdma    = 26;                       { BDOS function - set dma buffer address }
      fcb       = $80;                      { Default dma buffer address }
    type
      dirblock  = array [0..3] of FileBlock;
      fileblptr = ^FileBlock;
    var
      off: integer;                         { dir entry offset or end flag }
      fn: FileName;
      answerblk: dirblock;                  { block to receive file name }

    procedure insertfile(fn: FileName; fs: integer; var entries: integer; var first: FilePtr);
    { Insert a new file name in the alphabetic list }
      var
        f,                                  { file name entry being created }
        this, previous: FilePtr;            { followers for insertion }
      begin
        previous := nil;
        this := first;
        while (this <> nil) and (this^.fname < fn) do
          begin
            previous := this;
            this := this^.next
          end;
        if this^.fname <> fn
          then
            begin
              entries := succ(entries);
              new(f);
              f^.fname := fn;
              f^.fsize := fs;
              f^.next  := this;
              if previous = nil
                then first := f
                else previous^.next := f
            end
          else if this^.fsize < fs
                 then this^.fsize := fs
      end;

    begin { ReadDir }
      entries := 0;
      first := nil;
      BDOS(setdma, addr(answerblk));
      off := BDOS(findfirst, addr(searchblk));
      while off <> 255 do
        begin
          with answerblk[off] do
            if (ord(fname[10]) and $80) = 0 { Non-system? }
              then
                begin
                  drive := 11;              { File name length }
                  move(drive, fn, 12);      { File name }
                  insert('.', fn, 9);
                  insertfile(fn, reccount + (extent + (s2 shl 5)) shl 7, entries, first)
                end;
          off := BDOS(findnext, addr(searchblk));
        end;
      BDOS(setdma, fcb)                     { Restore DMA buffer }
    end;

  procedure DispDir(entries: integer; first: FilePtr);
  { Display directory list }
    var
      i, size,totsize: integer;
      OldName: FilePtr;
    begin
      i := 0;
      totsize := 0;
      GotoXY(1,6);
      WriteLn(Header); WriteLn;
      while first <> nil do
        begin                               { Scan the whole list }
          size := first^.fsize shr 3;
          totsize := totsize + size;
          if 0 <> (first^.fsize mod 8)
            then size := succ(size);
          write(first^.fname, size:4, 'k');
          i := succ(i);
          Oldname := first;
          first := first^.Next;             { Go to next on chain }
          dispose(Oldname);                 { Reclaim space }
          if i < columns
            then write(fence)
            else
              begin
                writeln;
                i := 0
              end
        end;
        WriteLn;
        WriteLn;
        write('Total number of Files : ',entries);
        writeln('              Using a total of : ',totsize,' K');
    end;

  begin { main }
    ClrScr;
    GotoXY(1,1); Writeln(ConstStr('-',79)); WriteLn; Write(ConstStr('-',79));
    GotoXY(1,2); Write(Centered('Disk Directory Routine'));
    GotoXY(1,22); Writeln(ConstStr('-',79)); WriteLn; Write(ConstStr('-',79));
    start :
    clearFrame;
    GetMask(prototype);                     { Read mask }
    ReadDir(prototype, entries, first);     { Read directory }
    DispDir(entries, first);                { Display directory }
    repeat
      Choice('Do directory on another drive ( Y or N ) : ',['Y','N'],CH);
      if Ch = 'Y' then goto start;
    until Ch = 'N';
    ClrScr;
  end.
