Delphi - Indy Client/Server sending buffer - delphi

I'm trying to send buffer from client to the server...Buffer revived but i get error message 'data error' while converting the buffer into steam on the server side.
Also i tried to send that buffer as a Stream but i get error message on the server side Out of memory
Client:
procedure TAudio.Buffer(Sender: TObject; Data: Pointer; Size: Integer);
var
Stream: TMemoryStream;
Buff:string;
begin
Move(Data^, ACMC.BufferIn^, Size);
if AConn.Client.Connected then begin
Stream := TMemoryStream.Create;
Stream.WriteBuffer(ACMC.BufferOut^, ACMC.Convert);
Stream.Position := 0;
Buff := ZCompressStreamToString(Stream);
AConn.Client.IOHandler.WriteLn(Buff);
Stream.Free;
Writeln('sent');
end;
end;
Server Thread:
try
List := MainForm.idtcpsrvrMain.Contexts.LockList;
try
if List.IndexOf(Ctx) <> -1 then
begin
TMainContext(Ctx).Queue.Add(EncryptStr('AUDIO|2|'+BYTES));
Stream:
Buffer := TMainContext(Ctx).Connection.IOHandler.ReadLn;
mStream := TMemoryStream.Create;
try
ZDecompressStringToStream(Buffer,mStream);
mStream.Position := 0;
SetLength(Buffer,mStream.Size);
mStream.ReadBuffer(pointer(Buffer)^,mStream.Size);
SendMessage(hLstbox,LB_ADDSTRING,0,lparam(Buffer));
iList := SendMessage(hLstbox,LB_GETCOUNT,0,0);
SendMessage(hLstbox,LB_SETTOPINDEX,iList-1,0);
ACMO.Play(Pointer(Buffer)^,Length(Buffer));
finally
mStream.Free;
end;
if NodesList.Items[index].TerminateAudioThreads then
begin
..
..
Terminate;
end
else goto Stream;
Note:
both ZCompressStreamToString & ZDecompressStringToStream functions are tested on the client side and its worked.

Related

Copy database to tethered client

