{************************************************}
{                                                }
{   Turbo Vision File Manager Demo               }
{   Copyright (c) 1992 by Borland International  }
{                                                }
{   For Virtual Pascal v2.1 (c) 2000 vpascal.com }
{                                                }
{************************************************}

{$X+,V-,R-,S-}
{&Use32+,Delphi-}

unit FileCopy;

interface

uses Objects;

const

  { I/O error constants }
  erWriteOpen  =  -1;  { error opening for Write }
  erReadOpen   =  -2;  { error opening for read  }
  erDiskFull   =  -3;  { error writing to file   }
  erLostFile   =  -4;  { file never finished }
  erNoFile     =  -5;  { file not found }
  erRename     =  -6;  { Unable to rename }
  erResetAFlag =  -7;  { Unable to reset archive flag on original file }

  { Internal error constants }
  erOutOfMemory = -99;  { could not allocate more memory }

  { copy options }
  coNormal     = $0000;   { normal copy }
  coCopyAOnly  = $0001;   { copy file only if Archive bit is set }
  coResetAFlag = $0002;   { reset Archive bit after succesful copy }

type

  erAction = (erAbort, erRetry);

  PFileCopy = ^TFileCopy;
  TFileCopy = object(TObject)
    Incomplete: Boolean;
    IsNewFile: Boolean;
    Offset: Longint;
    Base: Longint;
    FileList: PCollection;
    Result: Integer;
    Action: erAction;

    constructor Init(MaxFiles: Integer);
    destructor Done; virtual;

    { reporting methods }
    procedure ReadMsg(const FName: FNameStr; Progress: Longint); virtual;
    procedure WriteMsg(const FName: FNameStr; Progress: Longint); virtual;
    procedure ReportError(S: String); virtual;

    { copy and support methods }
    function CopyFile(const SourceName, TargetName: FNameStr; Options: Word): Boolean;
    procedure FlushBuffers;
    procedure EraseByName(const FName: FNameStr);

    { error methods }
    function IOError(const FName: FNameStr; ECode:Integer) : erAction; virtual;
    function InternalError(ECode: Integer) : erAction; virtual;
    function ErrorMsg(ECode: Integer) : String; virtual;
  end;


implementation

uses Dos;

const
  fmReadOnly = 0;
  fmReadWrite = 2;
  MaxXFerSize = $F000; { largest block to read from disk }

type
  String10 = String[10];

  { TPtrCollection implements a collection of pointers.  Instead of each  }
  { entry in the collection pointing to a pointer, the entries themselves }
  { are the actual pointers.                                              }

  PPtrCollection = ^TPtrCollection;
  TPtrCollection = object(TCollection)
    function GetItem(var S:TStream) : Pointer; virtual;
    procedure PutItem(var S: TStream; Item: Pointer); virtual;
    procedure FreeItem(Item:Pointer); virtual;
  end;

  { PFileRec represents a single file that is being processed. }

  PFileRec = ^TFileRec;
  TFileRec = object(TObject)
    Filename: PString;
    OrigName: PString;
    FTime: Longint;
    FSize: Longint;
    Buffers: PPtrCollection;
    Offset: Longint;
    Create: Boolean;
    OptFlags: Word;
    constructor Init(OldName, NewName: FNameStr);
    destructor Done; virtual;
  end;


{ TPtrCollection }

function TPtrCollection.GetItem(var S: TStream): Pointer;
var
  P : Pointer;
begin
  S.Read(P, SizeOf(Pointer));
  GetItem := P;
end;

procedure TPtrCollection.PutItem(var S: TStream; Item: Pointer);
begin
  S.Write(Item, SizeOf(Pointer));
end;

procedure TPtrCollection.FreeItem(Item:Pointer);
begin
  { do nothing }
end;


{ TFileRec }
constructor TFileRec.Init(OldName, NewName: FNameStr);
begin
  inherited Init;
  Filename := NewStr(NewName);
  OrigName := NewStr(OldName);
end;

