John
John

Reputation: 507

File delete after datasnap server method call in Delphi XE6

I have a Datasnap client server application written in Delphi XE6. I am calling a server method that creates temporary file using a TFileStream, fills it with my report in TStream format from my http post, and returns it to my client. This all works as needed. However, at the end of the method, I call deleteFile method to delete the temp file from the server, but it never gets delete. What am I doing wrong?

var
  r,f: String;
  SS: TStringStream;
  Uid: TGuid;
begin
   CreateGuid(Uid);
   f:= ChangeFileExt(GuidToString(Uid),'.rpt');
   result:= TFileStream.Create(f, fmCreate or fmOpenWrite);

  r := getRunReportJSON(ARunReportObj);
  SS := TStringStream.Create(r, TEncoding.ASCII);
  try
    try      
      ServerContainer1.idHttp1.Post(gUrl, SS, result);
      Result.Position:= 0;
    except
    end;
  finally
    SS.Free;
    if FileExists(f) then
      DeleteFile(f)
  end;
end;

Upvotes: 3

Views: 535

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 595349

As David said, you can't delete the file until the TFileStream is destroyed first so it can close its handle to the file. If you want the file to auto-delete when the server is done using it, you can either:

  1. open the file with the Win32 CreateFile() function directly so you can specify the FILE_FLAG_DELETE_ON_CLOSE flag, and then wrap the resulting handle with the THandleStream class.

    type
      TMyHandleStream = class(THandleStream)
      public
        destructor Destroy; override;
      end;
    
    destructor TMyHandleStream.Destroy;
    begin
      inherited;
      CloseHandle(Handle);
    end;
    
    var
      h: THandle;
      r, f: String;
      SS: TStringStream;
      Uid: TGuid;
    begin
      CreateGuid(Uid);
      f := 'C:\some folder\' + GuidToString(Uid) + '.rpt';
      h := Windows.CreateFile(PChar(f), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ, nil, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, 0);
      if h = INVALID_HANDLE_VALUE then RaiseLastOSError;
      Result := TMyHandleStream.Create(h);
      try
        r := getRunReportJSON(ARunReportObj);
        SS := TStringStream.Create(r, TEncoding.ASCII);
        try
          ServerContainer1.IdHTTP1.Post(gUrl, SS, Result);
        finally
          SS.Free;
        end;
        Result.Position := 0;
      except
        Result.Free;
        raise;
      end;
    end;
    
  2. derive a new class from TFileStream and override its destructor to delete the file:

    type
      TMyFileStream = class(TFileStream)
      public
        destructor Destroy; override;
      end;
    
    destructor TMyFileStream.Destroy;
    begin
      inherited;
      DeleteFile(Self.FileName);
    end;
    

    var
      r, f: String;
      SS: TStringStream;
      Uid: TGuid;
    begin
      CreateGuid(Uid);
      f := 'C:\some folder\' + GuidToString(Uid) + '.rpt';
      Result := TMyFileStream.Create(f, fmCreate or fmOpenReadWrite);
      try
        r := getRunReportJSON(ARunReportObj);
        SS := TStringStream.Create(r, TEncoding.ASCII);
        try
          ServerContainer1.IdHTTP1.Post(gUrl, SS, Result);
        finally
          SS.Free;
        end;
        Result.Position := 0;
      except
        Result.Free;
        raise;
      end;
    end;
    
  3. Use the TFileStreamEx class shown in this forum answer:

    type
      TFileStreamEx = class(THandleStream)
      public
        constructor Create(const FileName: string; Mode: Word; flags: DWORD = FILE_ATTRIBUTE_NORMAL);
        destructor Destroy; override;
      end;
    
    constructor TFileStreamEx.Create(const FileName: string; Mode: Word; flags: DWORD = FILE_ATTRIBUTE_NORMAL);
    const
      AccessMode: array[0..2] of LongWord = (
        GENERIC_READ,
        GENERIC_WRITE,
        GENERIC_READ or GENERIC_WRITE);
      ShareMode: array[0..4] of LongWord = (
        0,
        0,
        FILE_SHARE_READ,
        FILE_SHARE_WRITE,
        FILE_SHARE_READ or FILE_SHARE_WRITE);
    begin
      if Mode = fmCreate then
      begin
        inherited Create(
          CreateFile(
            PChar(FileName),
            GENERIC_READ or GENERIC_WRITE,
            0,
            nil,
            CREATE_ALWAYS,
            flags,
            0
          )
        );
        if Handle = INVALID_HANDLE then
          raise EFCreateError.CreateFmt(SFCreateError, [FileName]);
      end
      else
      begin
        inherited Create(
          CreateFile(
            PChar(FileName),
            AccessMode[Mode and 3],
            ShareMode[(Mode and $F0) shr 4],
            nil,
            OPEN_EXISTING,
            flags,
            0
          )
        );
        if Handle = INVALID_HANDLE then
          raise EFOpenError.CreateFmt(SFOpenError, [FileName]);
      end;
    end;
    
    destructor TFileStreamEx.Destroy;
    begin
      if Handle <> INVALID_HANDLE then CloseHandle(Handle);
    end;
    

    var
      r, f: String;
      SS: TStringStream;
      Uid: TGuid;
    begin
      CreateGuid(Uid);
      f := 'C:\some folder\' + GuidToString(Uid) + '.rpt';
      Result := TFileStreamEx.Create(f, fmCreate or fmOpenReadWrite, FILE_FLAG_DELETE_ON_CLOSE);
      try
        r := getRunReportJSON(ARunReportObj);
        SS := TStringStream.Create(r, TEncoding.ASCII);
        try
          ServerContainer1.IdHTTP1.Post(gUrl, SS, Result);
        finally
          SS.Free;
        end;
        Result.Position := 0;
      except
        Result.Free;
        raise;
      end;
    end;
    

Upvotes: 5

David Heffernan
David Heffernan

Reputation: 612794

You cannot delete the file stream that you created until you have destroyed the file stream object. The file stream object wraps a file handle, and while the file handle exists, the file object behind it cannot be deleted.

You need to destroy the file stream object before you can delete it.

Your code has some rather odd exception handling. It swallows exceptions without discrimination in the middle of the function. Do you really want to be quite so brutal? And outside the try/except block, any exceptions will lead to the file stream object being leaked.

There's not really much point in testing whether or not the file exists before deleting it. If you succeeded in creating the file stream then you can be confident that the file exists.

Upvotes: 5

Related Questions