unit uDivideAndGatherFile_TPS_HTTPFileServerAddon;
{< Dividing and gethering files in chunks

one copy of this Unit should be on Client and it will divide file in chunks
another one - on Server and it will gather file from chunks

}


interface

uses
  System.SysUtils, System.Classes,System.IOUtils,System.Types,
  Contnrs,
  Winapi.Windows, Winapi.Messages, System.Variants,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,

  //myUnits
  uSortStringList,uCreateUniqueName_TPS_HTTPFileServerAddon;

type
  TDivideAndGatherFileServer = class(TDataModule)
  private
    { Private declarations }




   procedure GatherChunkNamesInList(dir:string; var SL:TStringList);

   {<
   Gathers ChunkNames in One List
   we suppose that in one dir chunks only from one file
   so our task to gather all files in that dir to list
   }


  public

    { Public declarations }



   procedure DivideFileInChunks(AFilepath:string; AChunkSize:Int64; ADirForChunks:string);

   {<dividing file in Chunks
   Filepath - path to the file
   ChunkSize - size of the chunk
   DirForChunks - dir where chunks will be kept
   }


   procedure DivideFileInChunks2(AFilepath: string; AChunkSize: Int64; ADirForChunks, ANewFileNameWithGUID: string);
   {<dividing file in Chunks
   AFilepath - path to the file
   AChunkSize - size of the chunk
   ADirForChunks - dir where chunks will be kept
   ANewFileNameWithGUID - NewFileName - givenExternally
   }




   procedure GatherFileFromChunks(AChunksDir: string; AOutputDir: string; var AOutputFilename:string );
   {<Gathers File from Chunks - this method should work on Server
    AChunksDir - relative path here
    AOutputDir - relative path here
   }

  end;

//var
//  DivideAndGatherFile: TDivideAndGatherFile;

implementation

{%CLASSGROUP 'Vcl.Controls.TControl'}

{$R *.dfm}

{ TDivideAndGatherFile }

procedure TDivideAndGatherFileServer.
DivideFileInChunks(AFilepath: string; AChunkSize: Int64; ADirForChunks: string);

var Buffer:TBytes;
    FS:TFileStream;
    MS:TMemoryStream;
    FileName:string;

    ChunkNumber:Int64;


    ByteToRead: Int64;
    ReadSize: Int64;


    NewFileName: string;
  NewFileNameWithGUID: string;

begin

    FS:=TFile.OpenRead(AFilepath);
    FS.Position:=0;

    MS:=TMemoryStream.Create;
    MS.Position:=0;
    FileName:=ExtractFileName(AFilepath);

    //adding guid to fileName
    NewFileNameWithGUID:=TCreateUniqueNameServer.
    CreateUniqueNameAddingGUID(FileName,20);


      try

            try


          ByteToRead:=FS.Size;


          ChunkNumber:=0;
          while ByteToRead>0 do
           begin


              ReadSize := AChunkSize;
              if ReadSize > ByteToRead then ReadSize := ByteToRead;

              MS.CopyFrom(FS,ReadSize);

              NewFileName:=NewFileNameWithGUID;

              NewFileName:=
              TCreateUniqueNameServer.
              AddParamAndValueToName(NewFileName,'chunk',ChunkNumber.ToString);

              MS.SaveToFile(ADirForChunks+NewFileName);
              MS.Clear;
              MS.Position:=0;

              Inc(ChunkNumber);

              //ByteToRead:=ByteToRead-ReadSize; // or dec...

              dec(ByteToRead,ReadSize);

           end;


          finally

          FreeAndNil(FS);
          FreeAndNil(MS);

          end;




    except

    on E:EStreamError do
    raise Exception.Create('This is EStreamError EClassName'+E.ClassName+' '
    +'EMessage '+E.Message);

    on E:Exception do

    raise Exception.Create('This is Error EClassName'+E.ClassName+' '
    +'EMessage '+E.Message);

    end;





end;


//---------------------------------------------


procedure TDivideAndGatherFileServer.
DivideFileInChunks2(AFilepath: string; AChunkSize: Int64; ADirForChunks: string; ANewFileNameWithGUID:string);

var Buffer:TBytes;
    FS:TFileStream;
    MS:TMemoryStream;
    FileName:string;

    ChunkNumber:Int64;


    ByteToRead: Int64;
    ReadSize: Int64;


    NewFileName: string;


begin

    FS:=TFile.OpenRead(AFilepath);
    FS.Position:=0;

    MS:=TMemoryStream.Create;
    MS.Position:=0;
    FileName:=ExtractFileName(AFilepath);



      try

            try


          ByteToRead:=FS.Size;


          ChunkNumber:=0;
          while ByteToRead>0 do
           begin


              ReadSize := AChunkSize;
              if ReadSize > ByteToRead then ReadSize := ByteToRead;

              MS.CopyFrom(FS,ReadSize);

              NewFileName:=ANewFileNameWithGUID;

              NewFileName:='{chunk='+ChunkNumber.ToString+'}';
             // TCreateUniqueName.
            //  AddParamAndValueToName(NewFileName,'chunk',ChunkNumber.ToString);


              if Length(ADirForChunks+NewFileName)>MAX_PATH then // 255 or 260
              raise Exception.Create('FileName '+FileName+' too long - try shorten it');


              MS.SaveToFile(ADirForChunks+NewFileName);
              MS.Clear;
              MS.Position:=0;

              Inc(ChunkNumber);

              //ByteToRead:=ByteToRead-ReadSize; // or dec...

              dec(ByteToRead,ReadSize);

           end;


          finally

          FreeAndNil(FS);
          FreeAndNil(MS);

          end;




    except

    on E:EStreamError do
    raise Exception.Create('This is EStreamError EClassName'+E.ClassName+' '
    +'EMessage '+E.Message);

    on E:Exception do

    raise Exception.Create('This is Error EClassName'+E.ClassName+' '
    +'EMessage '+E.Message);

    end;





