Clientes FTP en Delphi


Hola amigos, en este articulo veremos como podemos construir Clientes Ftp en delphi, a través del componente NMFTP incluido en la ficha Fasnet. Por cierto Delphi 7 no contiene esta ficha pero puedes adquirir el componente en la pagina de sus creadores la cual es http://www.netmastersllc.com, claro esto con una modica suma. Bueno pero para los que tienen Delphi 7 no se preocupen, ya que trae la ficha Indy Clients y ahí viene un componente que se llama IdFtp con el cual pueden desarrollar su cliente, claro no es exactamente igual pero hacen lo mismo.

Bueno, como quizás ya sepan, FTP significa Protocolo de Transferencia de Archivos (File Transfer Protocol), este servicio comúnmente utiliza el puerto 21 para conectar los Sockets y nos permite precisamente eso, realizar transferencias de archivos entre un cliente y un servidor. Si no sabes lo que es un socket te recomiendo que entres al articulo que escribí sobre ellos (Articulo sobre Sockets).

En nuestro caso desarrollaremos un pequeño pero muy potente sistema de Cliente FTP, con el cual nos podremos conectar a un Servidor de FTP y realizar las operaciones mas comunes, según el tipo de cuenta y permisos que tengamos.

NOTA: Al final pongo el código fuente, pero aprenderías mas si tratas de hacerlo primero.

Es necesario entonces que describa las propiedades y eventos mas importantes del componente NMFTP.

Propiedad / Evento Descripción

CurrentDir

Contiene el nombre del directorio actualmente ocupado en el sistema remoto, si se cambia de directorio también cambia esta propiedad.
FTPDirectoryList
Es utilizada solamente cuando la propiedad de ParseList se pone en True. FTPDirectoryList contiene cada uno de los elementos del listado del directorio separado en características como el nombre del archivo, tamaño, atributos, etc.
OnListItem
Este evento ocurre cuando se está realizando  un listado del directorio remoto. 
ParseList
Es de tipo Boolean y según este activa o no  analiza los listados entrantes al directorio en la Propiedad  FTPDirectoryList.
Password
Especifica la contraseña usada para abrir una sesión  en el servidor remoto de ftp.
UserID Es el nombre de usuario con el cual se conectara al servidor remoto de ftp.
Vendor
Especifica el tipo de servidor de  ftp con el cual está siendo conectado. Esto permite que el componente analice los listados del directorio enviados del servidor de la manera apropiada. El valor por defecto detecta el tipo de servidor, pero de cualquier forma aquí tienes la lista de constantes que puedes utilizar:

NMOS_WINDOWS,  NMOS_VM,  NMOS_BULL,  NMOS_MAC NMOS_TOPS20,  NMOS_VMS,  NMOS_OS2,  NMOS_MVS_IBM,  NMOS_MVS_INTERLINK,  NMOS_OTHER,  NMOS_AUTO,  NMOS_NT NMOS_TANDEM,  NMOS_AS400 NMOS_OS9,  NMOS_NETWARE

BeenCanceled
Es de tipo Boolean y se activa a True cuando la operación ha sido cancelada.
BeenTimedOut
Es de tipo Boolean y esta en True cuando la operación actual ha medido el tiempo de espero, o FALSA si la operación actual no ha medido el tiempo de espero
BytesRecvd
Contiene el número de Bytes recibidos de la transferencia de datos actual.
BytesSent
Contiene el número de  Bytes enviados durante la transacción actual de los datos.
BytesTotal
BytesTotal contiene el número total de Bytes para enviar o para recibir en la transacción actual de los datos.
Connected
Esta en True si el cliente está conectado actualmente con el servidor remoto, y False si el cliente no está conectado.
Host
Contiene el nombre o la dirección IP del servidor remoto a conectar.
 LastErrorNo Contiene el ultimo error reportado del Socket.
 
