Memo1.Lines.LoadFromStream from IOHandler.ReadStream - delphi

I was trying to display the sent textfile to memo.lines with out saving it to a disk
from server
try
Ms := TMemoryStream.Create;
Ms.LoadFromFile('update.txt');
Ms.Position := 0;
AContext.Connection.IOHandler.LargeStream := True;
AContext.Connection.IOHandler.Write(Ms, 0, True);
finally
Ms.Free;
end;
to client...im not sure how to do this in client
try
Ms := TMemoryStream.Create;
Ms.Position := 0;
IdTCPClient1.IOHandler.LargeStream := True;
IdTCPClient1.Connection.IOHandler.ReadStream(Ms, -1,false);
finally
Memo1.Lines.LoadFromStream(Ms);
Ms.Free;
end;
can anyone help me on how to make this work if its possible ?

Your code is fine, you simply forgot to reset the stream's Position property back to 0 before calling the Memo's LoadFromStream() method:
IdTCPClient1.Connection.IOHandler.ReadStream(Ms, -1,false);
Ms.Position := 0; // <-- add this
Memo1.Lines.LoadFromStream(Ms);

If I were you, I would still buffer the read data because of two reasons:
The network stream is unidirectional and non-positionable therefore not really file like. (Yes, it also means, that you must refresh it manually/programmatically every time some new data is received.)
If the connection fails the read buffer could be filled with gibberish which gets diplayed, because You have no further means to control what gets displayed once you redirected the input of your memo.
If you do not want to save the sent data to disk, you could still save it to a TMemoryStream instance, which could be used as a parameter of the memo's .LoadFromStream() method.

Related

Indy. sending Attach pdf by stream

