Fórum Live update na prática #340939
25/04/2007
0
Há um certo tempo conversamos sobre algumas técnicas de como atualizar automaticamente o sistema usando um programa que faz o download da aplicação on-line. Bem, deu um pequeno problema na base de dados do fórum e perdemos a referência. Estou postando de novo pra gente voltar a discutir, pois embora esteja funcionando legal, quero compartilhar a idéia com todos e aprimorar se necessário. Ai vai:
[list:a6cbf17708]
[*:a6cbf17708]A imagem do aplicativo é a seguinte:
[URL=http://img77.imageshack.us/my.php?image=imagemgp8.jpg][img:a6cbf17708]http://img77.imageshack.us/img77/8798/imagemgp8.th.jpg[/img:a6cbf17708][/URL]
[*:a6cbf17708]Eu coloquei um image na parte superior pra mostrar o logotipo da empresa. No centro um componente do tipo TListView onde aparecerão os módulos necessários pra atualização. Depois alguns labels pra mostrar o progresso e a taxa de transferência de download. 1 TGauge, 1 TTimer e 1 TidFtp.
[*:a6cbf17708] Nas seções [b:a6cbf17708]public [/b:a6cbf17708]e [b:a6cbf17708]private[/b:a6cbf17708] eu declarei as variáveis, funções e procedimentos conforme abaixo:
...
const
gsArqRemoto = ´Atualizacoes.amk´;
gsArqLocal = ´Versoes.amk´;
...
type
...
private
procedure ApagarTemporarios;
procedure LimparPastaTemp;
procedure AtualizarINIVersoes(ASecao, ANomeVersao, AVersao: string);
{ Private declarations }
public
{ Public declarations }
gsPastaRemota : string;
Tamanho_Arquivo: LongWord;
STime: TDateTime;
Tempo_Medio: Double;
Bytes_Transf: Double;
ModuloEmAtualizacao: string;
gsPastaLocal: string;
sArqLocal: string;
sArqRemota: string;
Modulo: string;
Titulo: string;
ModulosAbertos: TStringList;
IniLocal: TIniFile;
IniRemota: TIniFile;
VersaoLocal: string;
VersaoRemota: string;
kbTotalDown: LongWord;
procedure CriaArquivoVersoes;
procedure PreencherModulosAbertos;
procedure FecharModulosAbertos;
function Conectar: Boolean;
procedure LimpaLabels;
procedure IncluiDownloads(AModulo, AVersaoAtual, AVersaoRemota: string;
ATamanho: LongWord);
procedure PosAtualizacao;
end;
[*:a6cbf17708] A brincadeira é o seguinte: No diretório da aplicação mantenho um arquivo chamado Versoes.amk que contém a seguinte estrutura:
[Atualizacoes] Modulo1=Operacao.exe Versao1=vF6.1.04 Titulo1=Operacional Modulo2=Cobranca.exe Versao2=vF6.1.04 Titulo2=Faturamento/Cobrança Modulo3=Comunica.exe Versao3=vF6.1.04 Titulo3=Integração entre Filiais
Note que é um arquivo .INI renomeado para uma extensão qualquer. Declarei duas constantes pra dizer que o arquivo local contendo a informação de versão chama-se Versoes.amk e outra constante para dizer qual é o nome do arquivo que contem a mesma estrutura, mas que está no servidor FTP.
[*:a6cbf17708] Quando o meu usuário entra nesta ferramenta eu faço download do arquivo Atualizacoes.amk e saio comparando os dois arquivos e incluindo os programas que precisam ser atualizados em um listview, vejam:
procedure TfAtualiza.Timer1Timer(Sender: TObject);
var
I: Integer;
begin
try
Screen.Cursor := crHourGlass;
kbTotalDown := 0;
Sleep(3000);
lblListandoAtalizacoes.Caption := ´Verificando se há atualizações disponíveis´;
lblListandoAtalizacoes.Font.Color := clBlue;
Update;
with idFtpAtualiza do
begin
{Tenta conectar-se ao servidor FTP da América}
if Conectar then
begin
try
IniLocal := TIniFile.Create(ExtractFilePath(Application.ExeName) + gsArqLocal);
IniRemota := TIniFile.Create(ExtractFilePath(Application.ExeName) + gsArqRemoto);
lstModulos.Items.Clear;
{Faz o download do arquivo Remoto.ini pra comparar as versões}
Get(sArqRemota, sArqLocal, True);
{Faz as comparações necessárias e atualiza a lista de atualizações pendentes}
for I := 1 to 50 do
begin
{Verifica se a chave existe, ou seja, se o "I" atual corresponde à algum módulo no arquivo. local.ini}
if not IniRemota.ValueExists(´Atualizacoes´, ´Modulo´ + IntToStr(I)) then
Break;
{Nome e tamanho do Módulo}
Modulo := IniRemota.ReadString(´Atualizacoes´, ´Modulo´ + IntToStr(I), Modulo);
ModuloEmAtualizacao := Modulo;
Tamanho_Arquivo := Size(gsPastaRemota + ´/´ + Modulo);
VersaoLocal := IniLocal.ReadString(´Atualizacoes´, ´Versao´ + IntToStr(I), VersaoLocal);
VersaoRemota := IniRemota.ReadString(´Atualizacoes´, ´Versao´ + IntToStr(I), VersaoRemota);
{Verifica se há atualizações e então atualiza a lista de downloads}
if (VersaoLocal <> VersaoRemota) or not
(IniLocal.ValueExists(´Atualizacoes´, ´Modulo´ + IntToStr(I))) then
begin
IncluiDownloads(ModuloEmAtualizacao, VersaoLocal, VersaoRemota, Tamanho_Arquivo);
kbTotalDown := kbTotalDown + Tamanho_Arquivo;
end;
end;
idFtpAtualiza.Disconnect;
IniLocal.Free;
IniRemota.Free;
if lstModulos.Items.Count > 0 then
begin
lstModulos.Visible := True;
btnAtualizar.Enabled := True;
lblTotalBytes.Caption := ´Total de bytes à baixar: ´ + FormatFloat(´#,,0´, kbTotalDown) + ´ Kb´;
end
else
begin
lstModulos.Visible := False;
lblListandoAtalizacoes.Caption := ´Não há atualizações disponíveis´;
lblListandoAtalizacoes.Font.Color := clRed;
DeleteFile(gsPastaLocal + gsArqRemoto);
end;
except on E: Exception do
begin
MessageDlg(´Ocorreu um erro durante o processo.´ + 1313 + ´Mensagem original: ´ + 13 +
E.Message + 1313 + ´O aplicativo será fechado.´, mtError, [mbOK], 0);
Halt;
end;
end;
end;
end;
finally
Screen.Cursor := crDefault;
Timer1.Enabled := False;
end;
end;
Se não houverem atualizações disponíveis simplesmente deleto o arquivo Atualizacoes.amk e informo a mensagem de que não há necessidade de atualizar nada.
[*:a6cbf17708] Se houver atualizações disponíveis, basta o usuário clicar em [b:a6cbf17708]Atualizar agora[/b:a6cbf17708] que o sistema vai fazer o dowload de cada módulo:
procedure TfAtualiza.btnAtualizarClick(Sender: TObject);
var
I: Integer;
iContaAtualizacao: Integer;
begin
try
Screen.Cursor := crHourGlass;
{Tenta conectar-se ao servidor FTP da América}
if Conectar then
begin
with idFtpAtualiza do
begin
IniLocal := TIniFile.Create(ExtractFilePath(Application.ExeName) + gsArqLocal);
IniRemota := TIniFile.Create(ExtractFilePath(Application.ExeName) + gsArqRemoto);
{Re-ativa os eventos do TidFtp}
OnWorkBegin := idFtpAtualizaWorkBegin;
OnWork := idFtpAtualizaWork;
try
btnAtualizar.Enabled := False;
btnFechar.Enabled := False;
Tamanho_Arquivo := 1;
{Aqui verifica se existem módulos abertos. Caso haja o usuário será alertado e os mesmos fechados na confirmação dele}
PreencherModulosAbertos;
if ModulosAbertos.Count > 0 then
begin
if MessageBox(Handle, ´Um ou mais módulos do sistema América encontram-se em execução.´ + #13 +
´Deseja fechá-los e continuar a atualização do sistema?´, ´Alerta´, MB_ICONQUESTION + MB_YESNO) = ID_YES then
FecharModulosAbertos
else
begin
btnAtualizar.Enabled := True;
btnFechar.Enabled := True;
Tamanho_Arquivo := 1;
Exit;
end;
end;
IniLocal := TIniFile.Create(ExtractFilePath(Application.ExeName) + gsArqLocal);
IniRemota := TIniFile.Create(ExtractFilePath(Application.ExeName) + gsArqRemoto);
{Aqui verifica se há atualizações e baixa o módulo}
iContaAtualizacao := 0;
for I := 1 to 20 do
begin
{Verifica se a chave existe, ou seja, se o "I" atual corresponde à algum módulo no arquivo. local.ini}
if not IniRemota.ValueExists(´Atualizacoes´, ´Modulo´ + IntToStr(I)) then
Break;
{Nome e tamanho do Módulo}
Modulo := IniRemota.ReadString(´Atualizacoes´, ´Modulo´ + IntToStr(I), Modulo);
ModuloEmAtualizacao := Modulo;
Tamanho_Arquivo := Size(gsPastaRemota + ´/´ + Modulo);
VersaoLocal := IniLocal.ReadString(´Atualizacoes´, ´Versao´ + IntToStr(I), VersaoLocal);
VersaoRemota := IniRemota.ReadString(´Atualizacoes´, ´Versao´ + IntToStr(I), VersaoRemota);
{Efetua o download}
if (VersaoLocal <> VersaoRemota) or not
(IniLocal.ValueExists(´Atualizacoes´, ´Modulo´ + IntToStr(I))) then
begin
Inc(iContaAtualizacao);
sArqRemota := gsPastaRemota + ´/´ + Modulo;
sArqLocal := gsPastaLocal + ´\´ + Modulo;
Tamanho_Arquivo := Size(sArqRemota);
{Aqui verifica se o módulo está em execução.}
{Em caso positivo avisa que não será possível fazer o download}
Get(sArqRemota, sArqLocal, True);
lstModulos.Items[iContaAtualizacao - 1].Checked := True;
AtualizarINIVersoes(´Atualizacoes´, ´Versao´ + IntToStr(I), VersaoRemota);
end;
end;
Disconnect;
IniLocal.Free;
IniRemota.Free;
{Função desativada - Atualizando a cada download}
//DeleteFile(gsPastaLocal + gsArqLocal);
//RenameFile(gsPastaLocal + gsArqRemoto, gsPastaLocal + gsArqLocal);
DeleteFile(gsPastaLocal + gsArqRemoto);
lblListandoAtalizacoes.Caption := ´Todos os módulos listados foram atualizados com sucesso´;
lblListandoAtalizacoes.Font.Color := clBlue;
btnAtualizar.Enabled := False;
btnFechar.Enabled := True;
LimpaLabels;
PosAtualizacao;
except on E: Exception do
begin
MessageDlg(´Ocorreu um erro durante o processo.´ + #1313 +
E.Message, mtError, [mbOK], 0);
Halt;
end;
end;
end;
end;
finally
Screen.Cursor := crDefault;
end;
end;
Antes de fazer o download eu verifico se existem módulos abertos. Faço isso procurando a referência da janela no Windows usando o FindWindow. Adiciono em uma TStringlist os módulos abertos e pergunto pro usuário se ele quer que o sistema mesmo feche ou se ele vai fechar manualmente.
A terceira chave de cada módulo é o título da janela do aplicativo. Aquela opção que preenchemos no Project >> Options. É por ai que sei se o módulo está ou não aberto. Daí preencho o TStringList:
procedure TfAtualiza.PreencherModulosAbertos;
var
I: Integer;
begin
IniRemota := TIniFile.Create(ExtractFilePath(Application.ExeName) + gsArqRemoto);
ModulosAbertos.Clear;
for I := 1 to 20 do
begin
{Verifica se a chave existe, ou seja, se o "I" atual corresponde à algum módulo no arquivo. local.ini}
if not IniRemota.ValueExists(´Atualizacoes´, ´Modulo´ + IntToStr(I)) then
Break;
{Nome e tamanho do Módulo}
Titulo := IniRemota.ReadString(´Atualizacoes´, ´Titulo´ + IntToStr(I), Titulo);
if FindWindow(nil, PChar(Titulo)) > 0 then
ModulosAbertos.Add(Titulo);
end;
IniRemota.Free;
end;
Se o usuário quiser que o sistema feche os módulos então rodo a função:
procedure TfAtualiza.FecharModulosAbertos; var I: Integer; begin IniRemota := TIniFile.Create(ExtractFilePath(Application.ExeName) + gsArqRemoto); for I := 0 to ModulosAbertos.Count - 1 do if FindWindow(nil, PChar(ModulosAbertos[I])) > 0 then PostMessage(FindWindow(nil, PChar(ModulosAbertos[I])), WM_CLOSE, 0, 0); IniRemota.Free; end;
[*:a6cbf17708] Pra fazer o download primeiro conectamos no ftp e depois começamos o download de cada arquivo. Um detalhe, temos problemas com nosso atual servidor de FTP, então armazemos os arquivos em dois servidores. No atual e no disco virtual do Terra. O sistema é esperto, ele tenta conexão em um e se não conseguir redireciona para o Terra.
function TfAtualiza.Conectar: Boolean;
begin
try
with idFtpAtualiza do
begin
gsPastaRemota := ´/Executaveis/Atualizacoes´;
gsPastaLocal := ExtractFilePath(Application.ExeName);
sArqRemota := gsPastaRemota + ´/´ + gsArqRemoto;
sArqLocal := gsPastaLocal + gsArqRemoto;
Host := ´ftp.MEU-FTP.com.br´;
Username := ´meu-usuario;
Password := ´minha-senha´;
Passive := True;
if not Connected then
Connect;
{Desativa temporariamente os eventos do idFTP pra não mostrar o download do arquivo texto}
OnWorkBegin := nil;
OnWork := nil;
try
ChangeDir(gsPastaRemota);
except
Timer1.Enabled := False;
Halt;
end;
List(nil);
Result := True;
end;
except
with idFtpAtualiza do
begin
gsPastaRemota := ´/´;
gsPastaLocal := ExtractFilePath(Application.ExeName);
sArqRemota := gsPastaRemota + ´/´ + gsArqRemoto;
sArqLocal := gsPastaLocal + gsArqRemoto;
lblListandoAtalizacoes.Caption := ´Tentando conexão com servidor alternativo...´;
Host := ´ftp.discovirtual.terra.com.br´;
Username := ´meu-usuario´;
Password := ´minha-senha´;
Passive := True;
if not Connected then
Connect;
{Desativa temporariamente os eventos do idFTP pra não mostrar o download do arquivo texto}
OnWorkBegin := nil;
OnWork := nil;
try
ChangeDir(gsPastaRemota);
except
Timer1.Enabled := False;
Halt;
end;
List(nil);
Result := True;
end;
//Result := False;
end;
end;
[*:a6cbf17708] Pra atualizar os labels e tudo mais eu escrevo nos eventos onWork e onWorkBegin do TidFtp. Os eventos estão abaixo:
procedure TfAtualiza.idFtpAtualizaWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Integer); var Contador, kbTotal, kbTransmitidos, kbFaltantes: Integer; Status_transf: string; TotalTempo: TDateTime; H, M, Sec, MS: Word; DLTime, Media: Double; begin btnAtualizar.Enabled := False; btnFechar.Enabled := False; kbTotal := Tamanho_Arquivo div 1024; TotalTempo := Now - STime; DecodeTime(TotalTempo, H, M, Sec, MS); Sec := Sec + M * 60 + H * 3600; DLTime := Sec + MS / 1000; KbTransmitidos := AWorkCount div 1024; kbFaltantes := kbTotal - kbTransmitidos; lblContador.Caption := ´Transmitidos: ´ + FormatFloat(´#,,0´, kbTransmitidos) + ´ Kb de ´ + FormatFloat(´,,0´, kbTotal) + ´ Kb´ + ´; Restam: ´ + FormatFloat(´,,0´, kbFaltantes) + ´ Kb´; Media := (100 / Tamanho_Arquivo) * AWorkCount; if DLTime > 0 then begin Tempo_Medio := (AWorkCount / 1024) / DLTime; Status_Transf := Format(´¬2d:¬2d:¬2d:´, [Sec div 3600, (Sec div 60) mod 60, Sec mod 60]); Status_Transf := ´Tempo de download ´ + Status_Transf; end; Status_Transf := ´Taxa de tranferência: ´ + FormatFloat(´0.00 Kb/s´, Tempo_Medio) + ´; ´ + Status_Transf; lblStatus.Caption := Status_Transf; lblNomeModulo.Caption := ModuloEmAtualizacao; Application.ProcessMessages; Contador := Trunc(Media); Gauge1.Progress := (contador); end; procedure TfAtualiza.idFtpAtualizaWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Integer); begin STime := Now; Tempo_Medio := 0; pnlNomeModulo.Visible := True; Update; end;
[*:a6cbf17708] O resto são funções pra ajudar em outras coisas. Por exemplo: Limpo as pastas temporárias do Windows, limpo os labels da tela e crio o arquivo Versoes.amk se for a primeira vez que o cara abre o sistema.
procedure TfAtualiza.ApagarTemporarios; var lpEntryInfo: PInternetCacheEntryInfo; hCacheDir: LongWord; dwEntrySize: LongWord; begin dwEntrySize := 0; FindFirstUrlCacheEntry(nil, TInternetCacheEntryInfo(nil^), dwEntrySize); GetMem(lpEntryInfo, dwEntrySize); if dwEntrySize > 0 then lpEntryInfo^.dwStructSize := dwEntrySize; hCacheDir := FindFirstUrlCacheEntry(nil, lpEntryInfo^, dwEntrySize); if hCacheDir <> 0 then begin repeat DeleteUrlCacheEntry(lpEntryInfo^.lpszSourceUrlName); FreeMem(lpEntryInfo, dwEntrySize); dwEntrySize := 0; FindNextUrlCacheEntry(hCacheDir, TInternetCacheEntryInfo(nil^), dwEntrySize); GetMem(lpEntryInfo, dwEntrySize); if dwEntrySize > 0 then lpEntryInfo^.dwStructSize := dwEntrySize; until not FindNextUrlCacheEntry(hCacheDir, lpEntryInfo^, dwEntrySize); end; FreeMem(lpEntryInfo, dwEntrySize); FindCloseUrlCache(hCacheDir); end; procedure TfAtualiza.LimparPastaTemp; var Lng: DWORD; ThePath: string; I: integer; flbArquivos: TFileListBox; begin SetLength(thePath, MAX_PATH); Lng := GetTempPath(MAX_PATH, PChar(ThePath)); SetLength(ThePath, Lng); flbArquivos := TFileListBox.Create(fAtualiza); flbArquivos.Parent := fAtualiza; flbArquivos.Directory := ThePath; flbArquivos.Visible := False; for I := flbArquivos.Items.Count - 1 downto 0 do DeleteFile(flbArquivos.Items[I]); flbArquivos.Free; end; procedure TfAtualiza.AtualizarINIVersoes(ASecao, ANomeVersao, AVersao: string); var Ini : TIniFile; begin Ini := TIniFile.Create(ExtractFilePath(Application.ExeName)+´Versoes.amk´); Ini.WriteString(ASecao, ANomeVersao, AVersao); Ini.Free; end; procedure TfAtualiza.CriaArquivoVersoes; var StringList: TStringList; begin StringList := TStringList.Create; with StringList do begin Add(´[Atualizacoes]´); Add(´Modulo1=Operacao.exe´); Add(´Versao1=vF1.1.01´); Add(´Titulo1=Operacional´); Add(´´); Add(´Modulo2=Cobranca.exe´); Add(´Versao2=vF1.1.01´); Add(´Titulo2=Faturamento/Cobrança´); Add(´´); Add(´Modulo3=Comunica.exe´); Add(´Versao3=vF1.1.01´); Add(´Titulo3=Integração entre Filiais´); Add(´´); Add(´Modulo4=Armazem.exe´); Add(´Versao4=vF1.1.01´); Add(´Titulo4=Armazenagem e Logistica´); Add(´´); Add(´Modulo5=Frota.exe´); Add(´Versao5=vF1.1.01´); Add(´Titulo5=Frota´); Add(´´); Add(´Modulo6=InstalarRelatorios.exe´); Add(´Versao6=vF1.1.01´); Add(´Titulo6=Instalador de relatórios´); Add(´´); Add(´Modulo6=InstalarBPLs.exe´); Add(´Versao6=vF1.1.01´); Add(´Titulo6=Instalador de complementos´); Add(´´); end; StringList.SaveToFile(ExtractFilePath(Application.ExeName) + gsArqLocal); StringList.Free; end;
[/list:u:a6cbf17708]
Bom, o código fonte completo eu coloquei pra download no meu FTP. Dêem uma olhada no meu blog que tem o link e todos podem entrar em contato comigo.
http://www.delphitodelphi.blogspot.com
Adriano Santos
Curtir tópico
+ 0Posts
26/04/2007
Nerdex
Pois bem, vou criar uma Unit somente para este fim dentro da aplicação mesmo, e não utilizarei arquivos .INI. A comparação (nº de versão) prefiro efetuar simplesmente via ´ping´ recorrendo a nomes de arquivos no lado FTP ou HTTP. Após baixar o download aplico uma verificação de integridade no arquivo via MD5 e/ou CRC, espero o usuário finalizar a aplicação para assim, de fato, ativar a atualização, para não ter que pedir, incovenientemente, que o usuário feche a aplicação corrente.
Não sei se dá para fazer assim como eu penso :cry: , mas é só a minha idéia... :wink:
flw?
Gostei + 0
30/11/2007
Facc
tentei pegar o arquivo, mas ele não vem
Gostei + 0
23/03/2008
Paullsoftware
Depois de listar não seria boa dar um
Gostei + 0
25/02/2009
Kripton
primeira imagem quando o TESTE.exe está lá funcionando 100¬
[img:57848e8911]http://www.megaconcursos.com.br/imagem1.JPG[/img:57848e8911]
agora quando clico no Atualizador.exe
[img:57848e8911]http://www.megaconcursos.com.br/imagem2.JPG[/img:57848e8911]
Agora depois de atualizar vejam como ficou o TESTE.exe
[img:57848e8911]http://www.megaconcursos.com.br/imagem3.JPG[/img:57848e8911]
ele fica assim como se fosse DOS e ai quando clica nele para abrir ele não abre, não sei o que acontece. Por favor alguem pode me ajudar a resolver esse problema...
[/img]
Gostei + 0
Clique aqui para fazer login e interagir na Comunidade :)