I suppose that after the HTTPRio component receives the data, it parses the data. I'm saying this because after the program leaves the AfterExecute procedure it takes ages before continuing to the next line of code.
Want I want is to clear the data that has arrived on the AfterExecute procedure. Is this possible?
procedure TEventHandlers.thhoptAfterExecute(const MethodName: string;
SOAPResponse: TStream);
var
fs : TFileStream;
begin
fs := TFileStream.Create('F:\Lixosam\LixoSMS'+
IntToStr(ThCampanha),
fmCreate, fmShareDenyNone);
SOAPResponse.Position := 0;
fs.CopyFrom(SOAPResponse, SOAPResponse.Size);
fs.Free;
end;
How can I clear the data so the component doesn't have to do any parsing?
You can write whatever you want into the SOAPResponse. You can clear it, edit it, whatever. If a completely empty response causes trouble with deserialization (complaints about "document must have top-level element, at line 0"), you could stick in a "skeleton" response that provides minimal structure. If you edit the response, you should set the size accordingly.
Here is some code that I have:
var
sl : TStringList;
begin
sl := TStringList.Create;
try
SOAPResponse.Position := 0;
sl.LoadFromStream(SOAPResponse); // Load the response into a stringlist so we can work on it.
// some manipulation to the lines in sl occur here, such as stringreplaces and such.
// Now write out edits back out to the stream.
SOAPResponse.Position := 0; // Now overwrite the crappy response with our good one.
SOAPResponse.size := length(sl.Text); // Important - set new length before saving. Otherwise, the old
sl.SaveToStream(SOAPResponse); // leftover crud is still there, at the end, and the XML will blow up on it.
finally
FreeAndNil(sl);
end;
end;
Related
This code work's fine when I send data across the LAN with an Indy client component, but when I receive data from an external application from the web, it's causing it to fail. Could there be something on the client-side that is causing IdTCPServer to disconnect before all the data is read? An average of 33,000 characters are being sent by the client. Any suggestions?
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
strm: TMemoryStream;
RxBuf: TIdBytes;
begin
Memo1.Clear;
strm := TMemoryStream.Create;
try
// read until disconnected
AContext.Connection.IOHandler.ReadStream(strm, -1, true);
strm.Position := 0;
ReadTIdBytesFromStream(strm, RxBuf, strm.Size);
finally
strm.Free;
end;
Memo1.Lines.Add(BytesToString(RxBuf));
AContext.Connection.IOHandler.WriteLn('000');
end;
I also tryed this other code, in this case unlike the first code it only reads part of the data beeing sent. Is there a way to make the IdTCPServer Handler wait until all the data is collected?
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
RxBuf: TIdBytes;
begin
RxBuf := nil;
with AContext.Connection.IOHandler do
begin
CheckForDataOnSource(10);
if not InputBufferIsEmpty then
begin
InputBuffer.ExtractToBytes(RxBuf);
end;
end;
AContext.Connection.IOHandler.WriteLn('000');
Memo1.Lines.Add( BytesToString(RxBuf) );
end;
This code you posted as an answer is all wrong.
First off, you can't use BytesToString() on arbitrary byte blocks, that won't handle multi-byte encodings like UTF-8 correctly.
Also, you are not looking for the EOT terminator correctly. There is no guarantee that it will be the last byte of RxBuf after each read, if the client sends multiple XML messages. And even if it were, using Copy(BytesToString(), ...) to extract it into a string will never result in a blank string, like your code is expecting.
If the client sends an EOT terminator at the end of the XML, there is no need for a manual reading loop. Simply call TIdIOHandler.ReadLn() with the EOT terminator, and let it handle the read looping internally until the EOT is reached.
Also, the CoInitialize() and CoUninitialize() calls should be done in the OnConnect and OnDisconnect events, respectively (actually, they would be better called in a TIdThreadWithTask descendant assigned to the TIdSchedulerOfThread.ThreadClass property, but that is a more advanced topic for another time).
Try something more like this:
procedure TFrmMain.IdTCPServer1Connect(AContext: TIdContext);
begin
CoInitialize(nil);
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
procedure TFrmMain.IdTCPServer1Disconnect(AContext: TIdContext);
begin
CoUninitialize();
end;
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
XML: string;
begin
cdsSurescripts.Close;
XML := AContext.Connection.IOHandler.ReadLn(#4);
Display('CLIENT', XML);
AContext.Connection.IOHandler.WriteLn('000');
end;
Personally, I would take a different approach. I would suggest using an XML parser that supports a push model. Then you can read arbitrary blocks of bytes from the connection and push them into the parser, letting it fire events to you for completed XML elements, until the terminator is reached. This way, you don't have to waste time and memory buffering the entire XML in memory before you can then process it.
For further reference to anyone, I had to create a loop and wait for an EOT chr(4) send by the client in order to collect all the data on the IdTCPServer1Execute. This happens because the data is fragmented by Indy, The code looks something like this:
procedure TFrmMain.IdTCPServer1Execute(AContext: TIdContext);
var
Len: Integer;
Loop: Boolean;
begin
CoInitialize(nil);
cdsSurescripts.Close;
Loop := True;
while Loop = true do
begin
if AContext.Connection.IOHandler.Readable then
begin
AContext.Connection.IOHandler.ReadBytes( RxBuf,-1, True);
Len := Length(BytesToString(RxBuf));
if Copy(BytesToString(RxBuf), Len, 1) = '' then
begin
loop := False;
end;
end;
end;
Display('CLIENT', BytesToString(RxBuf));
AContext.Connection.IOHandler.WriteLn('000');
CoUninitialize();
end;
I'm modifying a program that is written in Delphi 6.0
I have a table in Oracle with a BLOB column named FILE_CONTENT.
I have already managed to upload an XML File that is about 100 KB. I have verified that the file content was correctly uploaded using SQL Developer.
The problem I have is when I try to download back the file content from DB to a file. This is an example code I'm using to donwload it:
procedure TfrmDownload.Save();
var
fileStream: TFileStream;
bField: TBlobField;
begin
dmDigital.qrGetData.Open;
dmDigital.RequestLive := True;
bField := TBlobField(dmDigital.qrGetData.FieldByName('FILE_CONTENT'));
fileStream := TFileStream.Create('FILE.XML', fmCreate);
bField.SaveToStream(fileStream);
FlushFileBuffers(fileStream.Handle);
fileStream.Free;
dmDigital.qrGetData.Close;
end;
The previous code already downloads the file content to FILE.XML. I'm using RequestLive:=True to be able to download a large BLOB (otherwise the file content is truncated to 32K max)
The resulting file is the same size as the original file. However, when I compare the downloaded file with the original one there are some differences (for example the last character is missing and other characters are also changed), therefore it seems to be a problem while downloading the content.
Do you know what cuould be wrong?
The problem seems to be related to Delphi code because I already tried with C# and the file content is downloaded correctly.
Don't use TBlobField.SaveToStream() directly, use TDataSet.CreateBlobStream() instead (which is what TBlobField.SaveToStream() uses internally anyway):
procedure TfrmDownload.Save;
var
fileStream: TFileStream;
bField: TField;
bStream: TStream;
begin
dmDigital.qrGetData.Open;
try
dmDigital.RequestLive := True;
bField := dmDigital.qrGetData.FieldByName('FILE_CONTENT');
bStream := bField.DataSet.CreateBlobStream(bField, bmRead);
try
fileStream := TFileStream.Create('FILE.XML', fmCreate);
try
fileStream.CopyFrom(bStream, 0);
FlushFileBuffers(fileStream.Handle);
finally
fileStream.Free;
end;
finally
bStream.Free;
end;
finally
dmDigital.qrGetData.Close;
end;
end;
TDataSet.CreateBlobStream() allows the DataSet to decide the best way to access the BLOB data. If the returned TStream is not delivering the data correctly, then either the TStream class implementation that CreateBlobStream() uses is broken, or the underlying DB driver is buggy. Try taking CopyFrom() out of the equation so you can verify the data as it is being retrieved:
procedure TfrmDownload.Save;
const
MaxBufSize = $F000;
var
Buffer: array of Byte;
N: Integer;
fileStream: TFileStream;
bField: TField;
bStream: TStream;
begin
dmDigital.qrGetData.Open;
try
dmDigital.RequestLive := True;
bField := dmDigital.qrGetData.FieldByName('FILE_CONTENT');
bStream := bField.DataSet.CreateBlobStream(bField, bmRead);
try
fileStream := TFileStream.Create('FILE.XML', fmCreate);
try
//fileStream.CopyFrom(bStream, 0);
SetLength(Buffer, MaxBufSize);
repeat
N := bStream.Read(PByte(Buffer)^, MaxBufSize);
if N < 1 then Break;
// verify data here...
fileStream.WriteBuffer(PByte(Buffer)^, N);
until False;
FlushFileBuffers(fileStream.Handle);
finally
fileStream.Free;
end;
finally
bStream.Free;
end;
finally
dmDigital.qrGetData.Close;
end;
end;
I want to put data from Memo1 directly to my FTP server, I've got code:
procedure TForm5.SendClick(Sender: TObject);
var K: TStream;
begin
K := TStream.Create;
Memo1.Lines.SaveToStream(K);
FTP.Host := 'localhost';
FTP.Username := 'login';
FTP.Password := 'haslo';
FTP.Connect;
if FTP.Connected then FTP.Put(K,'');
end;
But when I click "Send" button I've got two errors:
when Memo is empty
when I try send data
TStream is an abstract class. You must never instantiate it. Use a concrete class instead like, for instance, TMemoryStream.
You'll also want to destroy the stream when you are finished with it, or it will leak. Do yourself a favour and set ReportMemoryLeaksOnShutdown to True, for instance in your .dpr file. That will allow you to get a report of all the memory you are leaking when your program terminates.
Your code might run like this:
var
Stream: TMemoryStream;
....
Stream := TMemoryStream.Create;
try
// .... initialize the Indy object
if FTP.Connected then begin
// .... populate stream
Stream.Position := 0;
FTP.Put(Stream, '');
end;
finally
Stream.Free;
end;
I would like to use SaveToStream to save a ClientDataSet ALONG WITH OTHER MATERIAL. Here is a short sample:
filename := ChangeFileExt(Application.ExeName, '.dat');
FS := TFileStream.Create(filename, fmCreate);
CDS.SaveToStream(FS);
ShowMessage('After save, position is ' + IntToStr(FS.Position));
{now write a longint}
L := 1234;
siz := SizeOf(L);
Write(L, siz);
FS.Free;
But when I try to load this back in using LoadFromStream, and I again display the position after the ClientDataSet has been loaded, I see that the position is now 4 bytes AFTER the clientdataset was originally saved. It seems that CDS.LoadFromStream just plows ahead and consumes whatever follows it. As a result, when I then try to read the longint, I get an end of file error.
It is not sufficient to just use the CDS.SaveToStream at the end of creating a file, because what I'd really like to do is to save TWO clientdatasets to the file, one after the other, plus other material.
Ideas? Thanks.
[NB, this solution is essentially doubling up the work that (TLama's suggestion) "ReadDataPacket/WriteDataPacket" already does internally. I would use TLama's approach i.e. sub-class TClientDataSet to expose the above protected methods, and use the WriteSize parameter.]
Save the datasets to a temporary stream and then copy that to your destination stream with size information:
procedure InternalSaveToStream(AStream: TStream);
var
ATempStream: TMemoryStream;
ASize: Int64;
begin
ATempStream := TMemoryStream.Create;
// Save first dataset:
DataSet1.SaveToStream(ATempStream, dfBinary);
ASize := ATempStream.Size;
AStream.WriteData(ASize);
ATempStream.Position := 0;
AStream.CopyFrom(ATempStream, ALength);
ATempStream.Clear;
// Save second dataset:
DataSet2.SaveToStream(ATempStream, dfBinary);
ASize := ATempStream.Size;
AStream.WriteData(ASize);
ATempStream.Position := 0;
AStream.CopyFrom(ATempStream, ALength);
ATempStream.Clear;
FreeAndNil(ATempStream);
end;
To read back, first read the size and then copy that section of your source to a temporary stream again and load your dataset from that:
procedure InternalLoadFromStream(AStream: TStream);
var
ATempStream: TMemoryStream;
ASize: Int64;
begin
ATempStream := TMemoryStream.Create;
// Load first datset:
AStream.Read(ASize,SizeOf(ASize));
ASize := ATempStream.Size;
ATempStream.CopyFrom(AStream,ASize);
ATempStream.Position := 0;
DataSet1.LoadFromStream(ATempStream);
//...etc.
end;
So, I have a form with a few dozen controls and someone would like to save and later restore their contents and settings - which radio button was selected, what was the Position of that up/down, etc.
I would also like to store any entries added to a list box at run time.
What's the simplest way to do it? DfmToString and reverse? Write/read a .INI? Something else?
PRUZ's solution is a ready made solution; JVCL is open-source, and using JvFormStorage is simple. But you can also use Delphi's own streaming mechanism without using any third-party components. Here is an example:
procedure SaveComponentToFile(Component: TComponent; const FileName: TFileName);
var
FileStream : TFileStream;
MemStream : TMemoryStream;
begin
MemStream := nil;
if not Assigned(Component) then
raise Exception.Create('Component is not assigned');
FileStream := TFileStream.Create(FileName,fmCreate);
try
MemStream := TMemoryStream.Create;
MemStream.WriteComponent(Component);
MemStream.Position := 0;
ObjectBinaryToText(MemStream, FileStream);
finally
MemStream.Free;
FileStream.Free;
end;
end;
SaveComponentToFile takes a component instance, plus a file name, and streams the component into the file, in a human-readable text.
To load the component from file, you can use a code like this:
procedure LoadComponentFromFile(Component: TComponent; const FileName: TFileName);
var
FileStream : TFileStream;
MemStream : TMemoryStream;
i: Integer;
begin
MemStream := nil;
if not Assigned(Component) then
raise Exception.Create('Component is not assigned');
if FileExists(FileName) then
begin
FileStream := TFileStream.Create(FileName,fmOpenRead);
try
for i := Component.ComponentCount - 1 downto 0 do
begin
if Component.Components[i] is TControl then
TControl(Component.Components[i]).Parent := nil;
Component.Components[i].Free;
end;
MemStream := TMemoryStream.Create;
ObjectTextToBinary(FileStream, MemStream);
MemStream.Position := 0;
MemStream.ReadComponent(Component);
Application.InsertComponent(Component);
finally
MemStream.Free;
FileStream.Free;
end;
end;
end;
LoadComponentFromFile takes a component instance, and a file name, then loads file content into the component instance. To avoid naming conflict, we are free all existing owned components of the instance, before loading file data into it.
Now you can use the above code for saving a form into a file:
SaveComponentToFile(FSecondForm,ExtractFilePath(Application.ExeName)+ 'formdata.txt');
FSecondForm is a form instance, and it will be saved into "formdata.txt" file inside the same folder as the EXE file.
And to load FSecondForm from "formdata.txt" file, we write this:
if not Assigned(FSecondForm) then
FSecondForm := TfrmSecond.Create(Application);
LoadComponentFromFile(FSecondForm,ExtractFilePath(Application.ExeName)+ 'formdata.txt');
FSecondForm.Show;
LoadComponentFromFile needs the instance to be created first, so we check if FSecondForm is assigned, if not, we create an instance of it (it is an instance of TfrmSecond class), and then load file data into it. And eventually, we show the loaded form.
It is pretty easy to read/write component or object properties, or forms position in INI file or registry. Everything you need exist in help. You just need to decide when you want to read them (on creating, before showing...) and store them (on close, ...). This depends on what you are saving/restoring.
If you are going to use ready made components and want to save form position, then make sure to check how do they treat multiple monitors. If you are doing it your own way, you should take care of that yourself. For example, you might have a laptop and a big 22" monitor, and position of a form was saved while your big monitor was used. Later, if you open this form on laptop it might be displayed of screen so you can not see the form if this case is not handled properly.