Ir para conteúdo
Fórum Script Brasil
  • 0

como fazer backup em ftp


aalessandre

Pergunta

1 resposta a esta questão

Posts Recomendados

  • 0

Veja este post

http://www.techtips.com.br/programacao/del...ivos-em-delphi/

http://scriptbrasil.com.br/forum/index.php...st&p=411163

Criando um sistema de atualização on-line.

Resposta:

Cenário inicial

Creio que muitos desenvolvedores Delphi já passaram pela situação de ter aplicativos instalados em diversas máquinas em um ambiente corporativo. Normalmente o programa começa com uma pequena base de usuários e, com o tempo, mais pessoas 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.

Pressupostos

O pressuposto básico, é óbvio, é a disponibilidade de um ambiente de rede. No entanto, é bom esclarecer que a versão apresentada do componente foi projetada para uma rede LAN de bom desempenho. Ainda não é uma boa solução para conexões de baixa 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.

O componente

Sem mais delongas, passemos então ao componente. Para facilitar a exposição, irei apresentar recortes de código sem a implementação; basicamente para apresentar a interface. As propriedades estão no ponto em que teclaríamos Ctrl+Shift+C para que o Delphi completasse o código. Ao final do artigo, apresento o 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.

TAutoUpdate = class(TComponent)

private

Client: TIdFTP;

AntiFreeze: TIdAntiFreeze;

public

published

property FTPHost: string;

property FTPUser: string;

property FTPPassword: string;

property FTPDir: string;

property FTPPassive: Boolean;

end;

Seria natural, nesse ponto, imaginar que deveríamos sobrepor o construtor para instanciar o Client FTP. No entanto, há uma particularidade: espera-se que os aplicativos sejam atualizados apenas ocasionalmente e, portanto, a instanciação seria 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.

procedure TAutoUpdate.CreateClient;

begin

if Client = nil then

begin

AntiFreeze := TIdAntiFreeze.Create(nil);

Client := TIdFTP.Create(nil);

end;

end;

destructor TAutoUpdate.Destroy;

begin

if Client <> nil then

begin

Client.Free;

AntiFreeze.Free;

end;

inherited;

end;

Quando a atualização for iniciar é conveniente apresentar uma mensagem informando a ocorrência ao usuário. Vamos criar uma propriedade para conter o texto dessa mensagem. Seu valor inicial pode ser configurado no construtor da classe. Vamos ainda, considerar que a atualização poderá ser opcional ou obrigatória, e criar uma propriedade para ajuste desse comportamento.

published

property UpdateMessage: string;

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;

Uma questão central no que diz respeito a este componente, é a questão do estilo de identificação das versões. Isso é importante porque é comparando a versão do executável corrente com o disponível é que será possível saber se a operação a realizar 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.

type

TCompareVersions = procedure(Sender: TObject; ExeVersion,

DeployVersion: string; var DeployIsLatest: Integer) of object;

TAutoUpdate = class(TComponent)

Published

property OnCompareVersions: TCompareVersions;

end;

Se o parametro var DeployIsLatest for maior que zero, a aplicação deve ser atualizada, se for menor que zero deve ser distribuída (é o caso em que o desenvolvedor está liberando uma versão para deploy e quer fazer o upload para o servidor FTP) e, se for 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.

Type

TNeedVersion = procedure (

Sender: TObject; var DeployVersion: string) of object;

TAutoUpdate = class(TComponent)

Published

property OnNeedVersion: TNeedVersion;

end;

Para facilitar a eventual gravação da versão atual no banco de dados ou outra utilização que o desenvolvedor possa ter, disponibilizamos, ainda, uma propriedade de só leitura.

property ExeVersion: string read GetExeVersion;

O último evento disponibilizado pelo componente é disparado quando o componente conclui que a versão corrente aplicação é mais recente que a versão do servidor de FTP. Talvez pareça um pouco estranho, afinal como a aplicação poderia ser mais nova que a 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.

property OnNeedDeploy: TNotifyEvent;

Bom, até agora está muito bonito, mas como o processo é iniciado? Precisamos de um método de tempo de execução que acione a verificação e orquestre todo o processo. Este método, chamamos de Execute. Criamos ainda us métodos Update e Deploy, que fazem respectivamente o download e o upload a partir do servidor de FTP.

protected

procedure Update;

public

procedure Execute;

procedure Deploy;

Vejamos a procedure Execute:

procedure TAutoUpdate.Execute;

var

VersaoExecutavel, VersaoDisponivel: string;

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;

Inicialmente nos certificamos que exista um manipulador para o evento OnNeedVersion, levantando uma exceção em caso negativo. Este manipulador deve fornecer o valor da versão disponível no servidor e, portanto, é obrigatório.

A seguir obtemos a versão atual através da função VersaoExe (ver 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.

procedure TAutoUpdate.Update;

var

tempFile, NomeExe, batchName, NomeDos: string;

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;

Inicialmente, procedemos à criação, configuração e conexão do client FTP. Em seguida verificamos a existência no servidor de um arquivo com o mesmo nome da aplicação. Se tudo estiver certo até aqui, podemos iniciar o download. Para dar um feedback ao 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:

Client.OnWorkBegin := FTPWorkBegin;

Client.OnWork := FTPWork;

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;

Em seguida, fazemos o download salvando o arquivo no diretório temporário do sistema. Neste momento surge a questão mais complicada do procedimento. Nós teríamos, neste ponto, que sobrepor o executával da aplicação. Mas como fazer isso se a 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.

procedure TAutoUpdate.Deploy;

var

NomeExe: string;

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;

Conclusão

O componente TAutoUpdate tem se revelado muito útil no dia-a-dia. Com ele, em questão de instantes conseguimos configurar uma aplicação que rode em um ambiente de rede para que seja auto-autualizável e auto-distribuível.

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.

Claro, gostaria de receber qualquer melhoria porventura implementada.

Fontes

Os fontes completos do componente estão disponíveis em http://www.dbquester.com/files/artigos/aut.../autoupdate.rar

abraço

Link para o comentário
Compartilhar em outros sites

Participe da discussão

Você pode postar agora e se registrar depois. Se você já tem uma conta, acesse agora para postar com sua conta.

Visitante
Responder esta pergunta...

×   Você colou conteúdo com formatação.   Remover formatação

  Apenas 75 emoticons são permitidos.

×   Seu link foi incorporado automaticamente.   Exibir como um link em vez disso

×   Seu conteúdo anterior foi restaurado.   Limpar Editor

×   Você não pode colar imagens diretamente. Carregar ou inserir imagens do URL.



  • Estatísticas dos Fóruns

    • Tópicos
      152,3k
    • Posts
      652,4k
×
×
  • Criar Novo...