I'm having trouble sending attachments via stream.
I use Indy 10.6.2 and Delphi Berlin.
The mail consists of html with attached images, plus one or more PDF files inserted directly from the database.
I don't get any errors in the process.
Mail is sent seamlessly, but attached PDFs are not received.
I look forward to comments
Msg := TIdMessage.Create(self);
try
Msg.ContentType := 'multipart/mixed';
Msg.From.Name := FromName;
Msg.From.Address := FromAddress;
Msg.Priority := mpHigh;
Msg.Subject := Asunto;
with TIdMessageBuilderHtml.Create do
try
if FMsgTxtPlnPac <> '' then
PlainText.Text := FMsgTxtPlnPac;
if FMsgHtmlPac <> '' then
begin
Html.Text := FMsgHtmlPac;
n := 0;
for s in FMsgHTMLFiles.Split([',']) do
begin
n := Succ(n);
c := 'img' + inttostr(n);
HTMLFiles.Add(s, c); // cid de imagen en HTML(cid:img1, cid:img2...)
end;
end;
if AttachFiles <> '' then
for s in AttachFiles.Split([',']) do
Attachments.Add(s);
// Attach from DB
while not dm.qryBlb.Eof do
begin
Attach := TIdAttachmentMemory.Create(Msg.MessageParts);
Attach.ContentType := 'application/pdf';
Attach.FileName := dm.qryBlb.FieldByName('nombre_archivo').AsString;
Attach.LoadFromStream(dm.GetDataBlbStrm('DATA_TXT')); // this ok in attach.datastream.size
Attach.CloseLoadStream;
dm.qryBlb.Next;
end;
FillMessage(Msg);
finally
Free;
end;
for s in FMailPac.Split([',']) do
begin
EmailAddress := Trim(s);
if EmailAddress <> '' then
begin
with Msg.recipients.Add do
begin
Address := EmailAddress;
end;
end;
end;
for s in MailCC.Split([',']) do
begin
EmailAddress := Trim(s);
if EmailAddress <> '' then
Msg.CCList.Add.Address := EmailAddress;
end;
for s in MailCCO.Split([',']) do
begin
EmailAddress := Trim(s);
if EmailAddress <> '' then
Msg.BccList.Add.Address := EmailAddress;
end;
finally
SMTP1.Send(Msg);
end;
TIdMessageBuilderHtml supports adding attachments via streams, as well as via files. However, those streams have to remain alive for the duration that the TIdCustomMessageBuilder.Attachments collection is populated, which is not an option in your case since you are looping through DB records one at a time, thus you would only be able to access 1 DB stream at a time.
You could create a local array/list of TMemoryStream objects, and then populate the TMessageBuilderHtml with those streams, but you will end up wasting a lot of memory that way since TIdMessageBuilderHtml would make its own copy of the TMemoryStream data. And there is no way to have TIdMessageBuilderHtml just use your TMemoryStream data as-is in a read-only mode (hmm, I wonder if I should add that feature!).
The reason why your manual TIdAttachmentMemory objects don't work is simply because TIdCustomMessageBuilder.FillMessage() clears the TIdMessage's body before then re-populating it, thus losing your attachments (and various other properties that you are setting manually beforehand).
You would have to add your DB attachments to the TIdMessage after FillMessage() has done its work first. But, then you risk TIdMessageBuilderHtml not setting up the TIdMessage structure properly since it wouldn't know your DB attachments exist.
On a side note, you are not using TIdAttachmentMemory correctly anyway. Do not call its CloseLoadStream() method if you have not called its OpenLoadStream() method first. Calling its LoadFromStream() method is enough in this case (or, you can even pass the TStream to TIdAttachmentMemory's constructor). Do note, however, that you are leaking the TStream returned by dm.GetDataBlbStrm().
So, in this case, you are probably better off simply populating the TIdMessage manually and not use TIdMessageBuilderHtml at all. Or, you could derive a new class from TIdMessageBuilderHtml (or TIdCustomMessageBuilder directly) and override its virtual FillBody() and FillHeaders() methods to take your DB streams into account.

Send array of string using tcp

I'm not sure if this is possible but here it goes.
Usually I send strings like this...
Connection.IOHandler.WriteLn('alpha');
Connection.IOHandler.WriteLn('bravo');
Connection.IOHandler.WriteLn('charley');
//and so on..
But what if I want to send it in just one go, just send it all at once.
Maybe I could put it on an array of strings then send the array.
someStr : array[1..3] of string = ('alpha','bravo','charley');//this could be more
...
StrListMem := TMemoryStream.Create;
try
StrListMem.WriteBuffer(someStr[0], Length(someStr));
StrListMem.Position:=0;
Connection.IOHandler.Write(StrListMem, 0, True);
finally
StrListMem.Free;
end;
I just have no idea how to this right, maybe somebody can give an example? and how the receiver(client) will read it.
EDIT:
I also having problem in how to read the stream, not sure what's wrong with this code.
Client:
msRecInfo: TMemoryStream;
arrOf: Array of Char;
...
msRecInfo := TMemoryStream.Create;
try
AContext.Connection.IOHandler.ReadStream(msRecInfo, -1, False);
SetLength(arrOf, msRecInfo.Size div SizeOf(Char));
msRecInfo.Position := 0;
msRecInfo.ReadBuffer(arrOf[0], Length(arrOf) * SizeOf(Char));
finally
MessageBox(0,pChar(arrOf[0]),0,0);//memo1.Lines.Add(arrOf[0]);
msRecInfo.Free;
end;
You can write the whole array to stream in one swoop. You actually already wrote close to a correct code
StrListMem := TMemoryStream.Create;
try
for I := 0 to Length(someStr) - 1 do
begin
StrListMem.WriteBuffer(someStr[I][1], Length(someStr[I]) * SizeOf(Char));
StrListMem.WriteBuffer(sLineBreak[1], Length(sLineBreak) * SizeOf(Char));
end;
StrListMem.Position:=0;
Connection.IOHandler.Write(StrListMem, 0, True);
finally
StrListMem.Free;
end;
Alternatively if you want to only work with data and abstract the transport layer away (read, easier coding) you can check out my IMC. It abstracts some things for you making it easier.
EDIT:
I added the newline character after each string as it seems to be consistent with original OP code. I don't have Delphi at hand so do not blindly copy the code please.
EDIT:
After reading the question again I realized that his goal is only to send it in one go and it is not mandatory to use a string array. So there is a better way to do it.
StrListMem := TStringList.Create;
try
StrListMem.Add('alpha');
StrListMem.Add('bravo');
StrListMem.Add('charley');
Connection.IOHandler.WriteBufferOpen;
try
Connection.IOHandler.Write(StrListMem);
finally
Connection.IOHandler.WriteBufferClose;
end;
finally
StrListMem.Free;
end;
To read it
StrListMem := TStringList.Create;
try
IdTCPClient1.IOHandler.ReadStrings(StrListMem);
finally
StrListMem.Free;
end;
Easier and cleaner :)