I have created a tethered app. The server needs to copy a Sqlite db
and stream it to the client.
I get the db with this code:
procedure TfmxServer.actStreamTheDbExecute(Sender: TObject);
var
ms: TMemoryStream;
begin
ms := tmemorystream.Create;
ms := dmplanner.GetDbAsStream; // get it from the datamodule
ms.Position := 0;
thrprofServer.SendStream(thrmanServer.RemoteProfiles.First,
'Stream_TheDB', ms); // send it to the client
end;
function TdmPlanner.GetDbAsStream: TMemoryStream; // datamodule
var
fs: TFilestream;
ms: TMemoryStream;
begin
fs := tfilestream.Create(consqlite.Params.Values['Database'] , fmOpenRead);
ms := tmemorystream.Create;
try
ms.loadfromstream(fs); // ms.size = 315392, file size = (315,392 bytes
result := ms; // so I am getting the full db3 file.
result.Position := 0;
finally
freeandnil(fs);
freeandnil(ms); // does this kill the result?
end;
end;
I catch the stream and to write the db with this code:
procedure TfrmMobile_Client_Main.DoStreamTheDb(
const Aresource: TremoteResource);
var
fs: TFilestream;
ms: TMemoryStream;
begin
fs := tfilestream.Create
(dmplannerclient.consqlite.Params.Values['Database'] ,
fmopenreadwrite or fmCreate);
try
ms := TMemoryStream.Create;
ms := TMemoryStream(AResource.Value.AsStream);
ms.Position := 0; // ms.size = 315392, so I got the whole file.
ms.SaveToStream(fs);
dmPlannerClient.FillLbx(lbxRecipeNames);
// now fill a listbox, but when I open a query, I get
// [FireDAC][Phys][SQLite] ERROR: unable to open database file.
finally
freeandnil(fs);
freeandnil(ms);
end;
end;
So my question is, How do I copy the db to the client
and then use it on the client?
Better yet, How do I an in-memory db instead of an on-disk db?
I have tried setting the FDConnection filename to :memory: but that
did not work.
Delphi CE Rio 10.3.2
Thanks...Dan'l' +
I don't think there is a way to copy a Sqlite database in its entirety to a tethered
client short of copying the entire database file to the client, because it may contain
numerous tables and other resources like views, stored procs, etc.
However, copying the entire database as a file is actually quite
simple to do. In the client, you can open a table in it using a local FDConnection
and FDQuery.
Server code:
procedure TApp1Form.SendDBAsStream;
var
StreamToSend : TMemoryStream;
const
DBName = 'D:\Delphi\Code\Sqlite\DB1.Sqlite';
begin
StreamToSend := TMemoryStream.Create;
try
StreamToSend.LoadFromFile(DBName);
StreamToSend.Position := 0;
TetheringAppProfile1.Resources.FindByName('SqliteDB').Value := StreamToSend;
finally
// Don't free StreamToSend ?
end;
end;
Client code
procedure TApp2Form.TetheringAppProfile1Resources0ResourceReceived(const Sender:
TObject; const AResource: TRemoteResource);
var
ReceivedStream : TStream;
FileStream : TFileStream;
begin
FileName := ExtractFilePath(Application.ExeName) + 'Temp.Sqlite';
AResource.Value.AsStream.Position := 0;
FileStream := TFileStream.Create(FileName, fmCreate);
ReceivedStream := AResource.Value.AsStream;
try
ReceivedStream.Position := 0;
FileStream.CopyFrom(ReceivedStream, ReceivedStream.Size);
finally
FileStream.Free;
// ReceivedStream.Free; No! The tethering framework frees the stream
end;
OpenTable;
end;
procedure TApp2Form.OpenTable;
begin
if FDConnection1.Connected then
FDConnection1.Connected := False;
FDConnection1.Params.Clear;
FDConnection1.Params.Add('Database=' + FileName);
FDConnection1.DriverName := 'Sqlite';
try
FDConnection1.Connected := True;
FDQuery1.Open('select * from mytable');
except
ShowMessage(Exception(ExceptObject).Message + ' ' + FileName);
end;
end;
I tested the above in Delphi 10.2.3 on Win10 64-bit and it works fine for me.
If you wanted to copy only a few tables to the client, what I would do is
In the server, open one of the tables in an FDQuery, then assign its data to an
FDMemtable by FDMemTable1.Data := FDQuery1.Data
Call SaveToStream on FDMemTable1 and send the stream as a stream resource to the client
On the client, call FDMemTable.LoadFromStream to load the received stream. I think,
because I haven't tried it that the client would need to contain a TFDPhysSQLiteDriverLink
to support loading from the stream.

Transfer of Large Stream from server to client fails