LocalIP
Contiene la dirección  IP de la computadora local.
port Especifica el numero de puerto con el cual se conectara al servidor de ftp, comúnmente suele ser el numero 21.
TimeOut
Especifica la cantidad de horas (en milisegundos) para esperar una respuesta del host antes de que se levante una excepción y se aborte la operación actual. Si el tiempo es 0, una excepción nunca se levanta, y las operaciones nunca miden el tiempo de espera.
TransactionReply Contiene el resultado del ultimo comando enviado al servidor.
 
OnAuthenticationFailed
Se llama al evento  OnAuthenticationFailed cuando  la contraseña es inválida. Esto podría ser debido un nombre de cuenta inválido (UserID), a o una contraseña inválida.
OnAuthenticationNeeded
Se llama al evento OnAuthenticationNeeded cuando la propiedad UserID o la propiedad Password está en blanco. Si el parámetro manejado se deja en False, una excepción será levantada, y la conexión será abortada, pero si el parámetro manejado se fija a True, y se proporcionan un UserID y/o una contraseña, la autentificación será reintentada otra vez. Si falla una segunda vez, una excepción será levantada y se aborta la conexión.
OnTransactionStart
Se llama al evento  OnTransactionStart cada vez que los datos se envían del servidor remoto a la computadora local usando el Socket de Datos.
OnTransactionStop
Se llama al evento  OnTransactionStop cuando una transferencia de datos del servidor remoto de ftp a la computadora local ha terminado.
OnUnSupportedFunction
Se llama al evento  OnUnSupportedFunction cuando un comando del ftp no puede ejecutarse en el servidor remoto de ftp.  El parámetro de Trans_Type especifica qué comando no pudo ejecutarse. Los valores  posibles para este parámetro se listan a continuacion. cmdChangeDir, cmdMakeDir, cmdDelete, cmdRemoveDir, cmdList, cmdRename, cmdUpRestore, cmdDownRestore, cmdDownload, cmdUpload, cmdAppend, cmdReInit, cmdAllocate, cmdNList, cmdDoCommand, cmdCurrentDir. La forma de utilizarlo sera untilizando dentro de este evento un case Trans_Type of
y a cntinacion las constantes y el codigo que quieres que se ejecute.
OnAccept
Se llama al evento  OnAccept cuando hay una conexión entrante al Socket, y el método Listen se ha invocado con el parámetro  Sinc como FALSO.
OnConnect
Se llama al evento  OnConnect cuando una conexión se establece con el servidor remoto. Esto corresponde al mensaje de FD_CONNECT del Winsock.
OnConnectionFailed
Se llama al evento OnConnectionFailed cuando una conexión no puede ser establecida con el servidor remoto.
OnConnectionRequired Se llama al evento de OnConnectionRequired siempre que se llamen métodos que requieran estar conectado con el servidor remoto
OnDisconnect
El evento OnDisconnect es llamado cuando el cliente se desconecta del servidor. Esto corresponde al mensaje de FD_CLOSE del Winsock.
OnError
Se llama al evento  OnError cuando ocurre un error del Winsock. El parámetro de ErrNo especifica la representación numérica del error que ocurrió, y el parámetro de ErrMsg es el mensaje de error asociado al número del error.
OnHostResolved
Se llama al evento OnHostResolved  cuando el Host ha respondido a la conexión.
OnInvalidHost
Se llama al evento OnInvalidHost cuando el Host especificado  es inválido. Si el parámetro manejado se fija a True, después de la conexión. si no  una excepción se levanta.
OnPacketRecvd
Se llama al evento OnPacketRecvd cuando los datos se reciben del servidor remoto. Utilizado conjuntamente con las propiedades BytesRecvd y de BytesTotal el progreso de una transacción de los datos puede ser supervisado.
OnPacketSent
Se llama al evento OnPacketSent cuando los datos se envían al servidor remoto. Utilizado conjuntamente con la propiedad  BytesSent y la propiedad  BytesTotal, el progreso de una transferencia de datos puede ser supervisado.
OnRead
Se llama al evento OnRead cuando hay datos entrantes que son enviados del servidor remoto. Esto corresponde al mensaje de FD_READ del Winsock.
OnStatus Es llamado cuando surge un cambio en el estado del componente.

 

