Thursday, September 17, 2009

How to copy a file and display it's progress

This is just a demo, it can be modified to copy files using thread(s) and display the progress in a separate window(like Total Commander does).
procedure TurboCopyFile(
  const
    SourceFile, (* the source file *)
    DestinationFile: String; (* destination file *)
  Display: PProgressbar (* this is a pointer to progress bar
    which we update if assigned,
    this procedure can be called without a
    progress bar like so:
    TurboCopyFile(Source, Destination, NIL); *)
  );
type
  (* define an array of 4096 bytes which holds the bytes *)
  TurboBuffer = array[1..4096] of Byte;
const
  (* we need to set the size of the buffer in a constant *)
  szBuffer = sizeof(TurboBuffer);
var
  (* File streams with which we work *)
  InStream,
  OutStream: TFileStream;
  (* we need this to store a logical operation's result *)
  CanCopy: Boolean;
  (* holds the number of bytes left when
     InStream.Size -InStream.Position < 4096 bytes *)
  BytesLeft: Integer;
  (* the Buffer almighty *)
  Buffer: TurboBuffer;
begin
  (* Open the source file so we can read it's bytes *)
  InStream := TFileStream.Create(SourceFile, fmOpenRead);
  (* create a new file to the desired destination *)
  OutStream := TFileStream.Create(DestinationFile, fmCreate);

  (* check if a progress bar was passed *)
  if Assigned(Display) then
    Display^.Max := InStream.Size;

  (* this is the operation I was talking about in the
     variable section, this checks if we can read a
     full buffer(4096 bytes) *)
  CanCopy :=
    (InStream.Size > InStream.Position) and
    ((InStream.Size -InStream.Position) >= szBuffer);

  (* this ensures that the progress bar is being painted *)
  Application.ProcessMessages;

  (* loop while we CanCopy *)
  while CanCopy do begin
    (* read 4096 bytes from source file *)
    InStream.ReadBuffer(Buffer, szBuffer);
    (* then write it to destination file *)
    OutStream.WriteBuffer(Buffer, szBuffer);
    (* check if display is assigned *)
    if Assigned(Display) then
      (* update it's position *)
      Display^.Position := InStream.Position;
    CanCopy :=
      (InStream.Size > InStream.Position) and
      ((InStream.Size -InStream.Position) >= szBuffer);
  end;

  (* store the number of bytes that is less than 4096 into
     a local variable *)
  BytesLeft := InStream.Size -InStream.Position;

  (* do we have some bytes left ? we don't care how many,
     we just know it's less than 4096 bytes *)
  if BytesLeft > 0 then begin
    (* surprise, or not we have some bytes left,
       read them all into the buffer *)
    InStream.ReadBuffer(Buffer, BytesLeft);
    (* write them to destination file *)
    OutStream.WriteBuffer(Buffer, BytesLeft);
  end;

  (* do we have a progress bar ? *)
  if Assigned(Display) then
    (* we do? then update it's progress *)
    Display^.Position := Display^.Max;

  (* free the memory *)
  FreeAndNil(InStream);
  FreeAndNil(OutStream);
end; (* end of TurboCopyFile *)

A demo application can be downloaded as
binary
or
sourcecode.

4 comments:

  1. For further reading please refer to :
    http://supercopier.sfxteam.org/
    ;-)

    ReplyDelete
  2. How do you account for a file in use?
    This code makes the developer assume that it should copy a file even if in use but because it reads the bytes if the file is in use it causes failure.

    ReplyDelete
  3. well this code does NOT provide too much error handling, if you wish to check if a file is in use you should check out this link.

    ReplyDelete

Blogroll(General programming and Delphi feeds)