All,
I am working on a new datasnap project based on the example project located in C:\Users\Public\Documents\Embarcadero\Studio\18.0\Samples\Object Pascal\DataSnap\FireDAC_DBX.
I am trying to transfer a large stream (1,606,408 bytes) from datasnap server to client. I am running into what appears to be a common issue and that is that the entire stream does not make it to the client.
Here is my server code:
//Returns Customer Info
function TServerMethods.GetBPInfo(CardCode : String): TStringStream;
begin
Result := TStringStream.Create;
try
qBPInfo.Close;
if CardCode.Trim = '' then
qBPInfo.ParamByName('ParmCardCode').AsString := '%%'
else
qBPInfo.ParamByName('ParmCardCode').AsString := '%' + CardCode + '%';
qBPInfo.Open;
FDSchemaAdapterBPInfo.SaveToStream(Result, TFDStorageFormat.sfBinary);
Result.Position := 0;
// Result.SaveToFile('output.adb');
except
raise;
end;
end;
Here is my client code:
procedure TdmDataSnap.GetBPInfo(CardCode : String);
var
LStringStream : TStringStream;
begin
dmDataSnap.FDStoredProcBPInfo.ParamByName('CardCode').AsString := CardCode;
FDStoredProcBPInfo.ExecProc;
LStringStream := TStringStream.Create(FDStoredProcBPInfo.ParamByName('ReturnValue').asBlob);
//LStringStream.Clear;
//LStringStream.LoadFromFile('Output.adb');
try
if LStringStream <> nil then
begin
LStringStream.Position := 0;
try
DataModuleFDClient.FDSchemaAdapterBP.LoadFromStream(LStringStream, TFDStorageFormat.sfBinary);
except
on E : Exception do
showmessage(e.Message);
end;
end;
finally
LStringStream.Free;
end;
end;
You will see the stream save and load code; that is how I determined that the server was getting the entire result set into the stream, and that the client could handle the entire result set and display it properly.
So smaller streams transfer just fine, but this big one, when examined in the ide debugger, does not start with the 65,66,68,83 characters and the load fails with the error, '[FireDAC][Stan]-710. Invalid binary storage format'.
I know from extended Googling that there are work-arounds for this, but I do not understand how to apply the workarounds to my case, with the use of Tfdstoredproc and TfdSchemaAdaptor components. I'm trying to stay with this coding scheme.
How do I adapt this code to correctly receive large streams?
Update 1:
Ok, I tried strings and Base64 encoding. It didn't work.
Client Code:
procedure TdmDataSnap.GetBPInfo(CardCode : String);
var
LStringStream : TStringStream;
TempStream : TStringStream;
begin
dmDataSnap.FDStoredProcBPInfo.ParamByName('CardCode').AsString := CardCode;
FDStoredProcBPInfo.ExecProc;
try
TempStream := TStringStream.Create;
TIdDecoderMIME.DecodeStream(FDStoredProcBPInfo.ParamByName('ReturnValue').asString,TempStream);
if TempStream <> nil then
begin
TempStream.Position := 0;
try
DataModuleFDClient.FDSchemaAdapterBP.LoadFromStream(TempStream, TFDStorageFormat.sfBinary);
except
on E : Exception do
showmessage(e.Message);
end;
end;
finally
TempStream.Free;
end;
end;
Here is my server code:
//Returns Customer Info
function TServerMethods.GetBPInfo(CardCode : String): String;
var
TempStream : TMemoryStream;
OutputStr : String;
begin
Result := '';
TempStream := TMemoryStream.Create;
try
try
qBPInfo.Close;
if CardCode.Trim = '' then
qBPInfo.ParamByName('ParmCardCode').AsString := '%%'
else
qBPInfo.ParamByName('ParmCardCode').AsString := '%' + CardCode + '%';
qBPInfo.Open;
FDSchemaAdapterBPInfo.SaveToStream(TempStream, TFDStorageFormat.sfBinary);
TempStream.Position := 0;
OutputStr := IdEncoderMIMEBPInfo.EncodeStream(TempStream);
Result := OutputStr
except
raise;
end;
finally
TempStream.Free;
end;
end;
The result is the same.

Delphi XE2 DataSnap - Download File via TStream With Progress Bar