Vaya...  finalmente termine, como pueden ver es una lista grande de propiedades y eventos, mismos que nos permiten controlar todo lo que sucede con nuestras transacciones en el servidor.

Ahora desarrollaremos nuestro Cliente FTP, Así es que arranca Delphi y crea una nueva aplicación.

Al Caption del Form ponle Cliente FTP . . ., Coloca un Componente CoolBar esta en la ficha Win32, ponlo algo ancho y en su propiedad Align déjala en alTop, dentro del CoolBar coloca dos componentes Panels y sus propiedades align ponlas también en alTop.

Dentro del primer panel pon cuatro SpeedButton´s y su propiedad Flat ponla en True, en orden ponles en su caption lo siguiente: Conectar, Desconectar, Cargar, Descargar. Puedes ponerles alguna imagen alusiva al texto.

En el Segund Panel pon Cuatro componentes Label´s y en orden ponles en su Caption lo siguiente: Host, Usuario, Clave, Puerto. Deja suficiente espacio entre ellas.

Ahora coloca cuatro componentes Edit, quedando el Edit1 para el Host, el Edit2 para el usuario y así sucesivamente. Borrales el contenido en la propiedad Text. Al Edit3 correspondiente al Label Clave en propiedad PasswordChar ponle un asterisco( * ), al Edit4 correspondiente al puerto ponle en su propiedad Text  21.

Coloca un componente Panel, su propiedad Align ponla en alTop, alárgalo de tal forma que ocupe casi todo el formulario, ya que en el pondremos mas componentes. Borra el contenido de Caption.

Coloca dentro del Panel un componente Memo, su propiedad Align ponla en alTop.

Ahora coloca un componente ShellComboBox, se encuentra en la ficha Samples, en su propiedad Root ponle C:\.

 Coloca un componente ShellListView el cual también se encuentra en la ficha Samples, colócalo debajo del ShellComboBox, su propiedad ViewStyle ponla en vsReport, dale click en su propiedad Root ponle c:\  esto con la finalidad de que nos muestre por defecto el contenido de el directorio raíz C. Puedes dejarlo como sale por defecto eso no es tan importante. En la propiedad ShellComboBox elige ShellComboBox1, por medio de esto estaremos ligando estos dos componentes de tal forma que cuando se cambie de ruta cualquiera de los dos, se actualicen automáticamente, etc.

Ahora coloca dos Labels, las cuales dirán Directorios, Archivos, debajo de cada una de ellas pondrás dos componentes ListBox los cuales están en la ficha Standard, quedando el uno para Directorio y el dos para Archivos.

Muy bien, ahora coloca otro componente Listbox, pero ponlo fuera del Panel, en la ultima parte del form que te queda libre, su propiedad Align ponla en alClient.

Coloca un componente StatusBar de la ficha Win32.

Ahora coloca dos componentes PopupMenu, vas crear las siguientes opciones dentro de ellos (Si no sabes como consulta el manual que  puse en este Sitio).

Opciones del PopupMenu1:

Ahora que ya tienes hecho este menú, hay que ligar el PopupMenu1 al ListBox2, así es que ve al ListBox2 y en su propiedad PopupMenu elige PopupMenu1.

Opciones del PopupMenu2:

Ahora que ya tienes hecho este menú, hay que ligar el PopupMenu2 al ListBox1, así es que ve al ListBox1 y en su propiedad PopupMenu elige PopupMenu2.

Puedes llegar a utilizar un solo menú para los dos ListBox, pero en esta ocasión para mayor facilidad la haremos así.

Por ultimo coloca un componente NMFTP1. y listo hemos terminado el diseño de nuestro formulario.

Ahora bien lo mas interesante el código, mismo que te lo listare a continuación:

He desarrollado un procedimiento propio, para actualizar directorios cada que se necesite, así es que debemos de declararlo, si no sabes no te preocupes es fácil.

