Einzelnen Beitrag anzeigen

mm1256

Registriert seit: 10. Feb 2014
Ort: Wackersdorf, Bayern
642 Beiträge
 
Delphi 10.1 Berlin Professional
 
#20

AW: CopyFile lässt die Anwendung hängen, wie umgehen?

  Alt 4. Jun 2015, 17:59
Hallo Robert,

danke für den Tipp. Hab's einfach mal probiert....und langsam verliere ich auch die Angst vor den Threads. Weil, es scheint auf Anhieb zu funktionieren.

Für den TE:

Delphi-Quellcode:
unit RuFileCopy;

interface

uses
  System.Classes;

type

  TFileCopyMode = (fomAlways, fomIfNewer, fomIfOlder, fomIfSameDate, fomIfSameDateTime);
  TFileCopyCallback = procedure(ProgressPercent: Integer) of object;

  TRuFileCopyThread = class(TThread)
  private
    { Private-Deklarationen }
    FCopyResult, FSourceName, FDestName: string;
    FFileCopyMode: TFileCopyMode;
    FFileCopyCallback: TFileCopyCallback;
    procedure ThreadCopyCallback(ProgressPercent: Integer);
  protected
    procedure Execute; override;
  public
    constructor Create(aSourceName, aDestName: string;
                       aFileCopyMode: TFileCopyMode;
                       aFileCopyCallback: TFileCopyCallback);
    procedure ThreadIsReady(Sender: TObject);
  end;

const
  cRuFileCopyReady = 200;

var
  RuFileCopyThreadResult: string = '';

{- Eine Datei kopieren: Direkt-Aufruf -}
function RuFileCopyExecute(aSourceName, aDestName: string;
                           aFileCopyMode: TFileCopyMode;
                           aFileCopyCallback: TFileCopyCallback): string;

{- Eine Datei kopieren: Kopiert die Datei in einem eigenen Thread -}
function RuFileCopyThread(aSourceName, aDestName: string;
                          aFileCopyMode: TFileCopyMode;
                          aFileCopyCallback: TFileCopyCallback): string;

implementation

uses System.SysUtils;

var
  StreamCopyBuffer: array[0..500 * 1024] of byte;
  ProgressPercent: Int64;

{------------------------------------------------------------------------------}
{- Eine Datei kopieren: Direkt-Aufruf oder Aufruf aus dem Thread              -}
{- aSourceName = Name der Quelldatei                                          -}
{- aDestName = Name der ZielDatei, darf auch das Zielverzeichnis sein         -}
{- aFileCopyCallback = ProgressCallback: Übergibt Wert in Prozent von 1-100   -}
{------------------------------------------------------------------------------}
function RuFileCopyExecute(aSourceName, aDestName: string;
                           aFileCopyMode: TFileCopyMode;
                           aFileCopyCallback: TFileCopyCallback): string;
var
  BytesRead, BytesToRead, P: Int64;
  SourceStream, DestStream: TFileStream;
  SourceDateTime, DestDateTime: TDateTime;
begin
  Result := '';
  if not FileExists(aSourceName) then Exit('Quelldatei <'+aSourceName+'> existiert nicht');
  if not FileAge(aSourceName, SourceDateTime) then Exit('Quelldatei: TimeStamp lesen fehlgeschlagen');
  BytesRead := 0;
  ProgressPercent := 0;
  P := 0;
  // DestName darf auch ein Directory sein...dann den Dateinamen ran hängen
  if DirectoryExists(aDestName) // DestName ist ein Directory
  then aDestName := IncludeTrailingBackslash(aDestName)+ExtractFileName(aSourceName);
  if FileExists(aDestName) then begin
    if not FileAge(aDestName, DestDateTime) then Exit('Zieldatei: TimeStamp lesen fehlgeschlagen');
    case aFileCopyMode of
      fomAlways :;// immer kopieren
      fomIfNewer : // Die Quelldatei muss neuer als die Zieldatei sein
                          if NOT (SourceDateTime > DestDateTime)
                          then Exit('Die Quelldatei ist nicht neuer als die Zieldatei');
      fomIfOlder : // Die Quelldatei muss älter als die Zieldatei sein => z.B. Downgrade
                          if NOT (SourceDateTime < DestDateTime)
                          then Exit('Die Quelldatei ist nicht älter als die Zieldatei');
      fomIfSameDate : // Quell- und Ziel-Datei müssen am selben Tag erstellt worden sein
                          if Trunc(DestDateTime) <> Trunc(SourceDateTime)
                          then Exit('Die Zieldatei ist nicht vom selben Datum als die Quelldatei');
      fomIfSameDateTime : // Das Datum von Quell- und Ziel-Datei muss identisch sein
                          if DestDateTime <> SourceDateTime
                          then Exit('Datum und Zeit von Zieldatei und Quelldatei sind unterschiedlich');
      else raise Exception.Create('Da hat der Programmierer was vergessen');
    end;
  end;
  SourceStream := TFileStream.Create(aSourceName,fmOpenRead or fmShareDenyNone);
  DestStream := TFileStream.Create(aDestName,fmCreate);
  try
    if @aFileCopyCallback = nil
    then DestStream.CopyFrom(SourceStream,SourceStream.Size) // so geht es am schnellsten
    else begin // mit Fortschrittanzeige
      aFileCopyCallback(0);
      BytesToRead := SizeOf(StreamCopyBuffer);
      if SourceStream.Size < BytesToRead then BytesToRead := SourceStream.Size;
      repeat
        SourceStream.ReadBuffer(StreamCopyBuffer,BytesToRead);
        DestStream.WriteBuffer(StreamCopyBuffer,BytesToRead);
        BytesRead := BytesRead + BytesToRead;
        ProgressPercent := (BytesRead * 100) div SourceStream.Size;
        if P <> ProgressPercent then begin
          aFileCopyCallback(Integer(ProgressPercent));
          P := ProgressPercent;
        end;
        if (SourceStream.Size - BytesRead) <= BytesToRead
        then BytesToRead := SourceStream.Size - BytesRead;
      until BytesRead >= SourceStream.Size;
      if BytesRead <> SourceStream.SIZE
      then Result := 'Fehler beim Kopiervorgang:'+#13#10
                     +'Bytes gelesen:' + IntToStr(BytesRead) + #13#10
                     +'Bytes geschrieben:' + IntToStr(SourceStream.SIZE)
      else aFileCopyCallback(100); // Kopiervorgang 100%
    end;
  finally
    SourceStream.Free;
    DestStream.Free;
  end;