Send Files with TCP from client to server knowing file size Delphi

Hello im trying to send a file Via TCP with delphi using Indy Components with a Client/Server
i manage to send and recieve the files correctly, the problem is before sending the file i would like to send its size aswell to compare it after i get it the server.
Now im sending the files from the Client To Server.
Client:
Ms := TMemoryStream.Create;
Ms.LoadFromFile('cliente.exe');
Ms.Position := 0;
Result := True;
Client.IOHandler.LargeStream := True;
try
Client.IOHandler.Write(ms, 0, True);// (Ms, 0, true);
except
Result := False;
end;
Ms.Free;
Server:
AStream := TFileStream.Create('C:\temp\file.exe', fmCreate + fmShareDenyNone);
try
AContext.Connection.IOHandler.LargeStream := True;
AContext.Connection.IOHandler.ReadStream(AStream, -1,false);
finally
FreeAndNil(AStream);
Memo1.Lines.Add('File received');
end;
So the question would be how could i send the file size with the file?
Your code is already sending the file size. You are setting the AWriteByteCount parameter of Write(TStream) to True, which tells it to send the stream size before sending the stream data. And you are telling ReadStream() to read the stream size before reading the stream data. So Indy is already validating the size for you before ReadStream() exits. You don't need to do it manually at all.

Delphi Indy equivalent to Python's Socket.recv?

I've this code snippet in Python:
s = socket.create_connection(('192.168.0.111', 123), timeout=2.0)
s.sendall('REQUEST,status,interface'); result = s.recv(1024)
How I can do the "s.recv(1024)" in Delphi using TIdTCPClient from Indy components? Server returns a string without any terminator, so ReadLn doesn't work.
In Python, recv(1024) simply reads whatever is available on the socket, up to 1024 bytes max. It is possible to do the same thing with TIdTCPClient (see below), but it is not the best way to handle socket programming in general. You really need to know how the server is actually terminating the data. Does it close its end of the connection after sending the data? If not, does it expect you to simply read whatever bytes are available regardless of the actual length? And if so, how does it expect you to handle TCP packet fragmentation (TCP/IP can separate transmitted data into multiple packets, and may receive data as multiple packets even if they were not sent as such)? Does it expect you to keep reading until some timeout occurs, indicating no more data is being sent?
It makes a big difference in how you write code to handle the reading properly. For example:
IdTCPClient1.Host := '192.168.0.111';
IdTCPClient1.Port := 123;
IdTCPClient1.ConnectTimeout := 2000;
IdTCPClient1.Connect;
try
IdTCPClient1.IOHandler.Write('REQUEST, status, interface');
// wait for a disconnect then return everything that was received
Result := IdTCPClient1.IOHandler.AllData;
finally
IdTCPClient1.Disconnect;
end;
Vs:
IdTCPClient1.Host := '192.168.0.111';
IdTCPClient1.Port := 123;
IdTCPClient1.ConnectTimeout := 2000;
IdTCPClient1.Connect;
try
IdTCPClient1.IOHandler.Write('REQUEST, status, interface');
// set the max number of bytes to read at one time
IdTCPClient1.IOHandler.RecvBufferSize := 1024;
// read whatever is currently in the socket's receive buffer, up to RecvBufferSize number of bytes
IdTCPClient1.IOHandler.CheckForDataOnSource;
// return whatever was actually read
Result := IdTCPClient1.IOHandler.InputBufferAsString;
finally
IdTCPClient1.Disconnect;
end;
Vs:
IdTCPClient1.Host := '192.168.0.111';
IdTCPClient1.Port := 123;
IdTCPClient1.ConnectTimeout := 2000;
IdTCPClient1.Connect;
try
IdTCPClient1.IOHandler.Write('REQUEST, status, interface');
// keep reading until 5s of idleness elapses
repeat until not IdTCPClient1.IOHandler.CheckForDataOnSource(5000);
// return whatever was actually read
Result := IdTCPClient1.IOHandler.InputBufferAsString;
finally
IdTCPClient1.Disconnect;
end;

