Mi problema y deseo: uno de mis tantos dilemas en una aplicación de la cual gracias a egostar se saco como un tema aparte http://www.delphiacc...as-con-raudus/ , surgió mi idea de aprovechar las bondades de DataSnap, en principio el proyecto relacionado al link anterior se divide en 3 partes, un servidor de datos DataSnap REST, un servidor DataSnap de calificación y un cliente que consume los dos servidores DataSnap (Datos y calificación).
Hoy en día que ya he terminado dicho proyecto , me surgió la idea de permitir la actualización automática de los archivos del cliente (.exe) por medio de la consulta y comparación de versión de archivos entre el cliente y una carpeta fija en la maquina servidores donde se localiza el Servidor DataSnap de datos.. Intente utilizando Copyfile, CopyfileEx, pero existía el problemas que entre pc me colocaban problema, por un lado que la carpeta no estaba compartida, que la ruta no era valida, eso toco palié como gato boca arriba y nada, hasta que me acorde del webinar al que había asistido vía online, referente al uso de callback (dictado por el amigo andreano lanusse), es mas tengo implementado callback entre mi aplicación Servidora y la Cliente (esta de tipo RAUDUS), en dicho webinar se mostraba el ejemplo de paso de un archivo de imagen vía callback a un servidor, el cliente tenia una progressbar para indicar el progreso del procesamiento de la imagen, esta imagen es recibida por el servidor quien la invierte (pone boca abajo) y la retorna al cliente. El problema de los diversos ejemplos de pasos de archivos es que siempre son imágenes o texto, pero archivos .exe o .dll..
Bueno lo que hice fue darme a la tarea de analizar como era el paso de archivos en dicho ejemplo y ver como adaptarlo para que me sirviera para pasar entre pc archivos de cualquier tipo, en mi caso dll y exe, por medio de la verificación de versión. Así de ese modo para actualizar cada cliente con la ultima versión de cliente y servidor de calificación solo bastaría con colocar los .exe del caso en la carpeta /updates del servidor y listo.
Como Funciona:
1. El cliente finaliza los procesos cliente y calificador
2. Envía nombre de archivo que se desea verificar y versión del mismo al servidor
3. Si existe una versión superior a la verificada en el servidor, este retorna el tamaño en bytes de la nueva versión del archivo.
4. En caso que las versiones no sean la misma retorna el valor de 0 y continua con la verificación del siguiente archivo configurado para verificar.
5. El cliente recibe el tamaño en bytes del archivo a actualizar y hace el llamado via callback hacia el servidor pasándole como parámetro el nombre del archivo por actualizar, versión y un argumento de tipo TDBXCallback (sin este parámetro el servidor no puede conversar con el cliente)
6. En el servidor de datos se lee carga una variable de tipo TMemoryStream con el contenido del archivo por actualizar haciendo un loadfromfile().
7. Unas ves cargado el archivo se inicia el paso por partes hacia el cliente quien lo recibe y lo va almacenando en una variable de tipo TStream, y al tiempo se actualiza la barra de progreso indicado el estado de la actualización. (cabe anotar que desde el cliente se hace el paso de una función de tipo abstracta, que es la encargada de la actualización de la barra de progreso)
8. Al terminar la transaferencia de datos, en el servidor se destruyen las variables creadas y que ocupan memoria y se hacen nulas.
9. En el cliente el archivo recibido desde el servidor queda inicialmente cargado en una variable de tipo TStream, y para guardarlo como archivo en la maquina local se le es asignado a una segunda variable de tipo TMemoryStream haciendo un loadfromStream(), y luego un savetofile () .. y listo.. ya tenemos nuestro archivo actualiza.. ahora pasemos al código fuente..
SERVIDOR DE DATOS : En la parte del public de la unidad ServerMethods1, agregue dos funciones.
delphi
public { Public declarations } function EchoString(Value: string): string; function ReverseString(Value: string): string; //Verifica version de archivo indicado y retorna version del archivo localizada en el servidor function RetUpdate_Disponible(Args:tdbxcallback ;FileName,Version: string):TStream; //Retonar el tamaño en byte del archivo indicado desde el cliente y si existe una nueva version retorna el tamaño en bytes function newupdate_size(FileName,Version:string):Int64;//de la nueva version procedure TratarExcepciones(DataSet: TDataSet; e:Exception;tabla:string); end;
Cuerpo de las funciones declaradas
delphi
function TServerMethods1.newupdate_size(FileName, Version: string): Int64; var Ruta: String; NewStream:TMemoryStream; begin Ruta:=GetAppVersion(FileName,Version); if Ruta<>'' then try NewStream:=TMemoryStream.Create; NewStream.LoadFromFile(Ruta); Result:=NewStream.Size; except FreeAndNil(NewStream); Result:=0; exit; end; end; function TServerMethods1.RetUpdate_Disponible(Args: tdbxcallback; FileName, Version: string): TStream; begin Result:=verifica_version(args,FileName,Version); end;
En una unidad a la que llame Ufunciones tengo la declaracion de la funcion Verifica_version().
delphi
unit UFunciones Uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls,IniFiles,WedModuleServer, Registry,DBXJSON; //CONSTANTES //PROCEDIMIENTOS //FUNCIONES function GetAppVersion(FileExeName,Version:string):string; function verifica_version(Args:TDBXCallback;FileName,Version:string):TStream; //VARIABLES GLOBALES implementation //***************************************************************** // Función que retorna la ruta del archivo que debe ser enviado al cliente dado su // nombre y version, si y solo si la version ingresada es menor que la version del // mismo archivo ubicado en la carpeta update del servidor //***************************************************************** function GetAppVersion(FileExeName,Version:string):string; var Size, Size2: DWord; Pt, Pt2: Pointer; VerUpdate:string; begin if not FileExists('.\Updates\'+FileExeName) then begin result:=''; exit; end; Size := GetFileVersionInfoSize(PChar ('.\Updates\'+FileExeName), Size2); if Size > 0 then begin GetMem (Pt, Size); try GetFileVersionInfo (PChar ('.\Updates\'+FileExeName), 0, Size, Pt); VerQueryValue (Pt, '\', Pt2, Size2); with TVSFixedFileInfo (Pt2^) do begin VerUpdate:= IntToStr (HiWord (dwFileVersionMS)) + '.' + IntToStr (LoWord (dwFileVersionMS)) + '.' + IntToStr (HiWord (dwFileVersionLS)) + '.' + IntToStr (LoWord (dwFileVersionLS)); if VerUpdate>Version then result:='.\Updates\'+FileExeName else result:=''; end; finally FreeMem (Pt); end; end; end; //***************************************************************** //***************************************************************** // Función callback que retorna la nueva version archivo solicitado, si y solo si la // version recibida es menor que la del mismo archivo ubicado en la carpeta /update // del servidor //***************************************************************** function verifica_version(Args:TDBXCallback;FileName,Version:string):TStream; const MaxBufSize = 25100; var Ruta: String; progress:TJSONArray; clientInstruction:TJSONObject; doAbort:Boolean; Buffer:PByte; inputstream:TMemoryStream; BytesRead:Integer; begin try Ruta:=GetAppVersion(FileName,Version);//solicitar verificar si hay una version mas reciente if Ruta<>'' then //si se obtiene como respuesta una ruta [dirve]:\[ruta] begin// entonces existe una nueva version Doabort:=false;//esta varibale se utiliza para indicar si se debe abortar el proceso de update Result:=TMemoryStream.Create;//se asigna el valor a retornar como tipo memorystream inputstream:=TMemoryStream.Create;//a esta variable se le asignara el archivo localizado en el servidor try inputstream.LoadFromFile(Ruta);//carga variable con datos del archivo a transmitir except Showmessage(' Oops.. couldn''t read file!' + #13+ SysErrorMessage(GetLastError)); exit; end; GetMem(Buffer,MaxBufSize);//se reserva un buffer memoria de tamaño maxbufsize (este valor es una constante, entre mas repeat// pequeño el valor mas lenta es la carga de la barra de progreso en el cliente) BytesRead:=inputstream.read(Buffer^,MaxBufSize);//se lee la cantidad de byte a enviar //callback para reportar el progreso del envío de datos progress:=TJSONArray.Create; progress.AddElement(TJSONString.Create('Sending...')); progress.AddElement(TJSONNumber.Create(BytesRead)); clientInstruction:=args.Execute(progress)as TJSONObject; try if clientInstruction.Get(0).JsonString.Value='Abort' then begin if clientInstruction.Get(0).JsonString.ToString='true' then begin doabort:=true; break; end; end; finally clientInstruction.Free; end; until bytesread <maxbufsize;//se ejecuta mientras existan bytes por enviar if not doAbort then//si no se aborto la operacion desde el cliente begin // se informa al cliente que el proceso de envio a termado progress:=TJSONArray.Create; progress.AddElement(TJSONString.Create('Processing...')); progress.AddElement(TJSONNumber.Create(0)); //todo objeto de tipo TDBXCallback tiene un evento [u]Execute[/u], el cual le permite comunicarse con el cliente. Args.Execute(progress).Free; //archivo fue completamente recibido inputstream.Position:=0; inputstream.SaveToStream(result);//se asigna el archivo en memoria al stream resultado Result.Seek(0,tseekorigin.soBeginning);//y es enviado al cliente Result.Position:=0; end; end; finally end; end;
CLIENTE ENCARGADO DE LA DESCARGA DE VERSIONES
delphi
unit uFrmActiver; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls, UFunciones, DBXDataSnap, DBXCommon, DSHTTPLayer, DB, SqlExpr, USvrServer, UDatamodulo, ExtCtrls, JvComponentBase, JvTrayIcon, JvBaseDlg, JvWinDialogs, ImgList, Menus, AppEvnts, ButtonComps, Mask,IniFiles, JvExMask, JvSpin, AdvGlassButton, AdvGroupBox, AdvSmoothButton, jpeg,zlib,DBXJSON, dbxdbreaders; type TupdateBar = reference to function (labelname:string; percentcomplete:integer):Boolean; TProgressBarCallback = class(TDBXCallback) private updatemethod:tupdateBar; Public constructor create(LupdateMethod:tupdateBar); function execute (const Args: Tjsonvalue):tjsonvalue;override; end; TFrmActiver = class(TForm) Edit1: TEdit; InvokeButton: TAdvGlassButton; BTAbort: TAdvGlassButton; PB1: TProgressBar; LBMsg: TLabel; LBStatus: TLabel; procedure BTAbortClick(Sender: TObject); procedure InvokeButtonClick(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } IsProcessing, DoAbort:Boolean; function CopyLargeStream(source:tstream):tstream; public { Public declarations } constructor Create(AOwner: TComponent); override; destructor Destroy; override; end; var FrmActiver: TFrmActiver; implementation {$R *.dfm} function TFrmActiver.CopyLargeStream(source: tstream): tstream; var BytesRead:integer; Buffer:PByte; Const MaxBufSize =$F000; begin try Result := TMemoryStream.Create; Source.Seek(0, TSeekOrigin.soBeginning); Source.Position := 0; GetMem(Buffer, MaxBufSize); repeat BytesRead := Source.Read(Buffer^, MaxBufSize); if BytesRead > 0 then Result.WriteBuffer(Buffer^, BytesRead); until BytesRead < MaxBufSize; Result.Seek(0, TSeekOrigin.soBeginning); finally end; end; procedure TFrmActiver.FormCreate(Sender: TObject); begin inherited; PB1.Min := 0; PB1.Max := 100; end; constructor TfrmActiver.Create(AOwner: TComponent); var Wpuerto,CPuerto,WHost,CHost :string; begin inherited; if Myinifile<>nil then begin Edit_DNsServer.text:=MyIniFile.ReadString('CONFIGSERV','DNS',DNSlocal); Edit_HTTPServer.text:=MyIniFile.ReadString('CONFIGSERV','HTTP','10010'); end; with Conex do begin if SERVERCONEX.Connected then splay.BackGroundSymbol:=bsPause else splay.BackGroundSymbol:=bsPlay; Edit_DNsServer.Enabled:=not SERVERCONEX.Connected; Edit_HTTPServer.Enabled:=not SERVERCONEX.Connected; Btn_DNsServer.Enabled:=not SERVERCONEX.Connected; InvokeButton.Enabled:=(Conex.SERVERCONEX.Connected); end; try PonerProgramaInicio; except begin application.MessageBox('La aplicacion no puede ser activada para iniciar automáticamente con Windows. Verifique si tiene permisos para escribir en el registro del sistema.','Activar Inicio con Windows',MB_ICONEXCLAMATION); exit; end; end; end; destructor TFrmActiver.Destroy; begin inherited; end; procedure TFrmActiver.InvokeButtonClick(Sender: TObject); var FileName,version:string; SQLServerMethod1:TSqlServerMethod; Proxyclient:TServerMethods1Client; UpdateProc:TUpdateBar; MyFileStream:TMemoryStream; ProccessedStream, TmpStream: TStream; NewFileSize,BytesSend:int64; begin inherited; KillTask('DQ Cliente.exe');//cerrar proceso KillTask('DQ Calificador.exe');//cerrar proceso //guardar parametros de conexion al servidor if Myinifile=nil then MyIniFile := TiniFile.Create('.\DBConex.params'); MyIniFile.WriteString('CONFIGSERV','DNS',Edit_DNsServer.Text); MyIniFile.WriteString('CONFIGSERV','HTTP',Edit_HTTPServer.Text); if Conex.SERVERCONEX.Connected then// si hay conexion begin FileName:='DQ Calificador.exe'; version:=GetAppVersion('.\DQ Calificador.exe');//obtener version //declaracion de funcion abstracta que se pasara por callaback al servidor. //Esta funcion se dispara en respuesta a los datos recibidos desde el servidor //indicando el estado del progreso UpdateProc := Function(l:string; a:Integer):boolean begin LBStatus.caption:=l; if l = 'Processing...' then BTAbort.enabled:=false else begin BTAbort.Enabled:=true; BytesSend:=BytesSend+a; PB1.Position:=(Trunc(100*(BytesSend/NewFileSize))); end; Application.ProcessMessages; result:=DoAbort; end; Proxyclient:=nil; MyFileStream:=TMemoryStream.Create; proccessedStream:=nil; try IsProcessing:=true; InvokeButton.Enabled:=false; DoAbort:=false; BytesSend:=0; PB1.Position:=0; Application.ProcessMessages; MyFileStream.LoadFromFile(Edit1.Text); if not DoAbort then begin Proxyclient:=TServerMethods1Client.Create(Conex.SERVERCONEX.DBXConnection); NewFileSize:=Proxyclient.newupdate_size(FileName,version); Assert(NewFileSize > 0, 'file size error'); LBMsg.caption:=''; LBStatus.caption:='Sending...'; ProccessedStream:=copylargestream(Proxyclient.retUpdate_Disponible(tprogressBarCallback.create(UpdateProc),FileName,version)); LBStatus.Caption:='Loading File...'; Application.ProcessMessages; end; if not DoAbort then begin myfilestream.LoadFromStream(ProccessedStream); myfilestream.SaveToFile('.\DQ Calificador.exe'); end; finally LBStatus.Caption := 'IDLE'; IsProcessing := False; InvokeButton.Enabled := True; if DoAbort then begin LBMsg.Caption := 'Process aborted'; LBMsg.Font.Color := clFuchsia end; if Assigned(ProccessedStream) then FreeAndNil(ProccessedStream); if Assigned(MyFileStream) then FreeAndNil(MyFileStream); if Assigned(ProxyClient) then ProxyClient.Free; end; end; if FileExists('DQ Calificador.exe') then WinExec(PansiChar('DQ Calificador.exe'),SW_SHOWNORMAL); end; procedure TFrmActiver.BTAbortClick(Sender: TObject); begin inherited; if IsProcessing then begin DoAbort := True; LBStatus.Caption := 'Aborting...'; Application.ProcessMessages; end; InvokeButton.Enabled := True; end;
Y listo... Quedo atento a sus dudas y preguntas anexo imagen de la ventana de la aplicación que se encarga de solicitar actualizacion al servidor.

Nota: Solo me hace falta el maquillaje e incluir parámetros de configuración como por ejemplo, cada cuando tiempo verifica si hay una nueva versión y cosas asi.
, gracias por tomarse el tiempo de leer este tema..

