O exemplo com fonte se realiza da seguinte forma:
Procura no servidor de FTP pelo executável de atualização, se o executável do seu
upload da nova versão. Se o executável for mais velho que o que está no FTP, ele baixa
e atualiza automaticamente o sistema.
salvar.
Em seguida vá ao menu do Delphi: Tools / Enviromment Options e na pasta Library,
defina o caminho onde está o componente.
Pronto! O componente está instalado.
Segue abaixo o artigo do Ricardo na integra.
passam a utilizá-lo. Se por um lado isso é um sinal do sucesso do software, por outro,
com o aumento da base de usuários começam a surgir novos requisitos e descobertas de
bugs que culminam com a necessidade de gerar novas versões do aplicativo. E uma
tarefa das mais desagradáveis, além de tomar muito tempo, é sair de máquina em
máquina atualizando aplicativos. Em grandes corporações, aplicativos podem estar
instalados inclusive em outras cidades ou estados. Deixar esta tarefa para os usuários é
boa receita para dores de cabeça. Com certeza, com esta política, não dá pra se
considerar que todos farão as atualizações.
A solução para este problema, em um ambiente de rede, é fazer com que o software se
auto-atualize. No entanto esta tarefa não é das mais triviais. A primeira solução que
desenvolvi neste sentido me custou uns dois meses para se tornar estável (lógico que
não fiquei este tempo só por conta dessa atividade). Há questões um tanto complicadas
envolvidas como, por exemplo, a dificuldade de fazer debug em equipamentos remotos,
as diferenças de comportamento entre versões diferentes do Windows, a estratégia para
fazer com que um software possa substituir seu próprio arquivo, entre outras. Se isso
vale a pena para um aplicativo importante, é desanimador quando pensamos em
softwares mais triviais.
Assim, após desenvolver uma solução de auto-update para um aplicativo, foi natural
concluir que esta funcionalidade deveria idealmente ficar em um componente para que
pudesse ser facilmente reaproveitada. Afinal trata-se de uma necessidade comum à
maioria dos programas desenvolvidos em um ambiente corporativo. Portanto, investi
algum tempo na elaboração do componente TAutoUpdate. O resultado prático foi tão
satisfatório, para mim assim como para diversos colegas, que me motivou a escrever o
presente artigo.
velocidade ou em ambiente Web, pois o programa principal congela durante o
download. Para redes de baixa velocidade o download deveria ser realizado em uma
thread distinta, o que implicaria em uma série de alterações no projeto.
Considera-se, ainda, que o software será distribuído a partir de um servidor FTP. Uma
vez que o arquivo executável esteja disponível no servidor, sua distribuição será
automática. Por ser muito oportuno, o componente também é capaz de fazer o upload da
aplicação para o servidor quando o desenvolvedor libera uma nova versão. Para tanto, o
servidor deverá permitir operações de escrita ao usuário configurado no componente.
Na implementação proposta, entende-se que a atualização do programa consiste na
simples substituição do executável pelo arquivo da nova versão, de nome idêntico, não
sendo realizado nenhum procedimento adicional de instalação. Não é difícil, entretanto,
fazer alterações para a inclusão de funcionalidades semelhantes.
Embora o componente não faça nenhuma pressuposição a respeito da aplicação (não
impõe requisitos à aplicação), é necessário que haja algum mecanismo de identificação
e comparação das versões do executável. Assim, esta tarefa pode ser personalizada pelo
aplicativo, mas se as informações de versão forem habilitadas (em Project/Options), o
componente será capaz de realizá-la sem necessidade de codificação via aplicação.
Nesse caso, o esquema de identificação de versões será o tradicional de quatro números
separados por pontos.
Delphi completasse o código. Ao final do artigo, disponibilizo um link para o código
fonte completo.
Faz sentido manter o Client de FTP no componente, já que a utilização do protocolo, a
princípio, não diz respeito à aplicação e sim à tarefa de auto-atualização. Portanto,
iremos encapsular um TIdFTP. Optamos por utilizar a versão 10 dos componentes Indy.
Para a versão anterior, há uma ligeira diferença na codificação, mas não iremos abordála
aqui, por não ser nada substancial.
AntiFreeze: TIdAntiFreeze;
public
published
property FTPHost: string;
property FTPUser: string;
property FTPPassword: string;
property FTPDir: string;
property FTPPassive: Boolean;
end;
inútil à maioria das vezes. Vamos então, deixar para instanciar o componente interno
quando concluirmos pela sua necessidade. O que podemos adiantar é o método privado
para executar esta tarefa, assim como o destrutor que fará a limpeza de contrapartida.
begin
AntiFreeze := TIdAntiFreeze.Create(Self);
Client := TIdFTP.Create(Self);
end;
end;
destructor TAutoUpdate.Destroy;
begin
if Client <> nil then
begin
Client.Free;
AntiFreeze.Free;
end;
inherited;
end;
considerar que a atualização poderá ser opcional ou obrigatória, e criar uma propriedade
para ajuste desse comportamento.
property OptionalUpdate: Boolean;
…
end;
…
constructor TAutoUpdate.Create(AOwner: TComponent);
begin
inherited;
UpdateMessage :=
‘Há uma nova versão do aplicativo disponível.’#13 +
‘A atualização automática será iniciada.’;
end;
deve ser de atualização, de distribuição ou nenhuma. Uma boa alternativa é adotar o
estilo padrão com quatro números separados por pontos. No entanto, há
desenvolvedores que preferem trabalhar com um ou dois números, outros com uma
data, outros com textos (personal, professional, entreprise) etc. Se, por um lado, não
queremos impor um estilo em particular, por outro, achamos que o componente deve
oferecer algum tratamento com essa finalidade. Assim, optamos por adotar o estilo
padrão, mas com a possibilidade de personalização. Como há um momento em que as
versões devem ser comparadas, criamos um evento que o desenvolvedor pode ignorar,
assumindo assim o tratamento padrão, ou escrever um manipulador para impor uma
regra de comparação personalizada.
TAutoUpdate = class(TComponent)
…
Published
property OnCompareVersions: TCompareVersions;
…
end;
igual a zero nada deve ser feito (as versões são as mesmas).
Ainda temos outra questão com relação à identificação da versão. Precisamos conhecer
a versão disponível no servidor para fazer a comparação. Claro que não faz sentido
baixar o arquivo só pra ver a versão. Então optei por deixar esta verificação a cargo da
aplicação. Usualmente, basta gravar esta informação no banco de dados, o que torna a
tarefa muito simples para o aplicativo. Fazer isso no componente exigiria protocolos
adicionais ou acesso ao banco, o que não é (nem deveria ser) do conhecimento desta
camada. Para isso criamos mais um evento.
TAutoUpdate = class(TComponent)
…
Published
property OnNeedVersion: TNeedVersion;
…
end;
do servidor? Na verdade é simples: quando o desenvolvedor gera uma nova versão.
Assim, esse evento é um ótimo facilitador para programar a distribuição do novo
arquivo. Basta alterar o número da versão e o programa pode fazer automaticamente o
upload e, se for o caso, gravar no banco de dados a identificação de versão do arquivo.
fazem respectivamente o download e o upload a partir do servidor de FTP.
public
procedure Execute;
procedure Deploy;
…
i: integer;
botoes: TMsgDlgButtons;
begin
if not Assigned(FOnNeedVersion) then
raise Exception.Create(
‘O manipulador do evento OnNeedVersion é obrigatório.’
);
VersaoDisponivel := ”;
FOnNeedVersion(Self, VersaoDisponivel);
if VersaoDisponivel = ” then
raise Exception.Create(‘Versão disponível inválida (vazia).’);
VersaoExecutavel := VersaoExe;
if VersaoExecutavel = ” then
VersaoExecutavel := ’1.0.0.0′;
i := CompareVersion(VersaoDisponivel, VersaoExecutavel);
if Assigned(FOnCompareVersions) then
FOnCompareVersions(Self, VersaoExecutavel, VersaoDisponivel, i);
if i > 0 then
begin
botoes := [mbOK];
if OptionalUpdate then
Include(botoes, mbCancel);
if MessageDlg(UpdateMessage, mtInformation, botoes, 0) = mrOk then
Update;
end
else if (i < 0) and Assigned(FOnNeedDeploy) then
FOnNeedDeploy(Self);
end;
A seguir obtemos a versão atual através da função VersaoExe (link para fontes
completos ao final do artigo) e a comparamos com a versão disponível. Se houver um
manipulador para o evento OnCompareVersions, este poderá alterar o resultado da
comparação padrão.
Finalmente, se a versão disponível for a mais recente iniciamos o procedimento de
Update. Para isso, enviamos uma mensagem para o usuário avisando que a versão será
atualizada. Esta mensagem terá um botão OK e, se OptionalUpdate for verdadeiro, um
botão Cancel para que o usuário possa cancelar o procedimento. Se a versão atual for
mais recente que a disponível, disparamos o evento OnNeedDeploy. Optamos por
deixar a decisão quanto a realizar a distribuição da nova versão a cargo do manipulador
de evento. Além disso, disponibilizamos um método Deploy que pode ser chamado
neste manipulador.
O próximo método a comentar é o Update, entendo que o mais importante do
componente, pois realiza a tarefa para o qual o mesmo foi desenvolvido.
lista: TStringList;
existe: Boolean;
begin
if FTPHost = ” then
raise Exception.Create(‘FTPHost não definido’);
CreateClient;
Client.Host := FTPHost;
Client.Username := FTPUser;
Client.Password := FTPPassword;
Client.Passive := FTPPassive;
if not Client.Connected then
Client.Connect;
if not Client.Connected then
raise Exception.Create(‘Erro na conexão com o servidor de FTP’);
Client.ChangeDir(FTPDir);
// verificar disponibilidade do arquivo no servidor
NomeExe := ExtractFileName(Application.ExeName);
lista := TStringList.Create;
frmAtualizando := TfrmAtualizando.Create(Self);
try
Client.TransferType := ftASCII;
Client.List(lista, NomeExe, False);
existe := (lista.Count > 0) and
(UpperCase(lista[0]) = UpperCase(NomeExe));
if not existe then
raise Exception.Create(‘Arquivo não disponível no servidor
FTP.’);
// Exibir transferência para o usuário
Client.TransferType := ftBinary;
BytesToTransfer := Client.Size(NomeExe);
frmAtualizando.Show;
// baixar arquivo temporário
tempFile := GetTmpDir + ChangeFileExt(NomeExe, ‘.tmp’);
Client.Get(NomeExe, tempFile, True);
Client.Disconnect;
if not FileExists(tempFile) then
exit;
// criar bath e sobrepor exe
NomeDos := ExtractShortPathName(ParamStr(0));
lista.Clear;
batchname := GetTmpFileName(‘.bat’);
FileSetAttr(ParamStr(0), 0);
lista.Add(‘:Label1′);
lista.Add(‘@echo off’);
lista.Add(‘del ‘ + NomeDos);
lista.Add(‘if Exist ‘ + NomeDos + ‘ goto Label1′);
lista.Add(‘Move ‘ + tempFile + ‘ ‘ + NomeDos);
lista.Add(‘Call ‘ + NomeDos);
lista.Add(‘del ‘ + batchname);
lista.SaveToFile(batchname);
ChDir(GetTmpDir);
WinExec(PChar(batchname), SW_HIDE);
finally
lista.Free;
FreeAndNil(frmAtualizando);
Application.Terminate;
end;
end;
usuário, utilizamos um formulário a parte (frmAtualizando), que encapsula um Gauge e
será atualizado pelo evento FTPWork do IdFTP. Para isso, alteramos o método
CreateCliente descrito anteriormente adicionando os manipuladores de evento:
…
procedure TAutoUpdate.FTPWorkBegin(Sender: TObject; AWorkMode:
TWorkMode;
AWorkCountMax: Integer);
begin
if AWorkCountMax > 0 then
frmAtualizando.Max := AWorkCountMax
else
frmAtualizando.Max := BytesToTransfer;
end;
procedure TAutoUpdate.FTPWork(Sender: TObject; AWorkMode: TWorkMode;
AWorkCount: Integer);
begin
frmAtualizando.Position := AWorkCount;
end;
mesma está rodando? O Sistema Operacional não permitiria sobrepor um arquivo em
uso. A solução, um tanto tortuosa, é verdade, foi criar um arquivo batch com esta
finalidade. O componente cria e aciona o script que irá entrar em looping até conseguir
apagar o arquivo executável. Como a aplicação terminará em seguida, o script sairá do
looping e realizará as tarefas subsequentes que são mover o arquivo temporário,
executá-lo e, por fim deletar seu próprio arquivo (sim, arquivos batches podem fazer
isso, provavelmente porque são inteiramente carregados para a memória antes da
execução).
O proximo método, o Deploy, é similar ao anterior, porém faz um updolad em vez de
um download, e não precisa fazer toda essa ginástica para sobrepor o arquivo.
begin
if FTPHost = ” then
raise Exception.Create(‘FTPHost não definido’);
CreateClient;
Client.Host := FTPHost;
Client.Username := FTPUser;
Client.Password := FTPPassword;
Client.Passive := FTPPassive;
if not Client.Connected then
Client.Connect;
if not Client.Connected then
raise Exception.Create(‘Erro na conexão com o servidor de FTP’);
Client.ChangeDir(FTPDir);
NomeExe := Application.ExeName;
frmAtualizando := TfrmAtualizando.Create(Self);
try
Client.TransferType := ftBinary;
BytesToTransfer := FileLength(NomeExe);
Client.Put(NomeExe, ExtractFileName(NomeExe));
frmAtualizando.Show;
Client.Disconnect;
finally
FreeAndNil(frmAtualizando);
Screen.Cursor := crDefault;
end;
ShowMessage(‘Deploy finalizado.’);
end;
Como sugestão de desenvolvimento, aponto a melhoria do componente para que o
mesmo possa operar adequadamente no ambiente Internet. Outra melhoria seria ter a
opção de, após o download, perguntar ao usuário se a aplicação deve ser atualizada
imediatamente ou apenas na próxima execução. Isso seria importante em aplicações que
possam ter dados não salvos.
Desenvolvimento de um componente para atualização e distribuição