Indy 10 problems with reading and writing streams

I am trying to exchange data between a IdTCPServer and an IdTCPClient using a collection that I convert to a stream before I send it over the network. Unfortunately, no matter how I try, I can't seem to pass the stream between the client and the server. The code always hangs on the IdTCPClient1.IOHandler.ReadStream(myStream, -1, True) line.
The relevant portion of my code is shown below:
Client side
with ClientDataModule do
begin
try
try
intStreamSize := StrToInt(IdTCPClient1.IOHandler.ReadLn); // Read stream size
IdTCPClient1.IOHandler.ReadStream(myStream, -1, True); // Read record stream
finally
ReadCollectionFromStream(TCustomer, myStream);
end;
except
ShowMessage('Unable to read the record from stream');
end;
end;
Server side
try
try
SaveCollectionToStream(ACustomer, MStream);
finally
MStream.Seek(0, soFromBeginning);
IOHandler.WriteLn(IntToStr(MStream.Size)); // Send stream size
IOHandler.Write(MStream, 0); // Send record stream
end;
except
ShowMessage('Unable to save the record to stream');
end;
I would greatly appreciate your assistance with solving this problem.
Thanks,
JDaniel
You are setting the AReadUntilDisconnect parameter of ReadStream() to True, which tells it to keep reading until the connection is closed. You need to set the parameter to False instead. You also need to pass in the stream size in the AByteCount parameter, since you are sending the stream size separately so you have to tell ReadStream() how much to actually read.
Try this:
Client:
with ClientDataModule do
begin
try
intStreamSize := StrToInt(IdTCPClient1.IOHandler.ReadLn);
IdTCPClient1.IOHandler.ReadStream(myStream, intStreamSize, False);
myStream.Position := 0;
ReadCollectionFromStream(TCustomer, myStream);
except
ShowMessage('Unable to read the record from stream');
end;
end;
Server:
try
SaveCollectionToStream(ACustomer, MStream);
MStream.Position := 0;
IOHandler.WriteLn(IntToStr(MStream.Size));
IOHandler.Write(MStream);
except
ShowMessage('Unable to save the record to stream');
end;
If you can change your protocol, then you can let Write() and ReadStream() exchange the stream size internally for you, like this:
Client:
with ClientDataModule do
begin
try
// set to True to receive a 64bit stream size
// set to False to receive a 32bit stream stream
IdTCPClient1.IOHandler.LargeStream := ...;
IdTCPClient1.IOHandler.ReadStream(myStream, -1, True);
myStream.Position := 0;
ReadCollectionFromStream(TCustomer, myStream);
except
ShowMessage('Unable to read the record from stream');
end;
end;
Server:
try
SaveCollectionToStream(ACustomer, MStream);
MStream.Position := 0;
// set to True to send a 64bit stream size
// set to False to send a 32bit stream stream
IOHandler.LargeStream := ...;
IOHandler.Write(MStream, 0, True);
except
ShowMessage('Unable to save the record to stream');
end;

Resources