Ve al código de tu Form, al inicio de tu código quiero que declares el procedimiento procedure Directorios; A continuación te muestro la parte en la que lo clararas así es que solo localizala y listo, esta al inicio de tu Unit.

 procedure Directorios;
procedure ListBox1DblClick(Sender: TObject);
procedure ListBox2Click(Sender: TObject);
procedure ListBox2DblClick(Sender: TObject);
 

Ahora que hemos declarado el procedure, solo falta desarrollarlo, en lo particular me gusta desarrollar los procedures propios antes de cualquier otro hecho por el Delphi. así es que vete a la parte en donde dice:

implementation

{$R *.dfm}
 

Ahora teclea todo el  código:


procedure TForm1.Directorios;
var i: Integer;
begin
Memo1.Lines.Add('Generando lista de Archivos y Directorios . . .');
ListBox1.Items.Clear;
ListBox1.Items.Add( '..' );
ListBox2.Items.Clear;
NMFTP1.ParseList := True;
NMFTP1.Vendor := NMOS_AUTO;
NMFTP1.List;
for i := 0 to NMFTP1.FTPDirectoryList.Name.Count - 1 do
if NMFTP1.FTPDirectoryList.Attribute[i][1] = 'd' then
ListBox1.Items.Add( NMFTP1.FTPDirectoryList.Name[i] )
else
ListBox2.Items.Add( NMFTP1.FTPDirectoryList.Name[i] );
Memo1.Lines.Add('Lista Terminada . . .');
end;

 

Ahora iremos a escribir el código de cada evento de los componentes:

Evento Onclose del Form:

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if NMFTP1.Connected then
NMFTP1.Disconnect;
end;
 

Código del Botón Conectar:

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
NMFTP1.Host := Edit1.Text;
//Asignamos el Host
NMFTP1.Port := StrToInt(Edit4.Text); 
//Se Asgina el Puerto
NMFTP1.Timeout := 5000; 
 //Sedefine el Tiempo de Espera
NMFTP1.UserID := Edit2.Text; 
//Se pone el nombre de usuario
NMFTP1.Password := Edit3.Text; 
 //Se pone la Contraseña
Try
begin
NMFTP1.Connect; 
//Conectamos al servidor
Directorios; 
//Si no sucede ningún error al conectar se genera lista de Archivos y Directorios.
end
Except
Memo1.Lines.Add('Error duarnte la conexion');
// Pega en el memo un mensaje de error.
end;

end;
 

Código del Botón Desconectar:

procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
if NMFTP1.Connected then 
//Si esta conectado se desconecta del servidor.
NMFTP1.Disconnect;
end;
 

Código del Botón Cargar:

procedure TForm1.SpeedButton3Click(Sender: TObject);
begin
if ShellListView1.SelectedFolder.IsFolder = False then
{verificamos que sea un Archivo y no Carpeta.}
begin
Memo1.Lines.Add('Cargando Archivo a Directorio Remoto . . .');
ListBox3.Items.Add('Carga.... ' + ShellListView1.SelectedFolder.PathName);
try
begin
NMFTP1.Upload(ShellListView1.SelectedFolder.PathName,'');
{Esta línea es la que carga el Archivo.}
Directorios; //Despues de la carga se Actualizan los Archivos.
Memo1.Lines.Add('La carga del Archivo ha terminado . . .');
end;
except
Memo1.Lines.Add('Ocurrio un error durante la carga. Intente de Nuevo . . .');
StatusBar1.SimpleText := 'Error en la Carga . . .';
end;
end;
end;

 

Código del Botón Descargar:

Form1.ListBox2DblClick(Sender); {En este caso el código esta en el evento DblClick del ListBox2, y para no escribirlo dos veces solo lo mando llamar}

Evento OnDblClick del ListBox1:

procedure TForm1.ListBox1DblClick(Sender: TObject);
begin
Memo1.Lines.Add('Cambiando al directorio ' + ListBox1.Items[ListBox1.ItemIndex]);
NMFTP1.ChangeDir(ListBox1.Items[ListBox1.ItemIndex] ); //Cambia de rirectorio
Directorios;
end;
 { al hacer Docle Click sobre un directorio debe acceder a el y mostrar su contenido }