I've written a DataSnap server method that returns a TStream object to transfer a file. The client application calls the method and reads the stream fine. My issue is that the method call takes a while to complete before the TStream object is available to read, but on the server side I can see that the method call only takes a second to create the object to return. I was hoping the stream object would be returned immediately so that I can read the stream and display a progress bar for the download progress. Is there another way I can do this?
The server method is very simple :
function TServerMethods.DespatchDocument(sCompanyID, sDocOurRef: string): TStream;
var
sSourceFilePath: string;
strFileStream: TFileStream;
begin
sSourceFilePath := GetDocumentPDFFilePath(sCompanyID, sDocOurRef);
strFileStream := TFileStream.Create(sSourceFilePath, fmOpenRead);
Result := strFileStream;
end;
This is how I did it a while back. I used XE and haven't had a chance to clean it up.
//Server side:
function TServerMethods1.DownloadFile(out Size: Int64): TStream;
begin
Result := TFileStream.Create('upload.fil', fmOpenRead or fmShareDenyNone);
Size := Result.Size;
Result.Position := 0;
end;
//Client side:
procedure TfMain.DownloadFile(Sender: TObject);
var
RetStream: TStream;
Buffer: PByte;
Mem: TMemoryStream;
BytesRead: Integer;
DocumentId: Int64;
Size: Int64;
filename: WideString;
BufSize: Integer;
begin
BufSize := 1024;
try
Mem := TMemoryStream.Create;
GetMem( Buffer, BufSize );
try
RetStream := FDownloadDS.DownloadFile(Size);
RetStream.Position := 0;
if ( Size <> 0 ) then
begin
filename := 'download.fil';
repeat
BytesRead := RetStream.Read( Pointer( Buffer )^, BufSize );
if ( BytesRead > 0 ) then
begin
Mem.WriteBuffer( Pointer( Buffer )^, BytesRead );
end;
lStatus.Caption := IntToStr( Mem.Size ) + '/' + IntToStr( Size );
Application.ProcessMessages;
until ( BytesRead < BufSize );
if ( Size <> Mem.Size ) then
begin
raise Exception.Create( 'Error downloading file...' );
end;
end
else
begin
lStatus.Caption := '';
end;
finally
FreeMem( Buffer, BufSize );
FreeAndNIl(Mem);
end;
except
on E: Exception do
begin
lErrorMessage.Caption := PChar( E.ClassName + ': ' + E.Message );
end;
end;
end;
You can adjust BufSize however you like. I was having trouble getting the size of the stream until I did it this way. I experimented with XE2 and didn't seem to have the same problem but I was uploading. There is probably a better way to retrieve the size of the stream. If I get the answer soon I'll let you know....
On another note - I haven't figured out how to display a progress bar on the server side. I'm still trying to figure this out too.
I hope this helps! Let me know if you have any questions!
Glad you have some luck! This is the other fix I had to do. You can refer to this link https://forums.embarcadero.com/thread.jspa?threadID=66490&tstart=0
After diving in the code I found in "Data.DBXJSONReflect.pas"
procedure TJSONPopulationCustomizer.PrePopulate(Data: TObject; rttiContext: TRttiContext);
...
3473: rttiField.GetValue(Data).AsObject.Free;
3474: rttiField.SetValue(Data, TValue.Empty);
...
I think it should be this way:
3473: rttiField.SetValue(Data, TValue.Empty);
3474: rttiField.GetValue(Data).AsObject.Free;

Problem sending more then 2 lines at time

