I've 2 Apps Let's call Server And Client.
I'm using Delphi-xe8. App ->Multi-Device Application
In Both Side using: App tethering[tManager,tAProfile], SQLite Database.
In Server SQLite Database I've 6 images. I would like to View that images In Client Side.
In Client Side I've 6 [TImage].
When I Click Button 'Get Image List' I'm getting 6 images with the same view.
I would like 6 images view differently.->[Get From Server Database]
Client "Get Image List" button Code:
procedure TForm1.GetImgLstClick(Sender: TObject);
begin
tAProfile.SendString(tManager.RemoteProfiles.First,'GetImages','');
end;
Server Received Code:
procedure TForm2.tAProfileResourceReceived(const Sender: TObject;
const AResource: TRemoteResource);
var
MS1:TMemorystream;
begin
if AResource.Hint='GetImages' then
begin
MS1:=TMemorystream.Create;
rQuery.Close;
rQuery.SQL.Clear;
rQuery.SQL.Add('select image from users');
rQuery.Open;
while not rQuery.Eof do
begin
tblobField(rQuery.FieldByName('image')).SaveToStream(MS1);
Image1.Bitmap:=nil;
rQuery.Next;
end;
tAProfile.SendStream(tManager.RemoteProfiles.First,'SendImages',MS1);
end;
end;
Client Received Code:
procedure TForm1.tAProfileResourceReceived(const Sender: TObject;
const AResource: TRemoteResource);
var
MS:TMemoryStream;
begin
if AResource.Hint='SendImages' then
begin
Image1.Bitmap.LoadFromStream(AResource.Value.AsStream);
Image2.Bitmap.LoadFromStream(AResource.Value.AsStream);
Image3.Bitmap.LoadFromStream(AResource.Value.AsStream);
Image4.Bitmap.LoadFromStream(AResource.Value.AsStream);
Image5.Bitmap.LoadFromStream(AResource.Value.AsStream);
Image6.Bitmap.LoadFromStream(AResource.Value.AsStream);
end;
end;
Update: I gather from your most recent comment that you want to send your
images one-by-one.
A problem is that a Delphi dataset's TGraphicField supports a number of formats
which may be of variable size, so if you just write them to the server's outbound
stream, there is no way for the client to know, when reading the stream, where the
data of one image ends and the next one begins. A simple solution to that is to have
the server write the size of the image to the stream before it writes the image's
data to the stream, and get the client's code to read the image size so that it
knows how much of what follows is the image's data.
I'm going back to the answer I posted to your other q (Delphi: How to Get All Images From Server Database by using App tethering?), which uses TClientDataSets,
but adapting it so that it sends only the images (and their sizes) in the stream. The
code is still quite simple and should be no different in principle than using FireDAC datasets and a Sqlite data table:
Server
procedure TApp1Form.SendImageStream;
var
StreamToSend,
ImageStream : TMemoryStream;
StreamedImageSize : Integer;
begin
StreamToSend := TMemoryStream.Create;
ImageStream := TMemoryStream.Create;
try
CDS1.DisableControls;
CDS1.First;
while not CDS1.Eof do begin
ImageStream.Clear;
CDS1Graphic.SaveToStream(ImageStream);
ImageStream.Position := 0;
StreamedImageSize := ImageStream.Size;
StreamToSend.Write(StreamedImageSize, SizeOf(Integer));
StreamToSend.CopyFrom(ImageStream, StreamedImageSize);
CDS1.Next;
end;
StreamToSend.Position := 0;
TetheringAppProfile1.Resources.FindByName('BioLife').Value := StreamToSend;
finally
CDS1.EnableControls;
ImageStream.Free;
end;
end;
Client
// Note: In the client, CDS1 has only two fields, one named ID which is an
// ftAutoInc field, and Graphic, which is a TGraphicField
procedure TApp2Form.TetheringAppProfile1Resources0ResourceReceived(const Sender:
TObject; const AResource: TRemoteResource);
var
ReceivedStream : TStream;
ImageStream : TMemoryStream;
ImageSize : Integer;
begin
AResource.Value.AsStream.Position := 0;
ReceivedStream := AResource.Value.AsStream;
ImageStream := TMemoryStream.Create;
try
if CDS1.Active then
CDS1.EmptyDataSet // discard existing data
else
CDS1.CreateDataSet;
CDS1.DisableControls;
while ReceivedStream.Position < ReceivedStream.Size - 1 do begin
ImageStream.Clear;
ReceivedStream.ReadBuffer(ImageSize, SizeOf(Integer));
ImageStream.CopyFrom(ReceivedStream, ImageSize);
CDS1.Insert;
TGraphicField(CDS1.FieldByName('Graphic')).LoadFromStream(ImageStream);
CDS1.Post;
end;
CDS1.First;
finally
ImageStream.Free;
CDS1.EnableControls;
end;
end;
Original answer follows
I have already shown you a very simple way to move images between server and client app using TClientDataSets in my answer to your q Delphi: How to Get All Images From Server Database by using App tethering?. I assumed you knew enough about Delphi programming to be able to get the data from your Sqlite db into a TCientDataSet but perhaps not.
Below is the code for the server + client of my other answer, adapted to use FireDAC components instead of TClientDataSets. Again, it uses the server dataset's SaveToStream method to save its data to the stream from the server and LoadFromStream on the client side.
Notice that there are only two lines of code in the client app.
FDApp1 code:
type
TApp1Form = class(TForm)
TetheringManager1: TTetheringManager;
TetheringAppProfile1: TTetheringAppProfile;
DBImage1: TDBImage;
btnConnect: TButton;
Label1: TLabel;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
btnSendStream: TButton;
FDConnection1: TFDConnection;
FDQuery1: TFDQuery;
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
FDStanStorageBinLink1: TFDStanStorageBinLink;
procedure btnConnectClick(Sender: TObject);
procedure btnSendStreamClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure TetheringManager1PairedToRemote(const Sender: TObject; const
AManagerInfo: TTetheringManagerInfo);
private
procedure DataSetToStream;
end;
[...]
procedure TApp1Form.btnConnectClick(Sender: TObject);
begin
TetheringManager1.AutoConnect;
end;
procedure TApp1Form.btnSendStreamClick(Sender: TObject);
begin
DataSetToStream;
end;
procedure TApp1Form.FormCreate(Sender: TObject);
begin
Caption := Format('App1 : %s', [TetheringManager1.Identifier]);
FDQuery1.LoadFromFile('D:\D10\Samples\Data\BioLife.FDS');
end;
procedure TApp1Form.TetheringManager1PairedToRemote(const Sender: TObject; const
AManagerInfo: TTetheringManagerInfo);
begin
Label1.Caption := Format('Connected : %s %s',
[AManagerInfo.ManagerIdentifier,
AManagerInfo.ManagerName]);
end;
procedure TApp1Form.DataSetToStream;
var
Stream : TMemoryStream;
begin
Stream := TMemoryStream.Create;
FDQuery1.SaveToStream(Stream);
Stream.Position := 0;
TetheringAppProfile1.Resources.FindByName('BioLife').Value := Stream;
end;
FDApp2 code:
type
TApp2Form = class(TForm)
TetheringManager1: TTetheringManager;
TetheringAppProfile1: TTetheringAppProfile;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
DBImage1: TDBImage;
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
FDMemTable1: TFDMemTable;
FDStanStorageBinLink1: TFDStanStorageBinLink;
procedure TetheringAppProfile1Resources0ResourceReceived(const Sender: TObject;
const AResource: TRemoteResource);
public
end;
[...]
procedure TApp2Form.TetheringAppProfile1Resources0ResourceReceived(const Sender:
TObject; const AResource: TRemoteResource);
begin
AResource.Value.AsStream.Position := 0;
FDMemTable1.LoadFromStream(AResource.Value.AsStream);
end;
Of course, on the client side, if for some reason you want the images (but not the other server data) copied into another dataset, you can do that by a row-by-row copy, similar to the code in your qs.
Related
good evening!
I am trying to make a connection with Tethering, following the explanations of Malcon Groves (http://www.malcolmgroves.com/blog/?p=1854), however I am having the following problem:
When you click Connect, App1 apparently connects, but does not display the App2 handle.
App2 happens the same thing .... ....
I inserted the tetheringappprofile and tetheringmanager components and made the settings indicated ....
The codes are:
//App1
procedure TForm1.ConnectClick(Sender: TObject);
begin
TetheringManager1.AutoConnect;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Caption := Format('App1 : %s',[tetheringmanager1.Identifier]);
end;
procedure TForm1.TetheringManager1PairedToRemote(const Sender: TObject;
const AManagerInfo: TTetheringManagerInfo);
begin
Label1.Text := Format('Connected : %s %s', [AManagerInfo.ManagerIdentifier,
AManagerInfo.ManagerName]);
end;
procedure TForm1.TetheringManager1RequestManagerPassword(const Sender: TObject;
const ARemoteIdentifier: string; var Password: string);
begin
Password := '1234';
end;
.
//App2
procedure TForm1.FormCreate(Sender: TObject);
begin
Caption := Format('App2 : %s', [tetheringmanager1.Identifier]);
end;
procedure TForm1.TetheringManager1PairedFromLocal(const Sender: TObject;
const AManagerInfo: TTetheringManagerInfo);
begin
Label1.Text := Format('Connected : %s %s',[AManagerInfo.ManagerIdentifier,
AManagerInfo.ManagerName]);
end;
Thanks!
When you use AutoConnect to discover other apps, the TetheringAppProfile.Group property in both apps has to be same.
Malcolm Groves has indeed great series of articles about App Tethering. I also attended presentation from Jens Fudge about the subject. Otherwise very hard to master. Thank you guys!
Delphi ships itself with number of useful samples about App Tethering. They are located in directory: ..\Samples\Object Pascal\RTL\Tethering\
#Dave: connection through IPv6 should be possible. Did you try AllowedAdapters property?
http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Tether.Manager.TTetheringManager.AllowedAdapters
Is it possible in Delphi to just save the breakpointss in the .DSK file for a project and no other Desktop settings?
Most of the .DSK gets in the way, but not being able to save debug breakpoints is a real pain (especially when they are conditionally or actions are attached).
I've never come across an IDE facility to save only the breakpoint-related settings in the .Dsk file.
For amusement, I thought I'd try and implement something via an IDE add-in using OTA notifications. The code below runs fine installed into a package installed in D7, and the IDE seems quite happy to re-open a project whose .Dsk file has been processed by it (and the breakpoints get set!).
As you can see, it catches an OTA notifier's FileNotification event when called with a NotifyCode of ofnProjectDesktopSave, which happens just after the IDE has saved the .Dsk file (initially with the extension '.$$$', which I faile to notice when first writing this). It then reads the saved file file, and and prepares an updated version from which all except a specified list of sections are removed. The user then has the option to save the thinned-out file back to disk. I've used a TMemIniFile to do most of the processing simply to minimize the amount of code needed.
I had zero experience of writing an OTA notifier when I read your q, but the GE Experts FAQ referenced below was immensely helpful, esp the example notifier code.
Normally, deleting a project's .Dsk file is harmless, but use this code with caution as it has not been stress-tested.
Update: I noticed that the filename received by TIdeNotifier.FileNotification event actually has an extension of '.$$$'. I'm not quite sure why that should be, but seemingly the event is called before the file is renamed to xxx.Dsk. I thought that would require a change to how
to save the thinned-out version, but evidently not.
Update#2: Having used a folder-monitoring utility to see what actually happens, it turns out that the desktop-save notification the code receives is only the first of a number of operations related to the .Dsk file. These include renaming any existing version of the .Dsk file as a .~Dsk file and finally saving the .$$$ file as the new .Dsk file.
unit DskFilesu;
interface
{$define ForDPK} // undefine to test in regular app
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Buttons, StdCtrls, IniFiles, TypInfo
{$ifdef ForDPK}
, ToolsApi
{$endif}
;
{$ifdef ForDPK}
{
Code for OTA TIdeNotifier adapted from, and courtesy of, the link on http://www.gexperts.org/open-tools-api-faq/#idenotifier
}
type
TIdeNotifier = class(TNotifierObject, IOTANotifier, IOTAIDENotifier)
protected
procedure AfterCompile(Succeeded: Boolean);
procedure BeforeCompile(const Project: IOTAProject; var Cancel: Boolean);
procedure FileNotification(NotifyCode: TOTAFileNotification;
const FileName: string; var Cancel: Boolean);
end;
{$endif}
type
TDskForm = class(TForm)
edDskFileName: TEdit;
SpeedButton1: TSpeedButton;
OpenDialog1: TOpenDialog;
lbSectionsToKeep: TListBox;
lbDskSections: TListBox;
moDskFile: TMemo;
btnSave: TButton;
procedure btnSaveClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
private
procedure GetSectionsToKeep;
function GetDskFileName: String;
procedure SetDskFileName(const Value: String);
function GetDskFile: Boolean;
protected
public
DskIni : TMemIniFile;
property DskFileName : String read GetDskFileName write SetDskFileName;
end;
var
NotifierIndex: Integer;
DskForm: TDskForm;
{$ifdef ForDPK}
procedure Register;
{$endif}
implementation
{$R *.DFM}
{$ifdef ForDPK}
procedure Register;
var
Services: IOTAServices;
begin
Services := BorlandIDEServices as IOTAServices;
Assert(Assigned(Services), 'IOTAServices not available');
NotifierIndex := Services.AddNotifier(TIdeNotifier.Create);
end;
{$endif}
procedure DskPopUp(FileName : String);
var
F : TDskForm;
begin
F := TDskForm.Create(Application);
try
F.DskFileName := FileName;
F.ShowModal;
finally
F.Free;
end;
end;
function TDskForm.GetDskFileName: String;
begin
Result := edDskFileName.Text;
end;
procedure TDskForm.SetDskFileName(const Value: String);
begin
edDskFileName.Text := Value;
if Assigned(DskIni) then
FreeAndNil(DskIni);
btnSave.Enabled := False;
DskIni := TMemIniFile.Create(DskFileName);
DskIni.ReadSections(lbDskSections.Items);
GetSectionsToKeep;
end;
procedure TDskForm.btnSaveClick(Sender: TObject);
begin
DskIni.UpdateFile;
end;
procedure TDskForm.FormCreate(Sender: TObject);
begin
lbSectionsToKeep.Items.Add('watches');
lbSectionsToKeep.Items.Add('breakpoints');
lbSectionsToKeep.Items.Add('addressbreakpoints');
if not IsLibrary then
DskFileName := ChangeFileExt(Application.ExeName, '.Dsk');
end;
procedure TDskForm.GetSectionsToKeep;
var
i,
Index : Integer;
SectionName : String;
begin
moDskFile.Lines.Clear;
for i := lbDskSections.Items.Count - 1 downto 0 do begin
SectionName := lbDskSections.Items[i];
Index := lbSectionsToKeep.Items.IndexOf(SectionName);
if Index < 0 then
DskIni.EraseSection(SectionName);
end;
DskIni.GetStrings(moDskFile.Lines);
btnSave.Enabled := True;
end;
function TDskForm.GetDskFile: Boolean;
begin
OpenDialog1.FileName := DskFileName;
Result := OpenDialog1.Execute;
if Result then
DskFileName := OpenDialog1.FileName;
end;
procedure TDskForm.SpeedButton1Click(Sender: TObject);
begin
GetDskFile;
end;
{$ifdef ForDPK}
procedure RemoveNotifier;
var
Services: IOTAServices;
begin
if NotifierIndex <> -1 then
begin
Services := BorlandIDEServices as IOTAServices;
Assert(Assigned(Services), 'IOTAServices not available');
Services.RemoveNotifier(NotifierIndex);
end;
end;
function MsgServices: IOTAMessageServices;
begin
Result := (BorlandIDEServices as IOTAMessageServices);
Assert(Result <> nil, 'IOTAMessageServices not available');
end;
procedure TIdeNotifier.AfterCompile(Succeeded: Boolean);
begin
end;
procedure TIdeNotifier.BeforeCompile(const Project: IOTAProject; var Cancel: Boolean);
begin
Cancel := False;
end;
procedure TIdeNotifier.FileNotification(NotifyCode: TOTAFileNotification;
const FileName: string; var Cancel: Boolean);
begin
Cancel := False;
// Note: The FileName passed below has an extension of '.$$$'
if NotifyCode = ofnProjectDesktopSave then
DskPopup(FileName);
end;
initialization
finalization
RemoveNotifier;
{$endif}
end.
Hi I am sending a file via tsocket, I'm editing the first code to not use opendilalog but I want to use a string with the path of the file to send, the problem is that the second code that eh OpenDialog edited for not using shoot me an error saying the file to send is being used by another process.
The first source
procedure TForm1.Button2Click(Sender: TObject);
begin
if ClientSocket1.Active = True then
begin
OpenDialog1.Filter := 'All Files (*.*)'; // you can add more choices by adding | and followed by description and (*.extension)
OpenDialog1.FilterIndex := 1; // Here you follow which index number from above you want
if OpenDialog1.Execute then
begin
Edit1.Text := ExtractFileName(OpenDialog1.FileName); // To send as filename after
ClientSocket1.Socket.SendText('FILE!'+Edit1.Text);
sleep(2000); // Need to sleep so the other end has time to process the commands
Streamsize := TFileStream.Create(OpenDialog1.FileName, fmopenread); // Stream created just to Calculate size
Edit2.Text := inttostr(Streamsize.Size);
Sleep(2000);
ClientSocket1.Socket.SendText('SIZE!'+Edit2.Text); // Sends filesize through primary socket
Streamsize.Position := 0;
Streamsize.Free;
sleep(2000);
ClientSocket2.Address := Edit3.Text;
ClientSocket2.Open; // ready to send file on second socket
if ClientSocket2.Socket.SendStream(TFileStream.Create(OpenDialog1.FileName, fmopenRead)) then memo1.Lines.Add('File Sent');
// above creates a stream and sends as a stream its in a if line because this is the only way it will automatically check the byte order and send the whole stream
end;
end
else
MessageDlg('Error: You are not connected', mtError, [MbOK],0); // Error Check above code won't work until the socket is connected
end;
The second source
procedure TForm1.Button2Click(Sender: TObject);
var
archivo: string;
begin
archivo := 'c:/clap.jpg';
if ClientSocket1.Active = True then
begin
Edit1.Text := ExtractFileName(archivo);
// To send as filename after
ClientSocket1.Socket.SendText('FILE!' + Edit1.Text);
sleep(2000); // Need to sleep so the other end has time to process the commands
Streamsize := TFileStream.Create(archivo, fmopenread);
// Stream created just to Calculate size
Edit2.Text := inttostr(Streamsize.Size);
sleep(2000);
ClientSocket1.Socket.SendText('SIZE!' + Edit2.Text);
// Sends filesize through primary socket
Streamsize.Position := 0;
Streamsize.Free;
sleep(2000);
ClientSocket2.Address := '127.0.0.1';
ClientSocket2.Open; // ready to send file on second socket
if ClientSocket2.Socket.SendStream(TFileStream.Create(archivo, fmopenread))
then
Memo1.Lines.Add('File Sent');
// above creates a stream and sends as a stream its in a if line because this is the only way it will automatically check the byte order and send the whole stream
end
else
MessageDlg('Error: You are not connected', mtError, [MbOK], 0); // Error Check above code won't work until the socket is connected
end;
The server.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ScktComp, ComCtrls, idglobal, ExtCtrls, ShellAPI;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Label1: TLabel;
ServerSocket1: TServerSocket;
ServerSocket2: TServerSocket;
GroupBox1: TGroupBox;
Edit1: TEdit;
Edit2: TEdit;
Label2: TLabel;
Label3: TLabel;
ProgressBar1: TProgressBar;
Timer1: TTimer;
StatusBar1: TStatusBar;
procedure Button1Click(Sender: TObject);
procedure ServerSocket1Accept(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocket2Accept(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
procedure ServerSocket2ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
IncommingStream: TFileStream;
TimeTaken: integer;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
// Written by me ColdFuzion
// All i ask is i be given some credit for coding this my e-mail is ColdFuzion#hushmail.com
// Program Usage: To recieve Files sent by the client
procedure TForm1.Button1Click(Sender: TObject);
begin
ServerSocket1.Open;
Memo1.Lines.Add('Server Listening on '+inttostr(ServerSocket1.Port) );
end;
procedure TForm1.ServerSocket1Accept(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo1.Lines.Add('Client connected From '+Socket.RemoteHost)
// Adds the clients host as it connects
end;
procedure TForm1.ServerSocket2Accept(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo1.Lines.Add('Incoming File Transfer');
end;
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var IncommingText, StrippedData, CommandName: string;
begin
IncommingText := socket.ReceiveText;
StrippedData := copy(IncommingText,6,length(IncommingText) );
CommandName := copy(IncommingText,0,5);
if CommandName = 'FILE!' then
begin
IncommingStream := TFileStream.Create(StrippedData, fmCREATE or fmOPENWRITE and fmsharedenywrite); // Once File name is recieved the stream to recieve
Edit1.Text := StrippedData; // The file is created
ServerSocket2.Open;
end
else
if CommandName = 'SIZE!' then
begin
Edit2.Text := StrippedData;
ProgressBar1.Max := StrToInt(StrippedData);
ProgressBar1.Min := 0;
Memo1.lines.Add('Recieving File '+Edit1.Text +' of size '+Edit2.Text);
end;
end;
procedure TForm1.ServerSocket2ClientRead(Sender: TObject;
// This is the secondary socket it is the most important part of the program
Socket: TCustomWinSocket);
// It processes the incomming file stream
var Buffer: array [0..9999] of Char;
IncommingLen, RecievedLen: integer;
Filepath: string;
begin
Timer1.Enabled := True;
IncommingLen := socket.ReceiveLength;
// If the size of any incomming data is the size of 0 then the process begins
Filepath := ExtractFilePath(Edit1.Text)+Edit1.Text;
// Sets a String Filepath for the actual directory with the filename so that the shellexecute can run this after
while IncommingLen > 0 do
// Must make sure the process ends
begin
RecievedLen := socket.ReceiveBuf(Buffer, Sizeof(Buffer));
// Changes the size of RecievedLen by the amount of incoming data recieved
if RecievedLen <= 0 then
// Small part of the code where once the buffer reaches 0 the code will exit
Break
else
IncommingStream.Write(Buffer, RecievedLen);
// Writes the Incoming data into a new stream by the filename and size which is recieved
ProgressBar1.StepBy(RecievedLen);
// through the primary socket Also this line increases the progess indicator bar
if IncommingStream.Size >= strtoint(Edit2.Text) then
// Onces the stream size begins to reach the size which was sent before sending the file then this
begin
// procedure will start
IncommingStream.Free;
// Free's the stream
memo1.Lines.Add('File '+Edit1.Text +' Recieved Successfuly');
memo1.Lines.Add('Time Taken to Recieve File ' +IntToStr(TimeTaken)+' seconds');
ServerSocket1.Socket.Connections[0].SendText('DONE!');
Edit1.Text := '';
// From here it starts setting the variables back
Edit2.Text := '';
ProgressBar1.Position := 0;
Timer1.Enabled := False;
TimeTaken := 0;
if Messagedlg('Would you Like to open the recieved file?', mtConfirmation, [MbYes,MbNo],0) = MrYes then // Simply asks the user if he wants to open the file if yes will execute if no break
begin
ShellExecute(Form1.Handle, 'open', pchar(Filepath),nil, nil, SW_NORMAL); // A shellapi was added to uses to beable to execute this line
end;
Break; // This line basically executes any file using the extension from the windows ini files.
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.Text := '';
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
inc(TimeTaken,1);
// Counts number of seconds starts once the filestream begins
end;
end.
// This entire Program could use alot more Error checking but it simply is a very basic
// Example of how to do certain things using the basic components that come with Delphi
// There are hardly any examples of sending files with delphi on the internet so most of
// the code here had to be improvised i hope this helps people where i had to struggle with
I can correct as the second code to pull no more errors?
If the OS is complaining that the file is already in use, then it really is already in use.
You are creating multiple streams to the same file (you are also using the wrong path delimiter). Create one stream and reuse it multiple times instead, eg:
procedure TForm1.Button2Click(Sender: TObject);
var
archivo: string;
Strm: TFileStream;
begin
archivo := 'c:\clap.jpg';
if ClientSocket1.Active then
begin
Strm := TFileStream.Create(archivo, fmOpenRead or fmShareDenyWrite);
try
Edit1.Text := ExtractFileName(archivo);
Edit2.Text := IntToStr(Strm.Size);
ClientSocket1.Socket.SendText('FILE!' + Edit1.Text);
// Need to sleep so the other end has time to process the commands
Sleep(2000);
ClientSocket1.Socket.SendText('SIZE!' + Edit2.Text);
Sleep(2000);
ClientSocket2.Address := '127.0.0.1';
ClientSocket2.Open; // ready to send file on second socket
if ClientSocket2.Socket.SendStream(Strm) then
begin
// SendStream() takes ownership of the Stream, so don't free it!
Strm := nil;
Memo1.Lines.Add('File Sent');
end;
finally
Strm.Free;
end;
end
else
MessageDlg('Error: You are not connected', mtError, [MbOK], 0);
end;
As noted, SendStream() takes ownership of the stream. If you are using the socket in non-blocking mode, it may take time to transfer the whole stream. You would not be able to re-open the file again until that transfer is finished. That could account for the error you are seeing.
Now, with that said, the fact that you have to introduce sleeps into your protocol in order to get commands processed correctly means you did not design your protocol very well to begin with. It would be much more reliable to put a delimiter between your commands instead, eg:
procedure TForm1.Button2Click(Sender: TObject);
var
archivo: string;
Strm: TFileStream;
begin
archivo := 'c:\clap.jpg';
if ClientSocket1.Active then
begin
Strm := TFileStream.Create(archivo, fmOpenRead or fmShareDenyWrite);
try
Edit1.Text := ExtractFileName(archivo);
Edit2.Text := IntToStr(Strm.Size);
ClientSocket1.Socket.SendText('FILE!' + Edit1.Text + #13#10);
ClientSocket1.Socket.SendText('SIZE!' + Edit2.Text + #13#10);
ClientSocket2.Address := '127.0.0.1';
ClientSocket2.Open; // ready to send file on second socket
if ClientSocket2.Socket.SendStream(Strm) then
begin
// SendStream() takes ownership of the Stream and will free it
// after it is done sending, so don't free it yourself!
Strm := nil;
Memo1.Lines.Add('File Sent');
end;
finally
Strm.Free;
end;
end
else
MessageDlg('Error: You are not connected', mtError, [MbOK], 0);
end;
Then the receiver can simply read inbound data and split it on the delimiters as needed, no sleeps needed.
BTW, you are essentially recreating the FTP protocol, jut with a different syntax, so why not use the actual FTP protocol instead? There are plenty of FTP components/libraries readily available.
i've an FTP uploader project that uses a form created on run time to start uploading to multiple FTP Servers ( using Indy ) , my issue is as follows ( and i really need your help ) .
On a Form i put an IdFTP Component + an Upload button + public properties named FTPSrvAdrs and SrcFile + TrgFolder like this way :
type
TFtpUploader = class(TForm)
IdFTP: TIdFTP;
StartUpload:TButton;
UploadProgress:TProgressBar;
procedure StartUploadClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
FFtpSrvAdrs:String;
FSrcFile:String;
FTargetFtpFld:String;
Procedure StartMyUpload();
procedure SetFtpAdrs(const value:string);
procedure SetSrcFile(const value:string);
procedure SetTargetFtpFld(const value:string);
{ Private declarations }
public
{ Public declarations }
property FtpAdrs:string read FFtpSrvAdrs write SetFtpAdrs;
property SourceFile:string read FSrcFile write SetSrcFile;
property TargetFtpFld:string read FTargetFtpFld write SetTargetFtpFld;
end;
var
FtpUploader: TFtpUploader;
implementation
procedure TFtpUploader.StartUploadClick(Sender: TObject);
begin
StartMyUpload();
end;
procedure TFtpUploader.SetFtpAdrs(const value: string);
begin
FFtpSrvAdrs:=value;
end;
procedure TFtpUploader.SetSrcFile(const value: string);
begin
FSrcFile:=value;
end;
procedure TFtpUploader.SetTargetFtpFld(const value: string);
begin
FTargetFtpFld:=value;
end;
procedure TFtpUploader.StartMyUpload;
var
FtpUpStream: TFileStream;
begin
ftpUpStream:= TFileStream.create(FSrcFile, fmopenread)
try
with IdFTP do begin
Host:= FFtpSrvAdrs;
Username:='MyUserName';
Password:='MyPassword';
end;
IdFTP.Connect(true, 1200)
IdFTP.Passive:= true;
IdFTP.ChangeDir(FTargetFtpFld)
IdFTP.Put(ftpUpStream,FSrcFile, false);
finally
ftpUpStream.Free;
end;
end;
procedure TFtpUploader.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:=caFree;
end;
This Form will be created on RunTime ( 4 times = 4 buttons will launch it separately like this way :
in the main form i've this procedure :
Procedure MainForm.UploadTo(FTPSrv,SrcFile,FtpTargetFld:String);
var
FUploadFrm:TFtpUploader;
begin
FUploadFrm:=TFtpUploader.Create(nil);
if assigned(FUploadFrm) then
begin
FUploadFrm.FtpAdrs:=FTPSrv;
FUploadFrm.SourceFile:=SrcFile;
FUploadFrm.TargetFtpFld:=FtpTargetFld;
FUploadFrm.Show;
end;
end;
procedure MainForm.Button1Click(Sender: TObject);
begin
UploadTo('MyFtpSrv_1','MySrcFile_1','MyFtpTargetFld_1');
end;
procedure MainForm.Button2Click(Sender: TObject);
begin
UploadTo('MyFtpSrv_2','MySrcFile_2','MyFtpTargetFld_2');
end;
// same with other 2 buttons
the FtpUploader form is Created / Opened ( 4 instances ) ,The ISSUE IS when i click on StartUpload button the FTP upload process is not started on all these 4 instances , but i've to wait each upload process is done ( finished ) and the other will auto-start , that means not all upload processes are started in same time .
Thank you .
It seems you have to either change Indy library for some non-blocking in-background library (event based or completion port based), or to make your program multi-threading (with it's own bunch of problems like user clicking a button 20 times or closing the form while the process is going, or even closing the program on the run).
Based on http://otl.17slon.com/book/doku.php?id=book:highlevel:async it can look anything like this:
TFtpUploader = class(TForm)
private
CanCloseNow: boolean;
...
procedure TFtpUploader.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Self.CanCloseNow
then Action := caFree
else Action := caIgnore;
end;
procedure TFtpUploader.MyUploadComplete;
begin
Self.CanCloseNow := True;
Self.Close;
end;
procedure TFtpUploader.StartMyUpload;
begin
Self.CanCloseNow := false;
Self.Enabled := False;
Self.Visible := True;
Application.ProcessMessages;
Parallel.Async(
procedure
var
FtpUpStream: TFileStream;
begin
ftpUpStream:= TFileStream.create(FSrcFile, fmopenread)
try
with IdFTP do begin
Host:= FFtpSrvAdrs;
Username:='MyUserName';
Password:='MyPassword';
Connect(true, 1200)
Passive:= true;
ChangeDir(FTargetFtpFld)
// this does not return until uploaded
// thus would not give Delphi a chance to process buttons
// pressed on other forms.
Put(ftpUpStream,FSrcFile, false);
end;
finally
ftpUpStream.Free;
end;
end
,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
MyUploadComplete;
end;
);
end;
Or you can use simplier AsyncCalls library http://andy.jgknet.de/blog/bugfix-units/asynccalls-29-asynchronous-function-calls/
I have small problem (I wish it's small) with disconnecting server - I mean - only in the moment when I want to disconnect it from server application (server.active=false).
Here is my simple code:
type
PClient = ^TClient;
type
TClient = record
Name: string;
AContext: TIdContext;
end;
clients: TThreadList;
SERVER: TIdTCPServer;
procedure TX.SERVERConnect(AContext: TIdContext);
var
NewClient: PClient;
s:string;
begin
s := AContext.Connection.socket.ReadLn();
GetMem(NewClient, SizeOf(TClient));
NewClient.name:=s;
NewClient.AContext := AContext;
AContext.data := TObject(NewClient);
try
clients.LockList.Add(NewClient);
finally
clients.UnlockList;
end;
AContext.Connection.socket.writeln('E:');//answer to client - "all right"
End;
procedure TX.SERVERDisconnect(AContext: TIdContext);
var
AClient: PClient;
begin
AClient := PClient(AContext.data);
try
clients.LockList.Remove(AClient);
finally
clients.UnlockList;
end;
FreeMem(AClient);
AContext.data := nil;
end;
It have to works only for sending data to clients therefore I read only one data line in onconnect procedure - it contains login name.
Procedure for sending data in my code looks like (is it good?):
var
procedure TX.send(what: string; where: string);
i, ile: integer;
s: string;
Aclient: PClient;
list: tlist;
begin
list:= SERVER.Contexts.LockList;
try
for i := 0 to list.Count - 1 do
with TIdContext(list[i]) do
begin
AClient := PClient(data);
if where = ActClient^.name then
Connection.IOHandler.writeln(what);
end;
finally
SERVER.Contexts.UnlockList;
end;
end;
It looks it works good - I mean. But when I want to disable server by SERVER.active:=false application freezes? I tried to free clients etc. but it dosen't work in my bad code.
Could Somebody help me and give me advice how to stop server for this code?
Artik