File Broadcast sample (Delphi)

Overview

This example shows how to transfer large amounts of data (disk files in this sample) to and from a Remoting SDK Server in chunks and how to monitor new files via server events.

Getting Started

  • Compile both projects.
  • Launch the server. Notice the folder used to store uploaded files. The path must exist and be writable, you can modify the upload folder if you wish.
  • Open two or more clients applications. Notice the folder used to store downloaded files; the path must exist and be writable. Every client application must use a different download folder to avoid conflicts when broadcasting files.
  • Try to upload a file. Once the file uploaded, the server sends a new notification to all other connected clients, which then download the file to the local download folders.

Examine the Code

  • See how the file is chunked up in the thread and how every part is uploaded with the help of the 'uploadChunk' service method:
function TUploadThread.GetUploadChunk(FileMemStream: TStream; aSize:
  Int64; const Sequence: Integer): Binary;
const
  Block: Integer = 65536;
var
  Position: Int64;
begin
  Result := Binary.Create;
  Position := Block * (Sequence - 1);
  if Position < aSize then begin
    FileMemStream.Position := Position;
    if Position + Block > aSize then
      Result.CopyFrom(FileMemStream, aSize - Position)
    else
      Result.CopyFrom(FileMemStream, Block);
  end;
end;


procedure TUploadThread.Runupload;
var
  FileMemStream: TFileStream;
  Chunk: Binary;
  Sequence: Int64;
  isfirst: Boolean;
begin
  fErrorText := '';
  fUploadOK := false;
  fChannelErrorscount := 0;
  fCurrentBytePos := 0;
  Sequence := 1;
  fTimeStarted := Now;
  isfirst := true;
  if FileExists(fFilename) then try
    FileMemStream := TFileStream.Create(fFileName,fmOpenRead or fmShareDenyWrite);
    try
      fFileName := ExtractFileName(fFileName);

      fFileSize := FileMemStream.Size;

      fInfoStr := DateTimetoStr(fTimeStarted) + ' ' +
        fFileName + ' ' +
        FloatToStrF(Filesize / 1024, fffixed, 15, 1) + ' KB';

      if assigned(fOnStartUpload) then fOnStartUpload(Self);
      Chunk := GetUploadChunk(FileMemStream, fFileSize, Sequence);
      Inc(fCurrentBytePos, Chunk.Size);
      Inc(Sequence);
      try
        fFileService.uploadChunk(isfirst, fFileName, Chunk);
        FreeAndNil(Chunk);
        isfirst := false;
        Chunk := GetUploadChunk(FileMemStream, fFileSize, Sequence);
        while Chunk.Size > 0 do begin

          if Terminated then begin
            if assigned(fOnAbort) then fOnAbort(Self);
            exit;
          end;

          fFileService.uploadChunk(isfirst, fFileName, Chunk);

          Inc(fCurrentBytePos, Chunk.Size);
          FreeAndNil(Chunk);

          Inc(Sequence);
          if assigned(fOnProgress) then fOnProgress(Self);
          Chunk := GetUploadChunk(FileMemStream, fFileSize, Sequence);
        end;

        fUploadOK := (FileSize = 0) or (FileSize = CurrentBytePos);
      finally
        FreeAndNil(Chunk);
      end;
    finally
      FileMemStream.Free;
    end;

  except
    on e: Exception do begin
      fErrorText := e.Message;
      if assigned(fOnError) then fOnError(Self);
    end;
  end;
end;

  • See how chunks are uploaded in service method:
procedure TFileBroadcastService.uploadchunk(const isfirst: Boolean; const filename: Unicodestring; 
                                            const filedata: Binary);
var
  NewFile: TFileStream;
  localfilename: Unicodestring;
begin
  localfilename := getFileDirectory + filename;
  if isfirst and Fileexists(localfilename) then DeleteFile(localfilename);
  if FileExists(localfilename) then
    NewFile := TFileStream.Create(localfilename, fmOpenReadWrite)
  else
    NewFile := TFileStream.Create(localfilename, fmCreate);
  try
    NewFile.Seek(0, soFromEnd);
    filedata.SaveToStream(NewFile);
  finally
    NewFile.Free;
  end;
end;
  • The uploadfinished service method is called in the custom OnUploadFinished event handler:
procedure TFileBroadcastClientMainForm.OnUploadFinished(Sender: TObject);
begin
  fCritical.Enter;
  with Sender as TUploadThread do try
    if UploadOK then begin
      Log(InfoStr + ': Upload finished');
      fFileService.uploadfinished(Filename, FileSize);
    end;
  finally
    fCritical.Leave;
  end;
end;
  • The uploadfinished method raises server event informing all the clients that a file is available for download:
procedure TFileBroadcastService.uploadfinished(const filename: Unicodestring; const filesize: Int64);
begin
  (EventRepository as IFileEvents_Writer).OnNewFileAvailable(Session.SessionID, filename, filesize);
end;
procedure TFileBroadcastService.uploadfinished(const filename: Unicodestring; const filesize: Int64);
begin
  (EventRepository.GetWriter<IFileEvents>(Session.SessionID)).Event.OnNewFileAvailable(filename, filesize);
end;
  • See how the thread for file downloading is created in the server event handler:
procedure TFileBroadcastClientMainForm.OnNewFileAvailable(const filename: Unicodestring; 
                                                          const filesize: Int64);
begin
  TDownloadThread.Create(eFolder.Text, filename, filesize,
    OnDownloadStarted, OnDownloadProgress, OnDownloadFinished,
    OnDownloadAborted, OnDownloadError);
end;
  • See how the file is downloading in chunks in the thread with help from the downloadsequence service method:
procedure TDownloadThread.RunDownload;
var
  NewFile: TFileStream;
  Chunk: Binary;
  Sequence: Integer;
begin
  fDownloadOK := false;
  Chunk := nil;
  try
    if Terminated then begin
      if assigned(fOnAbort) then fOnAbort(Self);
      exit;
    end;
    NewFile := TFileStream.Create(IncludeTrailingPathDelimiter(fDownloadDir) + fFilename,fmCreate);
    try
      fCurrentBytePos := 0;
      Sequence := 1;
      fTimeStarted := Now;

      fFileService.downloadsequence(fFilename, Sequence, chunk, fFileSize);

      fInfoStr := DateTimetoStr(fTimeStarted) + ' ' +
        ExtractFileName(fFilename) + ' ' +
        FloatToStrF(fFilesize / 1024, fffixed, 15, 1) + ' KB';

      if assigned(fOnStartDownload) then
        fOnStartDownload(Self);
        while (chunk <> nil) and  (Chunk.Size > 0) do begin
          if Terminated then begin
            FreeAndNil(Chunk);
            if assigned(fOnAbort) then fOnAbort(Self);
            exit;
          end;

          NewFile.Seek(0, soFromEnd);
          NewFile.CopyFrom(Chunk, Chunk.Size);
          Inc(fCurrentBytePos, Chunk.Size);

          FreeAndNil(Chunk);

          if assigned(fOnProgress) then
            fOnProgress(Self);
          Inc(Sequence);
          fFileService.downloadsequence(fFilename, Sequence, chunk, fFileSize);
        end;

      fDownloadOK := (NewFile.Size = 0) or (fFileSize = CurrentBytePos);

    finally
      FreeAndNil(Chunk);
      NewFile.Free;
    end;

  except
    on e: Exception do begin
      fErrorText := e.Message;
      fDownloadOK := false;
      if assigned(fOnError) then fOnError(Self);
    end;
  end;
end;
  • Examine the downloadsequence service method implementation:
procedure TFileBroadcastService.downloadsequence(const filename: Unicodestring; const sequence: Integer; 
                                                 out filedata: Binary; out filesize: Int64);
const
  Block: Integer = 65536;
var
  Position: Int64;
  MemStream: TFileStream;
  localfilename: Unicodestring;
begin
  fileData := Binary.Create;
  localfilename := getFileDirectory + filename;
  if not FileExists(localfilename) then exit;
  MemStream := TFileStream.Create(localfilename, fmopenRead);
  try
    FileSize := MemStream.Size;
    Position := Block * (Sequence - 1);
    if Position <= FileSize then begin
      MemStream.Position := Position;
      if Position + Block > FileSize then
        fileData.CopyFrom(MemStream, FileSize - Position)
      else
        fileData.CopyFrom(MemStream, Block);
    end;
  finally
    MemStream.Free;
  end;
end;