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;
Related
In Delphi 10.4, I try to save a valid TPicture base64-encoded to an INI file:
procedure TForm1.SavePictureToIniFile(const APicture: TPicture);
var
LInput: TMemoryStream;
LOutput: TMemoryStream;
MyIni: TIniFile;
ThisFile: string;
begin
if FileSaveDialog1.Execute then
ThisFile := FileSaveDialog1.FileName
else EXIT;
LInput := TMemoryStream.Create;
LOutput := TMemoryStream.Create;
try
APicture.SaveToStream(LInput);
LInput.Position := 0;
TNetEncoding.Base64.Encode(LInput, LOutput);
LOutput.Position := 0;
MyIni := TIniFile.Create(ThisFile);
try
MyIni.WriteBinaryStream('Custom', 'IMG', LOutput); // Exception# 234
finally
MyIni.Free;
end;
finally
LInput.Free;
LOutput.Free;
end;
end;
WriteBinaryStream creates an exception:
ERROR_MORE_DATA 234 (0xEA) More data is available.
Why? What does this mean? How can this problem be solved?
EDIT: Taking into consideration what #Uwe Raabe and #Andreas Rejbrand said, this code (which does not use base64-encoding) now works:
procedure TForm1.SavePictureToIniFile(const APicture: TPicture);
var
LInput: TMemoryStream;
MyIni: System.IniFiles.TMemIniFile;
ThisFile: string;
begin
if FileSaveDialog1.Execute then
ThisFile := FileSaveDialog1.FileName
else EXIT;
LInput := TMemoryStream.Create;
try
APicture.SaveToStream(LInput);
LInput.Position := 0;
MyIni := TMemIniFile.Create(ThisFile);
try
MyIni.WriteBinaryStream('Custom', 'IMG', LInput);
MyIni.UpdateFile;
finally
MyIni.Free;
end;
finally
LInput.Free;
end;
end;
I believe this is a limitation in the operating system's functions for handling INI files; the string is too long for it.
If you instead use the Delphi INI file implementation, TMemIniFile, it works just fine. Just don't forget to call MyIni.UpdateFile at the end.
Yes, this is indeed a limitation in the Windows API, as demonstrated by the following minimal example:
var
wini: TIniFile;
dini: TMemIniFile;
begin
wini := TIniFile.Create('C:\Users\Andreas Rejbrand\Desktop\winini.ini');
try
wini.WriteString('General', 'Text', StringOfChar('W', 10*1024*1024));
finally
wini.Free;
end;
dini := TMemIniFile.Create('C:\Users\Andreas Rejbrand\Desktop\pasini.ini');
try
dini.WriteString('General', 'Text', StringOfChar('D', 10*1024*1024));
dini.UpdateFile;
finally
dini.Free;
end;
(Recall that INI files were initially used to store small amounts of configuration data in the 16-bit Windows era.)
Also, Uwe Raabe is right: you should save the Base64 string as text.
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.
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.
We have a library function that goes like this:
class function TFileUtils.ReadTextStream(const AStream: TStream): string;
var
StringStream: TStringStream;
begin
StringStream := TStringStream.Create('', TEncoding.Unicode);
try
// This is WRONG since CopyFrom might rewind the stream (see Remys comment)
StringStream.CopyFrom(AStream, AStream.Size - AStream.Position);
Result := StringStream.DataString;
finally
StringStream.Free;
end;
end;
When I check the string that is returned by the function the first Char is the (little-endian) BOM.
Why doesn't TStringStream ignore the BOM?
Is there a better way to do this? I don't need backwards compatibility with older Delphi versions, a working solution for XE2 would be fine.
The BOM has to be coming from the source TStream, as TStringStream does not write a BOM. If you want to ignore the BOM if it is present in the source, you have to do it manually before then copying the data, eg:
class function TFileUtils.ReadTextStream(const AStream: TStream): string;
var
StreamPos, StreamSize: Int64;
Buf: TBytes;
NumBytes: Integer;
Encoding: TEncoding;
begin
Result := '';
StreamPos := AStream.Position;
StreamSize := AStream.Size - StreamPos;
// Anything available to read?
if StreamSize < 1 then Exit;
// Read the first few bytes from the stream...
SetLength(Buf, 4);
NumBytes := AStream.Read(Buf[0], Length(Buf));
if NumBytes < 1 then Exit;
Inc(StreamPos, NumBytes);
Dec(StreamSize, NumBytes);
// Detect the BOM. If you know for a fact what the TStream data is encoded as,
// you can assign the Encoding variable to the appropriate TEncoding object and
// GetBufferEncoding() will check for that encoding's BOM only...
SetLength(Buf, NumBytes);
Encoding := nil;
Dec(NumBytes, TEncoding.GetBufferEncoding(Buf, Encoding));
// If any non-BOM bytes were read than rewind the stream back to that position...
if NumBytes > 0 then
begin
AStream.Seek(-NumBytes, soCurrent);
Dec(StreamPos, NumBytes);
Inc(StreamSize, NumBytes);
end else
begin
// Anything left to read after the BOM?
if StreamSize < 1 then Exit;
end;
// Now read and decode whatever is left in the stream...
StringStream := TStringStream.Create('', Encoding);
try
StringStream.CopyFrom(AStream, StreamSize);
Result := StringStream.DataString;
finally
StringStream.Free;
end;
end;
Apparently TStreamReader doesn't suffer from the same problem:
var
StreamReader: TStreamReader;
begin
StreamReader := TStreamReader.Create(AStream);
try
Result := StreamReader.ReadToEnd;
finally
StreamReader.Free;
end;
end;
TStringList also works (thanks whosrdaddy):
var
Strings: TStringList;
begin
Strings := TStringList.Create;
try
Strings.LoadFromStream(AStream);
Result := Strings.Text;
finally
Strings.Free;
end;
end;
I also measured both methods and TStreamReader seems to be about twice as fast.
I'm trying to transfer some big streams (~1Mb) between DataSnap server/client but to no avail. I'm trying to understand the code of Jim Tierney (http://blogs.embarcadero.com/jimtierney/2009/04/06/31461) with no luck and i can't even compile the code because of a missing library, anyway ...
The max size of a stream i`m able to receive is 64k, so any tips/ideas/code samples you can provide for a weekend programmer like me will be very welcomed. Thank you!
my server code:
function TsrvMethods.getStream(iCount: integer): TStream;
begin
Result := dummyStream('0123456789', iCount);
end;
function dummyStream(sCnt: string; iCount: integer): TStream;
begin
Result := TMemoryStream.Create;
while iCount > 1 do begin
Result.Write(Pointer(sCnt)^, Length(sCnt));
Dec(iCount);
end;
Result.Seek(0, TSeekOrigin.soBeginning);
end;
my client calling code:
procedure TfrmMain.butStreamClick(Sender: TObject);
var
sStr : TStream;
begin
cycleConnection; //make sure we have an active connection
with TsrvMethodsClient.Create( SQLConn.DBXConnection, False ) do begin
sStr := getStream( Integer(SpinCount.Value) );
Free;
end;
FreeAndNil(sStr);
end;
Actually, i think i`ve got it. I'm posting this as an answer maybe somebody else need this.
procedure TfrmMain.butStreamClick(Sender: TObject);
const
iBufSize = 128;
var
sStr : TStream;
sMem : TMemoryStream;
buf: PByte;
iRead: integer;
begin
cycleConnection;
with TsrvMethodsClient.Create( SQLConn.DBXConnection, False ) do begin
sStr := getStream( 500000 ); //500k stream
GetMem(buf, iBufSize);
sMem := TMemoryStream.Create;
try
repeat
iRead := sStr.Read( Pointer(buf)^, iBufSize);
if iRead > 0 then sMem.WriteBuffer( Pointer(buf)^, iRead);
if iRead < iBufSize then break;
until iRead < iBufSize;
finally
FreeMem(buf, iBufSize);
end;
Free;
end;
FreeAndNil(sStr);
FreeAndNil(sMem);
end;
P.S.
Searching through DataSnap code samples i`ve found that one (speed related) improvement would be to have iBufSize set to 61440 (or equivalent hex value $F000) which seems to be the biggest size can be received in one go. If receiving stream is bigger then reported size will be -1 and the code above is needed to read the entire stream.