Evento OnClick del ListBox2:

procedure TForm1.ListBox2Click(Sender: TObject);
begin
StatusBar1.SimpleText := 'Tamaño: ' +
FormatFloat( '###,###,###,##0',
StrToFloat( NMFTP1.FTPDirectoryList.Size[ListBox2.ItemIndex] ) ) + ' bytes ' + 'Fecha: ' + NMFTP1.FTPDirectoryList.ModifDate[ListBox2.ItemIndex];
end
; { en este evento pegamos en el StatusBar el nombre y atributos del archivo}

Evento OnDblClick del ListBox2:

procedure TForm1.ListBox2DblClick(Sender: TObject);
Var Ruta : String;
begin
if (ListBox2.Count > 0) then //Verifico que no este vacio
begin
Ruta := ShellComboBox1.Path + '\';
ListBox3.Items.Add('Descarga.... ' + ListBox2.Items[ListBox2.ItemIndex]);
Ruta := Ruta + ListBox2.Items[ListBox2.ItemIndex];
NMFTP1.Mode( MODE_IMAGE );
Try
begin
NMFTP1.Download( ListBox2.Items[ListBox2.ItemIndex],Ruta);
//Descarga el Archivo.
Memo1.Lines.Add('Descarga Terminada . . .');
StatusBar1.SimpleText := 'Descarga Terminada . . .';
end
Except
Memo1.Lines.Add('Error duarnte la Descarga . . .');
if Application.MessageBox('Error al descargar, Desea Resumir la descarga ?','DESCARGA',MB_OKCANCEL+MB_ICONQUESTION) = IDOK then
begin
Memo1.Lines.Add('Resumiendo la Descarga Fallida . . .');
NMFTP1.DownloadRestore( ListBox2.Items[ListBox2.ItemIndex],Ruta);
//Resume la Descarga
Memo1.Lines.Add('Descarga Terminada . . .');
StatusBar1.SimpleText := 'Descarga Terminada . . .';
end;
end;
end;
end;

{ En este procedimiento utilizo la variable Ruta en la cual genero la ruta donde se guardara el archivo. Las descargas son susceptibles a errores, es por eso que con el Try y el Except controlo y me doy cuenta si se genero un error, para en caso de que se genere un error resumir la descarga y no tener que empezar desde el principio, claro si es que el servidor soporta los Restore.}

Opción Descargar del PopupMenu1:

procedure TForm1.DEscargar1Click(Sender: TObject);
begin
Form1.ListBox2DblClick(Sender);
end;

 

Opción Renombrar del PopupMenu1:

procedure TForm1.Renombrar1Click(Sender: TObject);
var
Actual, Anterior : String;
begin


if (ListBox2.Count > 0) then
begin
Anterior := ListBox2.Items[ListBox2.ItemIndex];

if InputQuery('Renombrar Archivo', 'Nuebo Nombre ?', Actual) then
begin
NMFTP1.Rename(Anterior,Actual);
Memo1.Lines.Add('Archivo Renombrado . . .');
Directorios;
end;
end;
end;
 

Opción Eliminar del PopupMenu1:

procedure TForm1.Eliminar1Click(Sender: TObject);
var Archivo : String;
begin
if (ListBox2.Count > 0) then
begin
Archivo := ListBox2.Items[ListBox2.ItemIndex];
if Application.MessageBox('Esta seguro de Eliminar el Archivo ?','ELIMINAR',MB_OKCANCEL+MB_ICONQUESTION) = IDOK then
begin
NMFTP1.Delete(Archivo);
Memo1.Lines.Add('Archivo eliminado . . .');
Directorios;
end;
end;
end;

 

Opción Renombrar Directorio del PopupMenu2:

procedure TForm1.RenombrarDirectorio1Click(Sender: TObject);
var Anterior, Actual : String;
begin
if (ListBox1.Count > 1) then
begin
Anterior := ListBox1.Items[ListBox1.ItemIndex];