destructor TFileRec.Done;
begin
  DisposeStr(Filename);
  DisposeStr(OrigName);
  if Buffers <> nil then Dispose(Buffers, Done);
  inherited Done;
end;


{ TFileCopy }
constructor TFileCopy.Init(MaxFiles: Integer);
begin
  inherited Init;
  FileList := New(PCollection, Init(MaxFiles, MaxFiles div 2));
  if Filelist = nil then
  begin
    ReportError(ErrorMsg(erOutOfMemory));
    Fail;
  end;
end;

destructor TFileCopy.Done;
begin
  if FileList^.Count <> 0 then FlushBuffers;
  if FileList <> nil then Dispose(FileList, Done);
  inherited Done;
end;

function TFileCopy.IOError(const FName: FNameStr; ECode: Integer): erAction;
begin
  ReportError(ErrorMsg(ECode));
  IOError := erAbort;
end;

function TFileCopy.InternalError(ECode: Integer): erAction;
begin
  ReportError(ErrorMsg(ECode));
  InternalError := erAbort;
end;

procedure TFileCopy.EraseByName(const FName: FNameStr);
var
  F: File;
  I: Integer;
begin
  Assign(F, FName);
  {$I-}
  Reset(F);
  {$I+}
  if IOResult = 0 then Erase(F);
  I := IOResult;   { read this so we don't leave any unused value there }
end;

procedure TFileCopy.FlushBuffers;
var
  Leave : Integer;

procedure FlushFile(CurFile: PFileRec); far;
var
  BufAddr: Pointer;
  BytesToXFer: Word;
  BytesRead: Word;
  TargetFile: File;
  RemainingBytes: Longint;
  Attr: Word;
begin
  FileMode := fmReadWrite;

  Action := erRetry;
  Result := 1;
  while (Action <> erAbort) and (Result <> 0) do
  begin
    Assign(TargetFile, CurFile^.FileName^);
    {$I-}
    if CurFile^.Create then Rewrite(TargetFile, 1)
    else Reset(TargetFile, 1);
    {$I+}
    Result := IOResult;
    if Result <> 0 then
    begin
      Action := IOError(CurFile^.FileName^, erWriteOpen);
      if Action = erAbort then Exit;
    end;
  end;

  Seek(TargetFile, CurFile^.Offset);

  if (FileList^.IndexOf(CurFile) = FileList^.Count-1) and Incomplete
    then Inc(Offset, CurFile^.FSize);

  RemainingBytes := CurFile^.FSize;

  repeat
    if RemainingBytes > MaxXFerSize then BytesToXFer := MaxXFerSize
    else BytesToXFer := RemainingBytes;
    BufAddr := CurFile^.Buffers^.At(0);  { get first address }
    BlockWrite(TargetFile, BufAddr^, BytesToXFer, BytesRead);
    WriteMsg(CurFile^.Filename^, BytesRead);
    Dec(RemainingBytes, BytesRead);
    FreeMem(BufAddr, BytesToXFer);
    CurFile^.Buffers^.AtDelete(0);
  until RemainingBytes = 0;

  if not Incomplete then SetFTime(TargetFile, CurFile^.FTime);
  Close(TargetFile);

  if ((FileList^.IndexOf(CurFile) <> FileList^.Count-1) or
    (not Incomplete)) then
  begin
    if CurFile^.OptFlags and coResetAFlag <> 0 then
    begin
      Assign(TargetFile, CurFile^.OrigName^);
      GetFAttr(TargetFile, Attr);
      Attr := Attr and (not Archive);
      SetFAttr(TargetFile, Attr);
      if DosError <> 0 then IOError(CurFile^.OrigName^, erResetAFlag);
    end;
  end;
end;

begin
  FileList^.ForEach(@FlushFile);
  if Incomplete then Leave := 1 else Leave := 0;
  while FileList^.Count <> Leave do
    FileList^.AtFree(0);
end;

function TFileCopy.CopyFile(const SourceName, TargetName: FNameStr;
  Options: Word): Boolean;
const
  Safety = 4096;
var
  Flush: Boolean;
  SourceFile: File;
  TargetFile: File;
  CurFile: PFileRec;
  RemainingBytes: Longint;
  BytesToXFer: Word;
  DidXFer: Word;
  BufAddr: Pointer;
  CurMaxAvail: Longint;
  SRec: SearchRec;
begin
  CopyFile := False;

  { validate the coCopyAOnly flag }
  if Options and coCopyAOnly <> 0 then
  begin
    FindFirst(SourceName, AnyFile, SRec);
    {$IFDEF VIRTUALPASCAL} FindClose(SRec); {$ENDIF}
    if (DosError = 0) and ((SRec.Attr and Archive) = 0) then Exit
    else IOError(SourceName, erNoFile);
  end;

  CurFile := New(PFileRec, Init(SourceName, TargetName));
  CurFile^.Buffers := New(PPtrCollection, Init(5,2));
  if (CurFile = nil) or (CurFile^.Buffers = nil) then
  begin
    InternalError(erOutOfMemory);
    Exit;
  end;
  CurFile^.OptFlags := Options;
  CurFile^.Offset := 0;
  CurFile^.Create := True;
  FileList^.Insert(CurFile);

  Offset := 0;
  Incomplete := False;
  IsNewFile := False;
  Base := 0;

  repeat
    Flush := False;

    FileMode := fmReadOnly;
    Assign(SourceFile, SourceName);

    Action := erRetry;
    Result := 1;
    while (Action <> erAbort) and (Result <> 0) do
    begin
      {$I-}
      Reset(SourceFile,1);
      {$I+}
      Result := IOResult;
      if Result <> 0 then
      begin
        if IOError(SourceName, erReadOpen) = erAbort then
        begin
          FileList^.Free(CurFile);
          Exit;
        end;
      end;
    end;

    if Incomplete then
    begin
      Seek(SourceFile, Offset-Base);
      CurFile^.Offset := Offset;
      if Incomplete then CurFile^.Create := False;
    end;

    Incomplete := False;

    CurFile^.FSize := FileSize(SourceFile) - Offset + Base;
    CurMaxAvail := MaxAvail - Safety;

    if CurFile^.FSize > CurMaxAvail then
    begin
      CurFile^.FSize := CurMaxAvail;
      Flush := True;
      Incomplete := True;
      CurFile^.Offset := Offset;
    end;
    RemainingBytes := CurFile^.FSize;

    repeat
      if RemainingBytes > MaxXFerSize then BytesToXFer := MaxXFerSize
      else BytesToXFer := RemainingBytes;
      GetMem(BufAddr, BytesToXFer);
      CurFile^.Buffers^.Insert(BufAddr);
      BlockRead(SourceFile, BufAddr^, BytesToXFer, DidXFer);
      ReadMsg(SourceName, DidXFer);
      Dec(RemainingBytes, DidXFer);
    until RemainingBytes = 0;

    GetFTime(SourceFile, CurFile^.FTime);
    Close(SourceFile);

    if Flush then FlushBuffers;

  until not Incomplete;
  CopyFile := True;
end;

procedure TFileCopy.ReadMsg(const FName: FNameStr; Progress: Longint);
begin
  Writeln('Reading ', FName);
end;

procedure TFileCopy.WriteMsg(const FName: FNameStr; Progress: Longint);
begin
  Writeln('Writing ', FName);
end;

procedure TFileCopy.ReportError(S: String);
begin
  Writeln(S);
end;

function TFileCopy.ErrorMsg(ECode: Integer): String;
begin
  case ECode of
    erWriteOpen   : ErrorMsg := 'Unable to open for write access';
    erReadOpen    : ErrorMsg := 'Unable to open for read access';
    erDiskFull    : ErrorMsg := 'Unable to write to file.  Disk full?';
    erLostFile    : ErrorMsg := 'File never flushed from buffers';
    erNoFile      : ErrorMsg := 'File not found.';
    erRename      : ErrorMsg := 'Unable to rename to final name.';
    erOutOfMemory : ErrorMsg := 'Unable to allocate memory.';
    else ErrorMsg := 'Unknown error.';
  end; { case }
end;

end.
