There is a certain remote server. I want to get an answer from him
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Clear;
IdTCPClient1.Host := '163.158.182.243';
IdTCPClient1.Port := 28900;
IdTCPClient1.Connect;
end;
procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
IdTCPClient1.IOHandler.Write('001');
IdTCPClient1.IOHandler.ReadStrings(Memo1.Lines, 25, IndyTextEncoding(IdTextEncodingType.encOSDefault));
end;
The procedure requires a parameter to specify AReadLinesCount, otherwise the program stops responding
procedure TIdIOHandler.ReadStrings(ADest: TStrings; AReadLinesCount: Integer = -1;
AByteEncoding: IIdTextEncoding = nil
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
);
How to AReadLinesCount from the responses received
The server needs to tell your client when to stop reading. There are two ways it can do that:
It can send the number of lines before sending the lines themselves. You would read the number first, and then read the specified number of lines that follow.
It can send a unique terminating delimiter after sending the lines. You would read lines in a loop until you reach the terminator.
You have not provided any details about the protocol you are trying to implement, so noone can tell you exactly what to write in your code to make this work.
Related
I am converting a Delphi app from using a TTreeView to using a TVirtualStringTree; The node data is held in TItemData records in a TList.
type
TItemData = record
idName: string;
end;
PItemData = ^TItemData
TMyForm = class(TForm)
...
private
itemData: TList<TItemData>;
..
end;
I wanted to get the displayed tree up and running in the most straightforward way possible, and then gradually convert the app little by little as I got to understand how to use the VirtualStringTree. So, I have a buildTreeFromItemData() method, which iterates through the TList elements and creates child nodes in the VirtualStringTree. I [tried to] pass a pointer to each TItemData record in each call to VST.addChild() which would then be dereferenced in vstGetText(), something like this:
procedure buildTreeFromItemData;
var
i: integer;
idA: TItemData;
begin
for i := 0 to itemData.count - 1 do begin
idA := itemData[i];
vst.addChild(NIL, #idA);
end;
end;
Dereference the pointer:
procedure TMyForm.vstGetData(...);
var
idB: TItemData;
begin
idB := node.getData^;
CellText := idB.idName;
end;
It quickly became apparent that no matter how many different ways I tried to code this, e.g. #itemData[i], the only times my code compiled every vst node was actually getting the address of the idA local variable, and every tree node was pointing to the most recent TItemData record pointed to by idA. I was then getting access violations in vstGetText() once buildTreeFromItemData() had completed and the local idA variable went out of scope, i.e. every node's data pointer in vst was now invalid.
Most of my attempts to somehow deference idA and get at the address location of the TItemData stored in idA generated an "invalid typecast" from the Delphi syntax checker, let alone the compiler.
At one point I tried something like this:
ptr1^ := #idA;
I have no idea what that actually means to the Delphi compiler. I know what I wanted it to mean: I wanted it to mean "set ptr1 to the [dereferened] address stored at the address of the idA local variable". To my surprise, it compiled but went bang as soon as the debugger hit that statement. (What does the compiler think "ptr1^ := " means?)
Eventually I hit upon the idea of typecasting idA to a TObject; at least then, my thinking went, the compiler would know we were at least in the realms of dereferencing and might actually let me, eventually, get to the pointer I really needed to pass to vst.addChild().
After much experimentation, and many more "invalid typecast"s, unbelievably [at least to me] the following code works!.....
procedure buildTreeFromItemData;
var
i: integer;
idA: TItemData;
myObj: TObject;
ptr1: pointer;
ptr2: PItemData;
begin
for i := 0 to itemData.count - 1 do begin
idA := itemData[i];
myObj := TObject(#idA);
ptr1 := pointer(myObj)
ptr2 := PItemData(ptr1^);
vst.addChild(NIL, ptr2);
end;
end;
ptr2 is now so far removed, syntactically and semantically, from idA, that the compiler finally allows the dereference in PItemData(ptr1^), although it only allowed it after I added the PItemData(...) typecast.
I don't even have to dereference this pointer in vstGetText!...
procedure TMyForm.vstGetText(...);
var
idB: PItemData;
begin
idB := PItemData(node.getData);
CellText := idB.idName;
end;
The tree displays perfectly and the access violations are gone. (NB. The actual code in buildTreeFromItemData() is a lot more involved and creates child nodes of child nodes to create a complex tree structure several levels deep.)
Although I eventually found a solution at gone 1am this morning after a lot of trial and error, I find it difficult to believe that my jiggerypokery with the local variable is really necessary for something so simple. So my question is this: what is the correct Delphi syntax for getting the address of my TItemData record stored in a plain "idA: TItemData;" local variable?
(I think this is my first ever question to stackoverflow; I hope I have formulated it well enough. I've kept the code to the absolute bare bones necessary to illustrate the issue and I wasn't able to completely reproduce the exact experimentation code I went through. The solution in the final two code blocks, though, is my working code. If I can improve how I've formulated the question and the explanation to meet stackoverflow's stringent standards, please let me know.)
what is the correct Delphi syntax for getting the address of my TItemData record stored in a plain "idA: TItemData;" local variable?
Well... That one is simple. You do it like this : #idA.
The issue here is that it is NOT what you want to do. You want to have the address of the TItemData in your list (idA is merely a copy of the record in itemData).
To get the address of a value inside of a TList<T>, you can't use property Items[Index: Integer]: T of TList<T> as it will only return you a copy of the value. You need to use property List: arrayofT which will give you direct access the underlying array in which the values are stored, and then you can get the address with : #itemData.List[I].
That being said, it is not something I would recommend. There is no guarantees how long that pointer will remain valid. There is a lot of operations on the list that might render that address invalid, or make it point to the wrong TItemData. If itemData is immutable by the time you acquire the pointer, it's should be ok. Otherwise, it would be way better to allocate new TItemData like described by IVO GELOV in his answer.
Answer to comment :
is there no legit Delphi syntax to achieve what I managed to do?
You did not manage to do what you think you did.
idA := itemData[i]; //Copy the value from the list
myObj := TObject(#idA); //Take the address of iDA and pretend it's a TObject
ptr1 := pointer(myObj) //Now, pretend my TObject is a Pointer
ptr2 := PItemData(ptr1^); //Now, pretend ptr1 is pointing to a PItemData.
//I didn't test it, but I'm pretty sure you could have just written `ptr2 := PItemData((#iDA)^)`
vst.addChild(NIL, ptr2);
So what does this code do? The "main event" here is ptr2 := PItemData(ptr1^). It takes the address of idA, take the first PointerSize(For simplicity, I'll assume 32 bits going forward) byte at that address and pretend it's a PItemData. What are the first 32 bit of TItemData? A string, more precisely idName. Maybe it will help you understand to consider that #idA = #idA.IdName.
So, in other words, vst.addChild(NIL, ptr2); is the same as vst.addChild(NIL, PChar(idA.idName)); or vst.addChild(NIL, #idA.idName[1]); (except for empty string... but I digress). So, what you add in the vst is not the address of the record in itemData, it's the address of the first character of the string idA.idName.
But, your confusion is probably coming from believing records are reference types. They are not, they are value types.
The idea of TVirtualTree is to store your data inside the nodes. TVirtualTree will reserve memory for your data and that's why you need to tell the tree how big is your data.
So, when your form is created (inside OnCreate handler) you should set the NodeDataSize property of your VirtualTree:
procedure TmyForm.TntFormCreate(Sender: TObject);
begin
inherited;
myVirtualTree.NodeDataSize:=SizeOf(TItemData);
end;
You should also handle the OnFreeNode event from your VirtualTree:
procedure TmyForm.myVirtualTreeFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
Data:PItemData;
begin
Data:=Sender.GetNodeData(Node);
if Assigned(Data) then Finalize(Data^);
end;
When you want to create a new node in the tree:
procedure TmyForm.CreateNewNode(data:TItemData);
var
node: PItemData;
P: PVirtualNode;
begin
with myVirtualTree do
begin
P:=AddChild(Nil);
node:=GetNodeData(P);
node^:=data;
// ReinitNode is very important - to update InternalNode precomputed
// text width. Otherwise node can not be properly selected if
// FullRowSelect is FALSE
ReinitNode(P,False);
end;
end;
You will also need to handle other events from TVirtualTree like OnGetText, onPaintText, onCompareNodes, onIncrementalSearch, onDblClick, onGetHint, etc.
I have an SQL script that is called from within a shell script and takes a long time to run. It currently contains dbms_output.put_line statements at various points. The output from these print statements appear in the log files, but only once the script has completed.
Is there any way to ensure that the output appears in the log file as the script is running?
Not really. The way DBMS_OUTPUT works is this: Your PL/SQL block executes on the database server with no interaction with the client. So when you call PUT_LINE, it is just putting that text into a buffer in memory on the server. When your PL/SQL block completes, control is returned to the client (I'm assuming SQLPlus in this case); at that point the client gets the text out of the buffer by calling GET_LINE, and displays it.
So the only way you can make the output appear in the log file more frequently is to break up a large PL/SQL block into multiple smaller blocks, so control is returned to the client more often. This may not be practical depending on what your code is doing.
Other alternatives are to use UTL_FILE to write to a text file, which can be flushed whenever you like, or use an autonomous-transaction procedure to insert debug statements into a database table and commit after each one.
If it is possible to you, you should replace the calls to dbms_output.put_line by your own function.
Here is the code for this function WRITE_LOG -- if you want to have the ability to choose between 2 logging solutions:
write logs to a table in an autonomous transaction
CREATE OR REPLACE PROCEDURE to_dbg_table(p_log varchar2)
-- table mode:
-- requires
-- CREATE TABLE dbg (u varchar2(200) --- username
-- , d timestamp --- date
-- , l varchar2(4000) --- log
-- );
AS
pragma autonomous_transaction;
BEGIN
insert into dbg(u, d, l) values (user, sysdate, p_log);
commit;
END to_dbg_table;
/
or write directly to the DB server that hosts your database
This uses the Oracle directory TMP_DIR
CREATE OR REPLACE PROCEDURE to_dbg_file(p_fname varchar2, p_log varchar2)
-- file mode:
-- requires
--- CREATE OR REPLACE DIRECTORY TMP_DIR as '/directory/where/oracle/can/write/on/DB_server/';
AS
l_file utl_file.file_type;
BEGIN
l_file := utl_file.fopen('TMP_DIR', p_fname, 'A');
utl_file.put_line(l_file, p_log);
utl_file.fflush(l_file);
utl_file.fclose(l_file);
END to_dbg_file;
/
WRITE_LOG
Then the WRITE_LOG procedure which can switch between the 2 uses, or be deactivated to avoid performances loss (g_DEBUG:=FALSE).
CREATE OR REPLACE PROCEDURE write_log(p_log varchar2) AS
-- g_DEBUG can be set as a package variable defaulted to FALSE
-- then change it when debugging is required
g_DEBUG boolean := true;
-- the log file name can be set with several methods...
g_logfname varchar2(32767) := 'my_output.log';
-- choose between 2 logging solutions:
-- file mode:
g_TYPE varchar2(7):= 'file';
-- table mode:
--g_TYPE varchar2(7):= 'table';
-----------------------------------------------------------------
BEGIN
if g_DEBUG then
if g_TYPE='file' then
to_dbg_file(g_logfname, p_log);
elsif g_TYPE='table' then
to_dbg_table(p_log);
end if;
end if;
END write_log;
/
And here is how to test the above:
1) Launch this (file mode) from your SQLPLUS:
BEGIN
write_log('this is a test');
for i in 1..100 loop
DBMS_LOCK.sleep(1);
write_log('iter=' || i);
end loop;
write_log('test complete');
END;
/
2) on the database server, open a shell and
tail -f -n500 /directory/where/oracle/can/write/on/DB_server/my_output.log
Two alternatives:
You can insert your logging details in a logging table by using an autonomous transaction. You can query this logging table in another SQLPLUS/Toad/sql developer etc... session. You have to use an autonomous transaction to make it possible to commit your logging without interfering the transaction handling in your main sql script.
Another alternative is to use a pipelined function that returns your logging information. See here for an example: http://berxblog.blogspot.com/2009/01/pipelined-function-vs-dbmsoutput.html When you use a pipelined function you don't have to use another SQLPLUS/Toad/sql developer etc... session.
the buffer of DBMS_OUTPUT is read when the procedure DBMS_OUTPUT.get_line is called. If your client application is SQL*Plus, it means it will only get flushed once the procedure finishes.
You can apply the method described in this SO to write the DBMS_OUTPUT buffer to a file.
Set session metadata MODULE and/or ACTION using dbms_application_info().
Monitor with OEM, for example:
Module: ArchiveData
Action: xxx of xxxx
If you have access to system shell from PL/SQL environment you can call netcat:
BEGIN RUN_SHELL('echo "'||p_msg||'" | nc '||p_host||' '||p_port||' -w 5'); END;
p_msg - is a log message
v_host is a host running python script that reads data from socket on port v_port.
I used this design when I wrote aplogr for real-time shell and pl/sql logs monitoring.
Using SQL*Assistant:
REPLACE PROCEDURE test_proc()
BEGIN
DECLARE l_msg varchar(128);
set l_msg = 'test';
-- PRINT is not supported
--print l_msg;
-- debug is recognized as a special token, but doesn't work
--debug l_msg;
-- this does nothing
--SIGNAL SQLSTATE '02000';
END;
Is there a simple way to output a text during a procedure execution, aside writing to a log table?
TD 14.xx
EDIT:
Not trying to handle exceptions, but rather send text messages to the client, as the procedure progresses, regardless of the state/condition, similar to PRINT (Sybase), DMBS_OUTPUT(Oracle), DEBUG(SQL Server).
This is a multiuser application (multithreaded) where various departments will access their own database.The database is SQLite and I am using FireDac.For each department I have assigned a separate ADConnection so I dont get any unexpected locks.
Which connection will be activated (active) depends solely on the number produced by the ADQuery3. This is done on MainForm Show because it needs to be this way (which gets shown after successfull login). I would like to be able to close every connection on FormClose but I run into some bad issues when multiusers use the same database and log in and out.So I would like to ask if this is the right programming logic I am doing or this could be done in a better way?
Also I have never used this many begin end else and I am wondering how to proceed with this?
I mean when I need to check the if the number of another department came up, like
if DataModule1.ADQuery3.FieldByName('DEPARTMENT').AsString = '12' where does the next ELSE come up?
procedure TMainForm.FormShow(Sender: TObject);
begin
if DataModule1.ADQuery3.FieldByName('DEPARTMENT').AsString = '13'
then begin
try
if DataModule1.1_CONNECTION.Connected = true then
DataModule1.1_CONNECTION.Connected := False
else
DataModule1.1_CONNECTION.DriverName:= 'SQLite';
DataModule1.1_CONNECTION.Params.Values['Database']:= ExtractFilePath(Application.ExeName)+ 'mydatabase.db';
DataModule1.1_CONNECTION.Connected := true;
DataModule1.ADTable1.TableName :='DEPT_13';
DataModule1.DEPT_13.Active:=True;
cxGrid1.ActiveLevel.GridView := DEPT_13;
except
on E: Exception do begin
ShowMessage('There was an error... : ' + E.Message);
end;
end;
end;
I use a web site that requires a logon and at one point shows a jpg using the http
<img src="https://www.theurl.com/pictures/Photo.ashx?theid=221">
I want to use Delphi to download this image (and others by using a different parameter) and store it in a SQLite database.
I am able to take a jpg file from my HDD and store it in the database with..
procedure TForm1.Button1Click(Sender: TObject); //this works
begin
DISQLite3Database1.DatabaseName := 'C:\testphoto.db';
DISQLite3Database1.Open;
try
Query1.Close;
Query1.selectSQL := ('insert into StudentPhotos(id,photo)
values(''sally'', :photo)');
Query1.Params.ParamByName('photo').LoadFromFile
('C:\Users\Admin\Documents\testpic2.jpg',ftGraphic);
Query1.Open ;
finally
DISQLite3Database1.close;
end;
end;
I am also able to download the image from the website into a file using the following (after running code that handles the logon)
procedure TForm1.Button2Click(Sender: TObject);
var
Strm: TMemoryStream;
HTTP: TIdHTTP;
LHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
try
http:= TIdHTTP.create; //make an http component
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
Strm := TMemoryStream.Create;
HTTP.IOHandler:=LHandler;
HTTP.HandleRedirects := true;
HTTP.Request.UserAgent := 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'; try
Http.Get('https://www.TheUrl.com/picures/Photo.ashx?theid=221' , Strm);
Strm.Position := 0;
Strm.SaveToFile('C:\Users\Admin\Documents\testpic2.jpg');
except
on e:Exception do
begin
ShowMessage(E.ClassName+' error raised, with message : '+E.Message);
showmessage('could not download file');
end;
end;
finally
http.Free;
LHandler.Free ;
Strm.free;
end;
end;
However I'd prefer not to save each file on the clients hard drive and then read it back in to save it into the database as that will be quite slow.
Question
What is the correct syntax to combine both of the above procedures so that I can download into a stream and then pass the stream directly into the query parameter ready to save it in the database?
Note I am using DISQLIte3 but the query methods/properties are similar to other components.
Most classes that have a LoadFromFile also have a LoadFromStream. Have you tried that?
ie.
Query1.Params.ParamByName('photo').LoadFromStream(Strm,ftGraphic);
# Joe Meyer - Yes I saw that link, it doesn't have anything to do with databases. Like most things I have seen over the last two days it only deals with blobs going to and from images
#bummi & HeartWare
I tried loads of different combinations of ... ParamByName('photo').LoadFromStream()... using Tfilestream and TmemoryStream but kept getting incompatible type errors, maybe because I didn't know what TBlobType to use when dealing with jpgs as opposed to bitmaps.
I copied exactly what you proposed into my first procedure to get
begin
DISQLite3Database1.DatabaseName := 'C:\Users\Admin\Documents\RAD Studio\Projects\sqlite with photos\testphoto.db';
DISQLite3Database1.Open;
try
strm := TmemoryStream.Create;
strm.LoadFromFile('C:\Users\Admin\Documents\RAD Studio\Projects\sqlite with photos\testpic2.jpg');
Query1.Close;
Query1.selectSQL := ('insert into StudentPhotos(id,photo) values(''sally'', :photo)');
Query1.Params.ParamByName('photo').LoadFromStream(Strm,ftGraphic);
Query1.Open ;
finally
strm.Free ;
DISQLite3Database1.close;
end;
end;
... and it worked first time!
I think sometimes when developing one can't see the wood for the trees.
I should be able to work out the rest now, Thanks to you both