I have problem with sockets.
If i send more then two lines of text using TClientSocket then server receives one line instead of two.
Client part:
ClientSocket1.Socket.SendText(Edit1.Text);//Text is 'Line1'
ClientSocket1.Socket.SendText(Edit2.Text);//Text is 'Line2'
Server part:
var
s: String;
begin
s := Socket.ReceiveText;
Memo1.Lines.Add(S);
The Memo1 created line is 'Line1Line2'
Why?
Sorry for my english!
SendText does not send a CRLF. If you need to send a new line, you'll have to do it explicitly:
ClientSocket1.Socket.SendText(Edit1.Text + #13#10);
ClientSocket1.Socket.SendText(Edit2.Text + #13#10);
TClientSocket and TServerSocket implement TCP/IP, which is a byte stream that has no concept of message boundaries (unlike UDP, which does). When you call SendText(), it just dumps the String contents as-is onto the socket. When you call ReceiveText(), it returns whatever is currently in the socket buffer at that moment. That is why you see the server receive 'Line1Line2'. If you want to differentiate between the two lines, then you need to send a delimiter between them, such as a CRLF sequence, and then your server code needs to be updated to look for that. Since TCP/IP is a byte stream, there is no guaranteed 1-to-1 relationship between writes and reads. Case in point, you wrote 5 bytes followed by 5 bytes, but the server received 10 bytes all at once. So your reading code needs to buffer everything it receives and then you can check your buffer for the data you are looking for, eg:
Client:
ClientSocket1.Socket.SendText(Edit1.Text + #13#10);
ClientSocket1.Socket.SendText(Edit2.Text + #13#10);
Server:
procedure TForm1.ServerSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
begin
Socket.Data := TMemoryStream.Create;
end;
procedure TForm1.ServerSocket1Disconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
TMemoryStream(Socket.Data).Free;
Socket.Data := nil;
end;
procedure TForm1.ServerSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
var
Strm: TMemoryStream;
RecvLen: Integer;
StrmSize, I: Int64;
Ptr: PByte;
B: Byte;
s: AnsiString;
begin
Strm := TMemoryStream(Socket.Data);
RecvLen := Socket.ReceiveLength;
if RecvLen <= 0 then Exit;
StrmSize := Strm.Size;
Strm.Size := StrmSize + RecvLen;
Ptr := PByte(Strm.Memory);
Inc(Ptr, Strm.Position);
RecvLen := Socket.ReceiveBuf(Ptr^, RecvLen);
if RecvLen <= 0 then
begin
Strm.Size := StrmSize;
Exit;
end;
Strm.Size := StrmSize + RecvLen;
while (Strm.Size - Strm.Position) >= 2 do
begin
Strm.ReadBuffer(B, 1);
if B <> 13 then Continue;
Strm.ReadBuffer(B, 1);
if B <> 10 then
begin
if B = 13 then
begin
Strm.Seek(-1, soCurrent);
Continue;
end;
end;
SetString(s, PAnsiChar(Strm.Memory), Strm.Position-2);
StrmSize := Strm.Size - Strm.Position;
if StrmSize then
begin
Strm.Clear;
end else
begin
Ptr := PByte(Strm.Memory);
Inc(Ptr, Strm.Position);
Move(Ptr^, Strm.Memory^, StrmSize);
Strm.Size := StrmSize;
Strm.Position := 0;
end;
Memo1.Lines.Add(S);
end;
end;
You need to add a CRLF or newline to Edit1.Text and Edit2.Text

delphi DataSnap 2010 Stream File working Sample

I am trying to stream an XML File from Server To Client using DataSnap, with the help of ldsandon, i was able to download the sample from embarcadero, but my problem is I cannot follow it.
a pseudo of the program should work this way.
client will request from the server for selected xml file in the combobox.
the server will load the client selected xml file back to client.
i am just am trying to figure it out using delphi DataSnap, if not I will either use synapse or indy for tranferring the file, but I found Datasnap to be interesting.
could anyone help me please, a working if possible?
thanks a lot.
Please Help me, I need your help very badly.. thanks and thanks
I found this link, but I could not figure out how to convert it to TFileStream
// server side
function TServerMethods1.GetCDSXML(SQL: String; var FileSize: Integer): TStream;
begin
QryMisc.Close;
QryMisc.SQL.Text := SQL;
CDSMisc.Open;
Result := TMemoryStream.Create;
try
CDSMisc.SaveToStream(Result, dfXML);
FileSize := Result.Size; // not CDSMisc.DataSize;
Result.Position := 0; // Seek not implemented in abstract class
finally
CDSMisc.Close;
end;
end;
// client side
procedure TClientModule1.PopMiscCDS(SQL: String);
const
BufSize = $8000;
var
RetStream: TStream;
Buffer: PByte;
MemStream: TMemoryStream;
BytesRead: Integer;
FileSize: Integer;
begin
try
MemStream := TMemoryStream.Create;
GetMem(Buffer, BufSize);
try
//---------------------------------------------------------
RetStream := ServerMethods1Client.GetCDSXML(SQL, FileSize);
//---------------------------------------------------------
repeat
BytesRead := RetStream.Read(Pointer(Buffer)^, BufSize);
if BytesRead > 0 then
MemStream.WriteBuffer(Pointer(Buffer)^, BytesRead);
until BytesRead < BufSize;
if FileSize <> MemStream.Size then
raise Exception.Create('Error downloading xml');
MemStream.Seek(0, TSeekOrigin.soBeginning);
CDSMisc.Close;
CDSMisc.LoadFromStream(MemStream);
finally
FreeMem(Buffer, BufSize);
MemStream.Free;
end;
except
on E: Exception do
begin
ShowMessage(E.Message);
end;
end;
end;

Resources