end;

function RuFileCopyThread(aSourceName, aDestName: string;
                          aFileCopyMode: TFileCopyMode;
                          aFileCopyCallback: TFileCopyCallback): string;
begin
  with TRuFileCopyThread.Create(aSourceName, aDestName,
                          aFileCopyMode, aFileCopyCallback)
  do begin
    OnTerminate := ThreadIsReady;
    FreeOnTerminate := true;
    Start; // Thread wird gestartet
  end;
end;

{------------------------------------------------------------------------------}
{- TRuFileCopyThread ----------------------------------------------------------}
{------------------------------------------------------------------------------}

procedure TRuFileCopyThread.ThreadCopyCallback(ProgressPercent: Integer);
begin
  Synchronize(procedure
              begin
                FFileCopyCallback(ProgressPercent);
              end);
end;

procedure TRuFileCopyThread.ThreadIsReady(Sender: TObject);
begin
  RuFileCopyThreadResult := FCopyResult;
  Synchronize(procedure
              begin
                FFileCopyCallback(cRuFileCopyReady);
              end);
end;

constructor TRuFileCopyThread.Create(aSourceName, aDestName: string;
  aFileCopyMode: TFileCopyMode; aFileCopyCallback: TFileCopyCallback);
begin
  inherited Create(True); // True = Thread nicht automatisch starten
  FCopyResult := '';
  FSourceName := aSourceName;
  FDestName := aDestName;
  FFileCopyMode := aFileCopyMode;
  FFileCopyCallback := aFileCopyCallback;
  FreeOnTerminate := true;
end;

procedure TRuFileCopyThread.Execute;
begin
  if not Terminated
  then FCopyResult := RuFileCopyExecute(FSourceName, FDestName,
                                        FFileCopyMode, FFileCopyCallback);
end;

end.

// Hauptprogramm

type
  TForm1 = class(TForm)
  ...
  public
    { Public-Deklarationen }
    procedure ShowProgress(ProgressPercent: Integer);
  end;

...

procedure TForm1.ShowProgress(ProgressPercent: Integer);
begin
  ProgressBar1.Position := ProgressPercent;
  if ProgressPercent = cRuFileCopyReady
  then Label4.Caption := 'FERTIG: '+DateTimeToStr(Now)+' Result['+RuFileCopyThreadResult+']';
end;
Man kann während des Kopiervorganges das MainFormular beliebig bewegen, da friert absolut nichts ein und auch der ProgressBar geht schön durch.

ToDo:
- Exception-Behandlung
- Gibt es eine bessere Lösung für die globale Variable "RuFileCopyThreadResult" und die in der Unit lokalen Variablen StreamCopyBuffer und ProgressPercent [EDIT] Stichwort "ThreadSafe", weil man dann mehrere Dateien gleichzeitig kopieren könnte. Das wär's
Gruss Otto PS: Sorry wenn ich manchmal banale Fragen stelle. Ich bin Hobby-Programmierer und nicht zu faul die SuFu zu benutzen

Geändert von mm1256 ( 4. Jun 2015 um 18:03 Uhr)
  Mit Zitat antworten Zitat