531 lines
18 KiB
ObjectPascal
531 lines
18 KiB
ObjectPascal
unit KM_NetFileTransfer;
|
|||
|
|
{$I KaM_Remake.inc}
|
||
|
|
interface
|
||
|
|
uses
|
||
|
|
SysUtils, Math, KM_Defaults, KM_CommonClasses, KM_NetTypes, KM_MapTypes
|
||
|
|
{$IFDEF FPC}, zstream {$ENDIF}
|
||
|
|
{$IFDEF WDC}, ZLib {$ENDIF};
|
||
|
|
|
||
|
|
const
|
||
|
|
MAX_TRANSFERS = MAX_LOBBY_SLOTS - 1; //One for each player and spectator
|
||
|
|
|
||
|
|
type
|
||
|
|
TKMTransferEvent = procedure(aClientIndex: TKMNetHandleIndex) of object;
|
||
|
|
TKMTransferPacketEvent = procedure(aClientIndex: TKMNetHandleIndex; aStream: TKMemoryStream; out aSendBufferEmpty: Boolean) of object;
|
||
|
|
TKMTransferProgressEvent = procedure(Total, Progress: Cardinal) of object;
|
||
|
|
TKMTransferProgressPlayerEvent = procedure(aNetPlayerIndex: Integer; Total, Progress: Cardinal) of object;
|
||
|
|
TKMTransferType = (kttMap, kttSave);
|
||
|
|
|
||
|
|
TKMFileSender = class
|
||
|
|
private
|
||
|
|
fReceiverIndex: TKMNetHandleIndex;
|
||
|
|
fChunksInFlight: Byte;
|
||
|
|
fSendStream: TKMemoryStream;
|
||
|
|
procedure AddFileToStream(const aFileName, aPostFix, aExt: UnicodeString);
|
||
|
|
public
|
||
|
|
constructor Create(aType: TKMTransferType; const aBundleName: UnicodeString; aMapKind: TKMMapKind; aReceiverIndex: TKMNetHandleIndex);
|
||
|
|
destructor Destroy; override;
|
||
|
|
procedure WriteChunk(aStream: TKMemoryStream; aLength: Cardinal);
|
||
|
|
procedure AckReceived;
|
||
|
|
function StreamEnd: Boolean;
|
||
|
|
property ReceiverIndex: TKMNetHandleIndex read fReceiverIndex;
|
||
|
|
end;
|
||
|
|
|
||
|
|
TKMFileReceiver = class
|
||
|
|
private
|
||
|
|
fReceiveStream: TKMemoryStream;
|
||
|
|
fType: TKMTransferType;
|
||
|
|
fBundleName: UnicodeString;
|
||
|
|
fBundleCRC: Cardinal;
|
||
|
|
fTotalSize: Cardinal;
|
||
|
|
fReceivedSize: Cardinal;
|
||
|
|
procedure ClearExistingFiles;
|
||
|
|
function ValidExtension(const aExt: UnicodeString): Boolean;
|
||
|
|
public
|
||
|
|
constructor Create(aType: TKMTransferType; const aBundleName: UnicodeString; aBundleCRC: Cardinal = 0);
|
||
|
|
destructor Destroy; override;
|
||
|
|
procedure DataReceived(aStream: TKMemoryStream);
|
||
|
|
property TotalSize: Cardinal read fTotalSize;
|
||
|
|
property ReceivedSize: Cardinal read fReceivedSize;
|
||
|
|
function ProcessTransfer: Boolean;
|
||
|
|
end;
|
||
|
|
|
||
|
|
TKMFileSenderManager = class
|
||
|
|
private
|
||
|
|
fSenders: array[1..MAX_TRANSFERS] of TKMFileSender;
|
||
|
|
fOnTransferCompleted: TKMTransferEvent;
|
||
|
|
fOnTransferPacket: TKMTransferPacketEvent;
|
||
|
|
function ActiveTransferCount: Byte;
|
||
|
|
public
|
||
|
|
destructor Destroy; override;
|
||
|
|
function StartNewSend(aType: TKMTransferType; const aBundleName: String; aMapKind: TKMMapKind;
|
||
|
|
aReceiverIndex: TKMNetHandleIndex): Boolean;
|
||
|
|
procedure AbortAllTransfers;
|
||
|
|
procedure AckReceived(aReceiverIndex: TKMNetHandleIndex);
|
||
|
|
procedure ClientDisconnected(aReceiverIndex: TKMNetHandleIndex);
|
||
|
|
procedure UpdateStateIdle(aSendBufferEmpty: Boolean);
|
||
|
|
property OnTransferCompleted: TKMTransferEvent write fOnTransferCompleted;
|
||
|
|
property OnTransferPacket: TKMTransferPacketEvent write fOnTransferPacket;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
implementation
|
||
|
|
uses
|
||
|
|
KromUtils, KM_Maps, KM_Saves,
|
||
|
|
KM_ScriptPreProcessor, KM_ScriptFilesCollection,
|
||
|
|
KM_Log, KM_FileIO;
|
||
|
|
|
||
|
|
|
||
|
|
const
|
||
|
|
VALID_MAP_EXTENSIONS: array [1..5] of UnicodeString = ('map', 'dat', EXT_FILE_SCRIPT, 'txt', 'pdf');
|
||
|
|
VALID_MAP_EXTENSIONS_POSTFIX: array[1..5] of UnicodeString = ('libx', 'wav', 'ogg', 'pdf', 'tiles');
|
||
|
|
VALID_SAVE_EXTENSIONS: array[1..3] of UnicodeString = (EXT_SAVE_MAIN, EXT_SAVE_BASE, EXT_SAVE_REPLAY);
|
||
|
|
|
||
|
|
|
||
|
|
function GetFullSourceFileName(aType: TKMTransferType; const aName: String; aMapKind: TKMMapKind; const aPostfix, aExt: UnicodeString): String;
|
||
|
|
begin
|
||
|
|
case aType of
|
||
|
|
kttMap: Result := TKMapsCollection.FullPath(aName, aPostfix + '.' + aExt, aMapKind);
|
||
|
|
kttSave: Result := TKMSavesCollection.FullPath(aName, aExt, True);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function GetFullDestFileName(aType: TKMTransferType; const aName, Postfix, aExt: String; const aCustomFileName: UnicodeString = ''): String;
|
||
|
|
begin
|
||
|
|
case aType of
|
||
|
|
kttMap: if aCustomFileName = '' then
|
||
|
|
Result := TKMapsCollection.FullPath(aName, Postfix + '.' + aExt, mkDL)
|
||
|
|
else
|
||
|
|
Result := TKMapsCollection.FullPath(aName, aCustomFileName, Postfix + '.' + aExt, mkDL);
|
||
|
|
kttSave: Result := TKMSavesCollection.Path(DOWNLOADED_LOBBY_SAVE, True) + DOWNLOADED_LOBBY_SAVE + '.' + aExt;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
{ TKMFileSender }
|
||
|
|
constructor TKMFileSender.Create(aType: TKMTransferType; const aBundleName: UnicodeString; aMapKind: TKMMapKind;
|
||
|
|
aReceiverIndex: TKMNetHandleIndex);
|
||
|
|
var
|
||
|
|
I, J: Integer;
|
||
|
|
F: TSearchRec;
|
||
|
|
fileName, postfix: UnicodeString;
|
||
|
|
sourceStream: TKMemoryStream;
|
||
|
|
compressionStream: TCompressionStream;
|
||
|
|
scriptPreProcessor: TKMScriptPreProcessor;
|
||
|
|
scriptFiles: TKMScriptFilesCollection;
|
||
|
|
begin
|
||
|
|
inherited Create;
|
||
|
|
fReceiverIndex := aReceiverIndex;
|
||
|
|
fSendStream := TKMemoryStreamBinary.Create;
|
||
|
|
fSendStream.PlaceMarker('TransferCompressed');
|
||
|
|
fSendStream.Write(aType, SizeOf(aType));
|
||
|
|
fSendStream.WriteW(aBundleName);
|
||
|
|
//Fill stream with data to be sent
|
||
|
|
case aType of
|
||
|
|
kttMap: begin
|
||
|
|
// Supports next file names:
|
||
|
|
// MapName.dat
|
||
|
|
// MapName.map
|
||
|
|
// MapName.txt
|
||
|
|
// MapName.pdf
|
||
|
|
// MapName.script + any included script
|
||
|
|
for I := Low(VALID_MAP_EXTENSIONS) to High(VALID_MAP_EXTENSIONS) do
|
||
|
|
begin
|
||
|
|
fileName := GetFullSourceFileName(aType, aBundleName, aMapKind, '', VALID_MAP_EXTENSIONS[I]);
|
||
|
|
if FileExists(fileName) then
|
||
|
|
AddFileToStream(fileName, '', VALID_MAP_EXTENSIONS[I]);
|
||
|
|
//Add all included script files
|
||
|
|
if (VALID_MAP_EXTENSIONS[I] = EXT_FILE_SCRIPT) and FileExists(fileName) then
|
||
|
|
begin
|
||
|
|
scriptPreProcessor := TKMScriptPreProcessor.Create(False);
|
||
|
|
try
|
||
|
|
if not scriptPreProcessor.PreProcessFile(fileName) then
|
||
|
|
//throw an Exception if PreProcessor was not successful to cancel FileSender creation
|
||
|
|
raise Exception.Create('Can''n start send file because of error while script pre-processing');
|
||
|
|
scriptFiles := scriptPreProcessor.ScriptFilesInfo;
|
||
|
|
for J := 0 to scriptFiles.IncludedCount - 1 do
|
||
|
|
begin
|
||
|
|
if FileExists(scriptFiles[J].FullFilePath) then
|
||
|
|
AddFileToStream(scriptFiles[J].FullFilePath, '', EXT_FILE_SCRIPT);
|
||
|
|
end;
|
||
|
|
finally
|
||
|
|
scriptPreProcessor.Free;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
// Supports next file names:
|
||
|
|
// MapName.snd_name.wav / .ogg
|
||
|
|
// MapName.snd_name.eng.wav / .ogg
|
||
|
|
// MapName.eng.libx
|
||
|
|
// MapName.eng.pdf
|
||
|
|
for I := Low(fileName) to High(VALID_MAP_EXTENSIONS_POSTFIX) do
|
||
|
|
begin
|
||
|
|
fileName := GetFullSourceFileName(aType, aBundleName, aMapKind, '.*', VALID_MAP_EXTENSIONS_POSTFIX[I]);
|
||
|
|
try
|
||
|
|
if FindFirst(fileName, faAnyFile, F) = 0 then
|
||
|
|
begin
|
||
|
|
repeat
|
||
|
|
if (F.Attr and faDirectory = 0) then
|
||
|
|
begin
|
||
|
|
// Find everything except map name and extension
|
||
|
|
// Cut ext with dot
|
||
|
|
postfix := ChangeFileExt(F.Name, '');
|
||
|
|
// Find 'MapName.'
|
||
|
|
if Pos(aBundleName + '.', postfix) = 1 then
|
||
|
|
// Copy '.snd_name.eng'
|
||
|
|
postfix := Copy(postfix, Length(aBundleName) + 1, Length(postfix))
|
||
|
|
else
|
||
|
|
// No postfix was found
|
||
|
|
postfix := '';
|
||
|
|
|
||
|
|
AddFileToStream(ExtractFilePath(fileName) + F.Name, postfix, VALID_MAP_EXTENSIONS_POSTFIX[I]);
|
||
|
|
end;
|
||
|
|
until FindNext(F) <> 0;
|
||
|
|
end;
|
||
|
|
finally
|
||
|
|
FindClose(F);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
kttSave: for I := Low(VALID_SAVE_EXTENSIONS) to High(VALID_SAVE_EXTENSIONS) do
|
||
|
|
begin
|
||
|
|
fileName := TKMSavesCollection.FullPath(aBundleName, VALID_SAVE_EXTENSIONS[I], True);
|
||
|
|
if FileExists(fileName) then
|
||
|
|
AddFileToStream(fileName, '', VALID_SAVE_EXTENSIONS[I]);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Compress fSendStream
|
||
|
|
sourceStream := fSendStream;
|
||
|
|
fSendStream := TKMemoryStreamBinary.Create;
|
||
|
|
fSendStream.PlaceMarker('Transfer');
|
||
|
|
compressionStream := TCompressionStream.Create(clDefault, fSendStream);
|
||
|
|
compressionStream.CopyFrom(sourceStream, 0);
|
||
|
|
// fSendStream now contains the compressed data from SourceStream
|
||
|
|
compressionStream.Free;
|
||
|
|
sourceStream.Free;
|
||
|
|
fSendStream.Position := 0;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
destructor TKMFileSender.Destroy;
|
||
|
|
begin
|
||
|
|
fSendStream.Free;
|
||
|
|
inherited;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMFileSender.AckReceived;
|
||
|
|
begin
|
||
|
|
Dec(fChunksInFlight);
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMFileSender.AddFileToStream(const aFileName, aPostFix, aExt: UnicodeString);
|
||
|
|
var
|
||
|
|
fileStream: TKMemoryStream;
|
||
|
|
begin
|
||
|
|
fileStream := TKMemoryStreamBinary.Create;
|
||
|
|
fileStream.LoadFromFile(aFileName);
|
||
|
|
|
||
|
|
fSendStream.PlaceMarker('FileStart');
|
||
|
|
fSendStream.WriteW(TruncateExt(ExtractFileName(aFileName)));
|
||
|
|
fSendStream.WriteW(aPostFix);
|
||
|
|
fSendStream.WriteW(aExt);
|
||
|
|
fSendStream.Write(Cardinal(fileStream.Size));
|
||
|
|
if fileStream.Size > 0 then
|
||
|
|
fSendStream.CopyFrom(fileStream, fileStream.Size);
|
||
|
|
|
||
|
|
fileStream.Free;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMFileSender.WriteChunk(aStream: TKMemoryStream; aLength: Cardinal);
|
||
|
|
begin
|
||
|
|
if aLength > fSendStream.Size - fSendStream.Position then
|
||
|
|
aLength := fSendStream.Size - fSendStream.Position;
|
||
|
|
|
||
|
|
aStream.PlaceMarker('FileChunk');
|
||
|
|
aStream.Write(aLength);
|
||
|
|
aStream.Write(Cardinal(fSendStream.Size)); //Every chunk includes the total transfer size
|
||
|
|
aStream.CopyFrom(fSendStream, aLength);
|
||
|
|
Inc(fChunksInFlight);
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TKMFileSender.StreamEnd: Boolean;
|
||
|
|
begin
|
||
|
|
Result := fSendStream.Position = fSendStream.Size;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
{ TKMFileReceiver }
|
||
|
|
constructor TKMFileReceiver.Create(aType: TKMTransferType; const aBundleName: UnicodeString; aBundleCRC: Cardinal = 0);
|
||
|
|
begin
|
||
|
|
inherited Create;
|
||
|
|
fReceiveStream := TKMemoryStreamBinary.Create;
|
||
|
|
fType := aType;
|
||
|
|
fBundleName := aBundleName;
|
||
|
|
fBundleCRC := aBundleCRC;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
destructor TKMFileReceiver.Destroy;
|
||
|
|
begin
|
||
|
|
FreeAndNil(fReceiveStream);
|
||
|
|
inherited;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMFileReceiver.DataReceived(aStream: TKMemoryStream);
|
||
|
|
var
|
||
|
|
chunkSize: Cardinal;
|
||
|
|
begin
|
||
|
|
aStream.CheckMarker('FileChunk');
|
||
|
|
aStream.Read(chunkSize);
|
||
|
|
aStream.Read(fTotalSize); //Every chunk includes the total transfer size
|
||
|
|
Assert(aStream.Size - aStream.Position = chunkSize, 'Chunk corrupted');
|
||
|
|
fReceiveStream.CopyFrom(aStream, chunkSize);
|
||
|
|
fReceivedSize := fReceivedSize + chunkSize;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMFileReceiver.ClearExistingFiles;
|
||
|
|
var
|
||
|
|
searchRec: TSearchRec;
|
||
|
|
fileName, saveFolder: UnicodeString;
|
||
|
|
begin
|
||
|
|
//Prepare destination
|
||
|
|
case fType of
|
||
|
|
kttMap: begin
|
||
|
|
//Create downloads folder if it's missing
|
||
|
|
fileName := ExeDir + MAPS_DL_FOLDER_NAME;
|
||
|
|
if not DirectoryExists(fileName) then
|
||
|
|
CreateDir(fileName);
|
||
|
|
//Create map folder if it is missing
|
||
|
|
fileName := fileName + PathDelim + fBundleName;
|
||
|
|
if not DirectoryExists(fileName) then
|
||
|
|
CreateDir(fileName)
|
||
|
|
else
|
||
|
|
try
|
||
|
|
//If any files already exist in the folder, delete them
|
||
|
|
if FindFirst(fileName + PathDelim + fBundleName + '*.*', faAnyFile, searchRec) = 0 then
|
||
|
|
begin
|
||
|
|
repeat
|
||
|
|
if (searchRec.Attr and faDirectory = 0) then
|
||
|
|
KMDeleteFile(fileName + PathDelim + searchRec.Name);
|
||
|
|
until FindNext(searchRec) <> 0;
|
||
|
|
end;
|
||
|
|
finally
|
||
|
|
FindClose(searchRec);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
kttSave: begin
|
||
|
|
saveFolder := TKMSavesCollection.Path(DOWNLOADED_LOBBY_SAVE, True);
|
||
|
|
KMDeleteFolder(saveFolder); // Delete old folder
|
||
|
|
ForceDirectories(saveFolder); // Create new
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TKMFileReceiver.ValidExtension(const aExt: UnicodeString): Boolean;
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
|
||
|
|
case fType of
|
||
|
|
kttMap: begin
|
||
|
|
for I := Low(VALID_MAP_EXTENSIONS) to High(VALID_MAP_EXTENSIONS) do
|
||
|
|
if aExt = VALID_MAP_EXTENSIONS[I] then
|
||
|
|
Exit(True);
|
||
|
|
for I := Low(VALID_MAP_EXTENSIONS_POSTFIX) to High(VALID_MAP_EXTENSIONS_POSTFIX) do
|
||
|
|
if aExt = VALID_MAP_EXTENSIONS_POSTFIX[I] then
|
||
|
|
Exit(True);
|
||
|
|
end;
|
||
|
|
kttSave: for I := Low(VALID_SAVE_EXTENSIONS) to High(VALID_SAVE_EXTENSIONS) do
|
||
|
|
if aExt = VALID_SAVE_EXTENSIONS[I] then
|
||
|
|
Exit(True);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TKMFileReceiver.ProcessTransfer: Boolean;
|
||
|
|
var
|
||
|
|
readType: TKMTransferType;
|
||
|
|
readName, ext, postfix, transferedFileName, fileName: UnicodeString;
|
||
|
|
readSize: Cardinal;
|
||
|
|
fileStream: TKMemoryStream;
|
||
|
|
decompressionStream: TDecompressionStream;
|
||
|
|
readStream: TKMemoryStream;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
if fReceiveStream.Size = 0 then Exit; //Transfer was aborted
|
||
|
|
|
||
|
|
//Decompress the stream
|
||
|
|
fReceiveStream.Position := 0;
|
||
|
|
fReceiveStream.CheckMarker('Transfer');
|
||
|
|
decompressionStream := TDecompressionStream.Create(fReceiveStream);
|
||
|
|
//We need custom methods like ReadAssert, ReadW, etc. so we need to read from a TKMemoryStream
|
||
|
|
readStream := TKMemoryStreamBinary.Create;
|
||
|
|
readStream.CopyFromDecompression(decompressionStream);
|
||
|
|
decompressionStream.Free;
|
||
|
|
readStream.Position := 0;
|
||
|
|
|
||
|
|
//Read from the stream
|
||
|
|
readStream.CheckMarker('TransferCompressed');
|
||
|
|
readStream.Read(readType, SizeOf(readType));
|
||
|
|
Assert(readType = fType, 'Unexpected transfer type received');
|
||
|
|
readStream.ReadW(readName);
|
||
|
|
if (readName <> fBundleName) and (readName + '_' + IntToHex(fBundleCRC, 8) <> fBundleName) then
|
||
|
|
raise Exception.Create('Unexpected transfer name received');
|
||
|
|
|
||
|
|
ClearExistingFiles;
|
||
|
|
|
||
|
|
//Load each file
|
||
|
|
while readStream.Position < readStream.Size do
|
||
|
|
begin
|
||
|
|
readStream.CheckMarker('FileStart');
|
||
|
|
readStream.ReadW(transferedFileName);
|
||
|
|
readStream.ReadW(postfix);
|
||
|
|
readStream.ReadW(ext);
|
||
|
|
//Check EXT is valid (so we don't allow EXEs and stuff)
|
||
|
|
Assert(ValidExtension(ext), 'Unexpected file extension received');
|
||
|
|
|
||
|
|
readStream.Read(readSize);
|
||
|
|
fileStream := TKMemoryStreamBinary.Create;
|
||
|
|
// Don't try to CopyFrom, if file is empty!
|
||
|
|
// because, according to http://docwiki.embarcadero.com/Libraries/Sydney/en/System.Classes.TStream.CopyFrom
|
||
|
|
// > If Count is 0, CopyFrom sets Source position to 0 before reading and then copies the entire contents of Source into the stream
|
||
|
|
if readSize > 0 then
|
||
|
|
fileStream.CopyFrom(readStream, readSize);
|
||
|
|
|
||
|
|
// Scripts can have arbitrary names
|
||
|
|
if (ext = EXT_FILE_SCRIPT) and (transferedFileName <> readName) then
|
||
|
|
fileName := GetFullDestFileName(fType, fBundleName, postfix, ext, transferedFileName)
|
||
|
|
else
|
||
|
|
fileName := GetFullDestFileName(fType, fBundleName, postfix, ext);
|
||
|
|
|
||
|
|
// Sometimes the file is not deleted yet. Probably OS glitches
|
||
|
|
if FileExists(fileName) then
|
||
|
|
KMDeleteFile(fileName);
|
||
|
|
|
||
|
|
fileStream.SaveToFile(fileName);
|
||
|
|
fileStream.Free;
|
||
|
|
end;
|
||
|
|
readStream.Free;
|
||
|
|
Result := True;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
{ TKMFileSenderManager }
|
||
|
|
procedure TKMFileSenderManager.AbortAllTransfers;
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
for I := Low(fSenders) to High(fSenders) do
|
||
|
|
FreeAndNil(fSenders[I]);
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMFileSenderManager.AckReceived(aReceiverIndex: TKMNetHandleIndex);
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
for I := Low(fSenders) to High(fSenders) do
|
||
|
|
if (fSenders[I] <> nil) and (fSenders[I].ReceiverIndex = aReceiverIndex) then
|
||
|
|
fSenders[I].AckReceived;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMFileSenderManager.ClientDisconnected(aReceiverIndex: TKMNetHandleIndex);
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
for I := Low(fSenders) to High(fSenders) do
|
||
|
|
if (fSenders[I] <> nil) and (fSenders[I].ReceiverIndex = aReceiverIndex) then
|
||
|
|
FreeAndNil(fSenders[I]);
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
destructor TKMFileSenderManager.Destroy;
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
for I := Low(fSenders) to High(fSenders) do
|
||
|
|
FreeAndNil(fSenders[I]);
|
||
|
|
inherited;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TKMFileSenderManager.StartNewSend(aType: TKMTransferType; const aBundleName: String; aMapKind: TKMMapKind;
|
||
|
|
aReceiverIndex: TKMNetHandleIndex): Boolean;
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
bundleName: String;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
bundleName := aBundleName; //To save const String param locally //@Rey: There are no threads here, it is not needed?
|
||
|
|
|
||
|
|
for I := Low(fSenders) to High(fSenders) do
|
||
|
|
if (fSenders[I] = nil) or (fSenders[I].ReceiverIndex = aReceiverIndex) then
|
||
|
|
begin
|
||
|
|
if fSenders[I] <> nil then
|
||
|
|
//There is an existing transfer to this client, so free it
|
||
|
|
fSenders[I].Free;
|
||
|
|
try
|
||
|
|
fSenders[I] := TKMFileSender.Create(aType, bundleName, aMapKind, aReceiverIndex);
|
||
|
|
except
|
||
|
|
on E: Exception do
|
||
|
|
begin
|
||
|
|
gLog.AddTime(E.Message);
|
||
|
|
// Ignore exception, just return False
|
||
|
|
Exit(False);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
Exit(True);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TKMFileSenderManager.ActiveTransferCount: Byte;
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
Result := 0;
|
||
|
|
for I := Low(fSenders) to High(fSenders) do
|
||
|
|
if fSenders[I] <> nil then
|
||
|
|
Inc(Result);
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMFileSenderManager.UpdateStateIdle(aSendBufferEmpty: Boolean);
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
stream: TKMemoryStream;
|
||
|
|
clientIndex: TKMNetHandleIndex;
|
||
|
|
maxChunksInFlightPerSender: Byte;
|
||
|
|
begin
|
||
|
|
//Reserve some bandwidth for each sender
|
||
|
|
maxChunksInFlightPerSender := Max(1, MAX_CHUNKS_BEFORE_ACK div Max(1, ActiveTransferCount));
|
||
|
|
for I := Low(fSenders) to High(fSenders) do
|
||
|
|
while (fSenders[I] <> nil) and (fSenders[I].fChunksInFlight < maxChunksInFlightPerSender) and aSendBufferEmpty do
|
||
|
|
begin
|
||
|
|
stream := TKMemoryStreamBinary.Create;
|
||
|
|
fSenders[I].WriteChunk(stream, FILE_CHUNK_SIZE);
|
||
|
|
fOnTransferPacket(fSenders[I].ReceiverIndex, stream, aSendBufferEmpty); //Updates SendBufferEmpty
|
||
|
|
stream.Free;
|
||
|
|
if fSenders[I].StreamEnd then
|
||
|
|
begin
|
||
|
|
clientIndex := fSenders[I].ReceiverIndex;
|
||
|
|
FreeAndNil(fSenders[I]); //We must free it before calling OnTransferCompleted
|
||
|
|
fOnTransferCompleted(clientIndex);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
end.
|
||
|
|
|