end;


//----------------------------------------------------
procedure TDivideAndGatherFileServer.GatherChunkNamesInList(dir: string; var SL: TStringList);
begin

// TDirectory.GetFiles(dir);


end;


//-------------------------------------------------------

procedure TDivideAndGatherFileServer.
GatherFileFromChunks(AChunksDir:string; AOutputDir: string; var AOutputFilename:string );

var FilenamesWithChunksSDA:TStringDynArray;

    ChunkPathesSL:TStringList;
    ChunkNumbersSL:TStringList;

    ChunksPath:string;

    i: Integer;

    SLObjectList:TObjectList;

    SortStringList:TSortStringList;
    ChunkNumber: string;

    FS:TFileStream;
    AppServerPath:string;

    MS: TmemoryStream;
    Outputfilepath: string;

  NamesInDirSDA: TStringDynArray;
  NamesInDirSL: TStringList;
  UniqueFileNameOnServer:String;
  difference:integer;
  StringWithoutExtension:String;
  Extension:String;


begin

//---------------------1 PREPARATION---------------------

//Create
    ChunkPathesSL:=TStringList.Create;
    ChunkNumbersSL:=TStringList.Create;
    SLObjectList:=TObjectList.Create;
    SortStringList:=TSortStringList.Create(Self);


AppServerPath:=ExtractFilePath(Application.ExeName);

//Gathering all files from directory


 try
 FilenamesWithChunksSDA:=TDirectory.GetFiles(AppServerPath+AChunksDir);
 except
 on E:Exception do raise Exception.Create('Error Message '+'Classname= ' +
 E.ClassName+' Message='+E.Message);
 end;


// Filling String lists...


      ChunkPathesSL.Clear;
      for i := Low(FilenamesWithChunksSDA) to High(FilenamesWithChunksSDA) do

      begin

      if FilenamesWithChunksSDA[i].Contains('chunk') then
        begin

        ChunkPathesSL.Add(FilenamesWithChunksSDA[i]);


        ChunkNumber:=
        TCreateUniqueNameServer.GetParamValueFromFileName(FilenamesWithChunksSDA[i],'chunk' );

        ChunkNumbersSL.Add(ChunkNumber);

        end;




      end;


//IntegrityCheck - check chunks and their sizes  - done externally


//Sorting

      SLObjectList.Add(ChunkPathesSL);
      SLObjectList.OwnsObjects:=False;

      // Sorting at last
      SortStringList.SortOneOrderManyStringLists(
      ChunkNumbersSL,
      SLObjectList,
      'int64',
      true
      );



//------------------------2  GATHERING-------------------

{so after all preparations we suggest that fileChunks pathes in ChunkPathesSL }

AppServerPath:=ExtractFilePath(Application.ExeName);

 try

  if not TDirectory.Exists(AppServerPath+AOutputDir) then
  TDirectory.CreateDirectory(AppServerPath+AOutputDir);

 except

  on E:Exception
  do raise Exception.Create('Error Message '+'Classname= '+
  E.ClassName+' Message='+E.Message);

 end;


    //CreateUniqueFileName if needed
    NamesInDirSDA:=TDirectory.GetFiles(AppServerPath+AOutputDir);
    NamesInDirSL:=TStringList.Create;

    for i := 0 to High(NamesInDirSDA) do
    NamesInDirSL.Add(
    ExtractFileName(NamesInDirSDA[i])
    );

    UniqueFileNameOnServer:=
    TCreateUniqueNameServer.CreateUniqueNameAddingNumber(NamesInDirSL,AOutputFilename);



Outputfilepath:=AppServerPath+AOutputDir+'\'+UniqueFileNameOnServer;

// check length of the string
if Outputfilepath.Length>255 then
  begin

        //try to shorten
        difference:=Outputfilepath.Length-255+5; // for example 20 symbols


        if (difference>0) and (UniqueFileNameOnServer.Length>Difference) then begin

        StringWithoutExtension:=
        TCreateUniqueNameServer.GetStringWithoutExtension(UniqueFileNameOnServer,Extension);

        StringWithoutExtension:=StringWithoutExtension.Substring(0,StringWithoutExtension.Length-difference);

        UniqueFileNameOnServer:=
            TCreateUniqueNameServer.CreateUniqueNameAddingNumber(NamesInDirSL,StringWithoutExtension+'.'+Extension);

        Outputfilepath:=AppServerPath+AOutputDir+'\'+UniqueFileNameOnServer;

        end;

  end;

AOutputFilename:=UniqueFileNameOnServer;


FS:=TFile.Create(Outputfilepath);
FS.Position:=0;


MS:=TMemoryStream.Create;

    try

        try

            for i := 0 to ChunkPathesSL.Count-1 do

            begin

            MS.LoadFromFile(ChunkPathesSL[i]);

            FS.CopyFrom(MS,MS.Size);

            MS.Clear;
            MS.Position:=0;

            end;


        except


        on E:EStreamError do
        raise Exception.Create('This is EStreamError EClassName'+E.ClassName+' '
        +'EMessage '+E.Message);

        on E:Exception do
        raise Exception.Create('This is Error EClassName'+E.ClassName+' '
        +'EMessage '+E.Message);



        end;




    finally

       FreeAndNil(FS);
       FreeAndNil(MS);

       FreeAndNil(NamesInDirSL);

    end;











//Freeing
FreeAndNil(ChunkPathesSL);
FreeAndNil(ChunkNumbersSL);
FreeAndNil(SLObjectList);
FreeAndNil(SortStringList);

end;



end.