if InputQuery('Renombrar Directorio', 'Nuebo Nombre ?', Actual) then
begin
NMFTP1.Rename(Anterior,Actual);
Memo1.Lines.Add('Directorio Renombrado . . .');
Directorios;
end;
end;

end;

 

Opción Eliminar Directorio del PopupMenu2:

procedure TForm1.EliminarDirectorio1Click(Sender: TObject);
Var Dir : String;
begin
if (ListBox1.Count > 0) then
begin
Dir := ListBox1.Items[ListBox1.ItemIndex];
if Application.MessageBox('Esta seguro de Eliminar el Directorio ?','ELIMINAR',MB_OKCANCEL+MB_ICONQUESTION) = IDOK then
begin
NMFTP1.RemoveDir(Dir);
Memo1.Lines.Add('Directorio Eliminado . . .');
Directorios;
End;
end;
end;

 

Opción Crear Directorio del PopupMenu2:

procedure TForm1.CrearDirectorio1Click(Sender: TObject);
var Dir : String;
begin
if NMFTP1.Connected then
begin
if InputQuery('Crear Directorio', 'Nombre Directorio ?',Dir) then
begin
NMFTP1.MakeDirectory(Dir);
Memo1.Lines.Add('Directorio Creado . . .');
Directorios;
end;

end;
end;

 

Evento OnAuthenticationFailed del NMFTP1:

procedure TForm1.NMFTP1AuthenticationFailed(var Handled: Boolean);
begin
Memo1.Lines.Add('Pasword Incorrecto, Intente de nuevo');
end;

 

Evento OnConnect del NMFTP1:

procedure TForm1.NMFTP1Connect(Sender: TObject);
begin
Memo1.Lines.Add('Conexion establecida');
Directorios;
end;
 

Evento OnConnectionFailed del NMFTP1:

procedure TForm1.NMFTP1ConnectionFailed(Sender: TObject);
begin
Memo1.Lines.Add('No se puede conectar con el Sitio.');
end;
 

Evento OnDisconnet del  NMFTP1:

procedure TForm1.NMFTP1Disconnect(Sender: TObject);
begin
Memo1.Lines.Add('Desconectado del Sitio . . .')
end;

 

Evento OnHostResolved del NMFTP1:

procedure TForm1.NMFTP1HostResolved(Sender: TComponent);
begin
Memo1.Lines.Add('EL Host esta atendiendo la petición . . .');
end;
 

Evento OnPacketRecvd del NMFTP1:

procedure TForm1.NMFTP1PacketRecvd(Sender: TObject);
begin
StatusBar1.SimpleText := 'Descargando: ' + ListBox2.Items[ListBox2.ItemIndex]+ ' ' + IntToStr(NMFTP1.BytesRecvd)+' of '+IntToStr(NMFTP1.BytesTotal);

end; //se genera en Status Bar el progreso.

EventoOnPacketSent del NMFTP1:

procedure TForm1.NMFTP1PacketSent(Sender: TObject);
begin
StatusBar1.SimpleText := 'Cargando: ' + ShellListView1.SelectedFolder.PathName +
' ' + IntToStr(NMFTP1.BytesRecvd) + ' of '+IntToStr(NMFTP1.BytesTotal);
end;

 

Con esto hemos concluido de construir nuestro Cliente FTP. Si te das cuenta con solo un poco mas de cosas quedaría perfectamente bien, ya que lo único que le haría falta a parte de mas validaciones seria que te permitiera copiar las carpetas enteras y con múltiples archivos.


Bueno como ya se esta haciendo tradición en mi sitio, les dejo el código fuente del Cliente FTP. Si tienes algún comentario bueno o malo házmelo saber, recuerda que mi intención solo es ayudar y conociendo sus opiniones lo podré hacer mucho mejor.

CÓDIGO FUENTE DEL CLIENTE FTP
 

Si tienes algún comentario o deseas compartir alguna información da Click Aquí.....