Einzelnen Beitrag anzeigen

Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#1

TStrings.SaveToFileSafety

  Alt 28. Jun 2010, 12:45
Wenn man über SaveToFile z.B. etwas speichert und beim Speichern etwas passiert (z.B. irgendeine Exception), dann sind alle Daten futsch.

Sowohl die Originaldatei, wenn vorher noch eine alte gleichnamige Datei existierte, sowohl eventuell auch die aktuellen Daten.

Diese Funktion legt daher eine temporäre Datei an, speichert darin die Stringliste und tauscht im Nachhinein, wenn alles Erfolgreich war, die Dateien erst aus.

Als Bonus bleibt die Originaldatei sogar noch erhalten.
Ein "normales" SaveToFile überschreibt ja die alten Daten unwiederruflich.
Dabei bleibt das alte Original entweder im Papierkorb erhalten oder es ließe sich eine Weile lang über entsprechende Datenrettungstools aus dem Dateisystem wiederherstellen.
(gelöschte Dateien werden ja nicht sofort im Dateisystem entfernt/überschrieben)

Delphi-Quellcode:
Uses RTLConsts, ShellAPI;

Type TStringsSafetyHelper = Class Helper for TStrings
    Procedure SaveToFileSafety(Const FileName: String;
      BackupToRecycler: Boolean = False); Overload;
    Procedure SaveToFileSafety(Const FileName: String; Encoding: TEncoding = nil;
      BackupToRecycler: Boolean = False); Overload;
  End;

Procedure TStringsSafetyHelper.SaveToFileSafety(Const FileName: String;
    BackupToRecycler: Boolean = False);

  Begin
    SaveToFileSafety(FileName, nil, BackupToRecycler);
  End;

Procedure TStringsSafetyHelper.SaveToFileSafety(Const FileName: String; Encoding: TEncoding = nil;
    BackupToRecycler: Boolean = False);

  Var H, Hs: THandle;
    i: Integer;
    Stream: TStream;
    SHFile: TSHFileOpStruct;

  Begin
    Hs := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
    If Hs <> INVALID_HANDLE_VALUE Then Begin
      H := INVALID_HANDLE_VALUE;
      Try
        i := -1;
        Try
          Repeat
            Inc(i);
            H := CreateFile(PChar(Format('%s.%d', [FileName, i])), GENERIC_READ or GENERIC_WRITE,
              0, nil, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, Hs);
          Until (H <> INVALID_HANDLE_VALUE) or (i >= $FFFF);
          If H = INVALID_HANDLE_VALUE Then
            Raise EFCreateError.CreateResFmt(@SFCreateErrorEx, [ExpandFileName(FileName), SysErrorMessage(GetLastError)]);
          Stream := THandleStream.Create(H);
          Try
            SaveToStream(Stream, Encoding);
          Finally
            Stream.Free;
          End;
          FlushFileBuffers(H);
          If BackupToRecycler Then Begin
            SHFile.Wnd := 0;
            SHFile.wFunc := FO_DELETE;
            SHFile.pFrom := PChar(FileName + #0);
            SHFile.pTo := nil;
            SHFile.fFlags := FOF_ALLOWUNDO or FOF_NOCONFIRMATION or FOF_NOERRORUI or FOF_NO_UI or FOF_SILENT;
            If not SHFileOperation(SHFile) Then
              Raise EFCreateError.CreateResFmt(@SFCreateErrorEx, [ExpandFileName(FileName), SysErrorMessage(GetLastError)]);
          End Else If not Windows.DeleteFile(PChar(FileName)) Then
            Raise EFCreateError.CreateResFmt(@SFCreateErrorEx, [ExpandFileName(FileName), SysErrorMessage(GetLastError)]);
          If not MoveFile(PChar(Format('%s.%d', [FileName, i])), PChar(FileName)) Then Begin
            i := -1;
            Raise EFCreateError.CreateResFmt(@SFCreateErrorEx, [ExpandFileName(FileName), SysErrorMessage(GetLastError)]);
          End;
        Except
          If i <> -1 Then Windows.DeleteFile(PChar(Format('%s.%d', [FileName, i])));
          Raise;
        End;
      Finally
        CloseHandle(H);
      End;
    End Else SaveToFile(FileName, Encoding);
  End;
So wie die Funktion ist, läßt sie sich nur ab Delphi 2009 nutzen,
aber indem man einfach nur den Encoding-Parameter entfernt, würde es bis runter zu Delphi 2006 / Turbo Delphi funktionieren.
Und für noch ältere Delphi-Versionen muß man sich "nur" eine eigene Klasse/Funktion aus diesem Class-Helper basteln.

Die ganze Speicherroutine liese sich auch auf andere Dateispeicherungen anwenden, nicht nur für Stringlisten.
Dafür müßte man dann halt nur die Speicherroutinen des Objektes anpassen/austauschen und z.B. einen passenden Class-Helper erstellen.
$2B or not $2B

Geändert von himitsu (28. Jun 2010 um 13:02 Uhr)
  Mit Zitat antworten Zitat