[Delphi] Funcionalidade de Atualização Automática – Parte 3

[Delphi] Funcionalidade de Atualização Automática - Parte 3Leitores, vamos fechar a série de artigos sobre atualização automática? Vamos!
A terceira e última parte dessa série traz algumas observações sobre as codificações do artigo anterior, com o objetivo de esclarecer dúvidas que talvez tenham surgido e também para explicar os recursos que utilizei no tutorial. Recomendo a leitura desse artigo!

 

Enquanto escrevia os dois primeiros artigos da série, procurei prever possíveis dúvidas e dificuldades dos leitores. O resultado foi a lista de observações abaixo, cada uma com sua respectiva justificativa. Mesmo assim, sintam-se à vontade para utilizar o formulário de comentário no final da página para questionar ou complementar algo, ok?

 

1) Arquivo INI
Para manter o aspecto didático no artigo, optei pela utilização de arquivos INI devido à simplicidade de manipulação, mas isso não é uma regra! A informação da versão pode ser armazenada de várias formas, como uma tabela no banco de dados, um arquivo XML ou até mesmo um documento na nuvem. Além disso, a informação da versão também pode ser obtida nas propriedades do executável através da seguinte função:

function TForm1.ExtrairVersaoAplicacao: string;
var
  nTamanhoInfo, nTamanhoValor, Handle: Cardinal;
  pInfoVersao: Pointer;
  pValorVersao: PVSFixedFileInfo;
begin
  nTamanhoInfo := GetFileVersionInfoSize(PChar(Application.ExeName), Handle);
  GetMem(pInfoVersao, nTamanhoInfo);
  try
    GetFileVersionInfo(PChar(Application.ExeName), 0, nTamanhoInfo, pInfoVersao);
    VerQueryValue(pInfoVersao, '\', Pointer(pValorVersao), nTamanhoValor);
 
    result := Format('%d.%d.%d.%d', [
      HiWord(pValorVersao^.dwFileVersionMS),
      LoWord(pValorVersao^.dwFileVersionMS),
      HiWord(pValorVersao^.dwFileVersionLS),
      LoWord(pValorVersao^.dwFileVersionLS)]);
  finally
    FreeMem(pInfoVersao, nTamanhoInfo);
  end;
end;

 

2) Clean Code
Alguns métodos do artigo anterior podem ser melhorados ao aplicar as práticas de Clean Code. Por exemplo, os métodos que consultam a versão local e a versão do FTP são bastante semelhantes e podem ser unificados, recebendo um parâmetro para definir qual arquivo será lido. Do mesmo modo, as instruções que excluem os arquivos existentes também podem ser refatorados para um novo método definido como “ExcluirArquivo”. Porém, mais uma vez, mantive a implementação bem detalhada para facilitar a compreensão.
Além disso, os nomes dos componentes também devem ser adequados, como a alteração do nome do componente TProgressBar de “ProgressBar1” para “pbProgressoDownload”.

 

3) Validação da conexão com a internet
A rotina de atualização pode apresentar algumas exceções caso o computador esteja desconectado da internet. Para garantir a confiabilidade, podemos implementar uma função que verifica se há uma conexão válida e utilizá-la em uma condição IF antes da tentativa de acesso ao FTP:

uses
  WinInet;
 
{ declaração }
function VerificarExisteConexaoComInternet: boolean;
 
{ implementação }
function TForm1.VerificarExisteConexaoComInternet: boolean;
var
  nFlags: Cardinal;
begin
  result := InternetGetConnectedState(@nFlags, 0);
end;
 
{ utilização (método ConectarAoServidorFTP) }
if not VerificarExisteConexaoComInternet then
  Exit;

 

4) Exibir a quantidade de KBytes baixados
Lembram-se que implementamos a porcentagem do download no evento OnWork do componente TIdFTP? Pois bem, no mesmo evento, podemos exibir também a quantidade de KBytes que estão sendo baixados em tempo real:

var
  nTamanhoTotal, nTransmitidos: real;
begin
  // obtém o tamanho total do arquivo em bytes
  nTamanhoTotal := FnTamanhoTotal div 1024;
 
  // obtém a quantidade de bytes já baixados
  nTransmitidos := AWorkCount div 1024;
 
  // atualiza o componente TLabel com a quantidade de KBytes
  Label1.Caption := Format('%s KB de %s KB',
    [FormatFloat('##,###,##0', nTransmitidos), FormatFloat('##,###,##0', nTamanhoTotal)]);
end;

 

5) Reiniciar a aplicação após a atualização
Essa é uma boa ideia, hein? Afinal, a atualização só entrará em vigor quando o sistema for fechado e iniciado novamente. Para executar essa ação, basta implementar os comandos abaixo quando a atualização for finalizada:

ShowMessage('Atualização concluída com sucesso. A aplicação será reiniciada.');
ShellExecute(Handle, nil, PChar(Application.ExeName), '', nil, SW_SHOWNORMAL);
Application.Terminate;

 

6) Executar script
Muitas vezes, só atualizar o executável não é o suficiente. É necessário executar também alguns scripts no banco de dados como parte da atualização. No entanto, essa ação depende de qual banco de dados está sendo utilizado no projeto. Para Firebird, por exemplo, pode-se utilizar o utilitário isql para executar scripts via linha de comando, assim como foi feito com o 7-Zip no artigo anterior. Veja um exemplo da sintaxe:

isql.exe C:\Aplicativo\Banco.fdb -m -b -i C:\Atualizacao\Script.sql -q -u SYSDBA -p masterkey

Opcionalmente, o desenvolvedor também pode criar um aplicativo exclusivo para essa finalidade, executado durante a atualização.
Se este procedimento for necessário, entre em contato comigo. Talvez eu possa ajudá-lo!

 

7) Copiar arquivos adicionais
Recebi essa dúvida na semana passada do leitor Marcos Vinicius (obrigado pela questão, Marcos!).
A cópia de arquivos adicionais, como uma DLL, por exemplo, pode ser feita por meio de um arquivo de lotes (extensão BAT), contendo comandos do MS-DOS, como no exemplo a seguir:

Copy C:\Atualizacao\Biblioteca.dll C:\Aplicativo

A rotina de atualização, por sua vez, deve executar esse arquivo com a função ShellExecute:

ShellExecute(Application.Handle, 'open', 'cmd',
  PChar(' /c ' + 'C:\Atualizacao\Atualizacao.bat'), nil, SW_NORMAL);

 

8) Log de atualização
Procure registrar o processo de atualização em algum local, como um arquivo de texto, contendo os seguintes dados:

  • Data e hora da atualização
  • Tamanho do pacote de atualização
  • Usuário conectado
  • Versão que está sendo atualizada
  • Resultados do(s) script(s)

Essas informações podem ser relevantes para atividades de manutenção, estatísticas (quantidade de atualizações em período) ou simplesmente para controle interno. Para complementar, este log pode ser enviado automaticamente ao desenvolvedor por e-mail após o término da atualização.

 

Bom, pessoal, aqui encerro a série de artigos sobre atualização automática.
Dúvidas? Deixe um comentário!

Na próxima semana comentaremos sobre Engenharia de Valor!
Abraços!


Confira as outras partes desse artigo:

[Delphi] Funcionalidade de Atualização Automática – Parte 1
[Delphi] Funcionalidade de Atualização Automática – Parte 2
[Delphi] Funcionalidade de Atualização Automática – Parte 3


 

99 comentários

  1. Fala Batera, tudo joia?
    Acompanhei os 3 posts sobre atualização automática, muito bom. Esse tema sempre rende muitas dúvidas. Referente a execução de scripts no banco firebird, o que você aconselharia, utilizar o isql ou um aplicativo externo? Abraço.

    1. Opa, Ivan, quanto tempo! Tudo certo?
      Bom, eu optaria pelo isql utilizando o parâmetro “-o” para gerar um arquivo de log, como no exemplo abaixo:

      isql Banco.fdb -m -b -i Script.sql -q -u SYSDBA
      -p masterkey -o ArquivoLog.txt

      Dessa forma, após a execução do script, é possível ler o arquivo de log e verificar se houve algum erro. Outra alternativa é utilizar o CreateProcess para exibir o resultado das instruções do script na tela do usuário. Se você precisar de um exemplo, é só falar!

      Abraço!

  2. Olá André, minha dúvida é, você poderia da um exemplo de como faria para executar os scripts no banco enquanto atualizo a aplicação?

    1. Olá, Allan, tudo certo?
      Bom, conforme mencionado no artigo, a forma de executar scripts depende do banco de dados utilizado.
      Para Firebird, por exemplo, basta utilizar o utilitário isql, informando os seguintes parâmetros:

      isql.exe C:\Aplicativo\Banco.fdb -m -b -i
        C:\Atualizacao\Script.sql -q -u SYSDBA -p masterkey

      Para executar este comando, pode-se utilizar o ShellExecute.

  3. Olá André

    Tudo bem?
    Gostei muito do seu post!

    No entanto, acompanhei todas as etapas, mas quando executei deu o seguinte erro:

    Can’t open atualizacao/VersaoFTP.ini: No such file or directory

    Mas a versão está no servidor.
    O que pode estar acontecendo?

    Inclusive, baixei o seu codigo fonte e alterei os parametros, mas ocorreu o mesmo erro.

    Desde de já agradeço

    Anselmo Lima

    1. Olá, Anselmo, tudo bem?
      Bom, o erro informado indica que o arquivo “VersaoFTP.ini” não existe dentro da pasta “atualizacao” no servidor FTP. Experimente seguir as orientações abaixo:
      1) Verifique se a pasta “atualizacao” está na raiz do diretório FTP;
      2) Verifique se o arquivo “VersaoFTP.ini” está com o nome correto, inclusive a extensão;
      3) Acesse o servidor FTP pelo Filezilla ou pela Web e certifique-se de que o arquivo se encontra na pasta.

      Se tudo estiver correto e o erro ainda persistir, volte a avisar!
      Abraço!

  4. Gostaria de saber como colocar uma verificação dos arquivos, no caso ‘VersãoLocal’, por meio do MD5. E verificar também o executável.

    1. Olá, Eltomarle, tudo bem?
      Bom, depende do tipo de verificação que você pretende aplicar. Vou entrar em contato com você por e-mail, ok?

      Abraço!

  5. Bom dia professor!
    O seu código está baixando apenas um arquivo.zip, como fazer para baixar vários arquivos.zip de uma única vez?

    Tipo:
    atualizacao1.zip
    atualizacao2.zip
    atualizacao3.zip

    E assim por diante… Para baixar todos os arquivos com extensão .zip.

    1. Olá, Eltomarle!
      Para baixar outros arquivos do FTP, basta utilizar o comando

      IdFTP.Get()

      conforme no exemplo abaixo:

      IdFTP1.Get('Arquivo1.zip', C:\Arquivo1.zip', True, True);
      IdFTP1.Get('Arquivo2.zip', C:\Arquivo2.zip', True, True);
      IdFTP1.Get('Arquivo3.zip', C:\Arquivo3.zip', True, True);

      Abraço!

  6. Boa tarde.
    Bom professor, obrigado por me responder, mas o que eu não queria era justamente ter que ficar incrementando linhas no código, por que tipo: Se eu tiver 200 arquivos(isso e só um exemplo), ai terei que fazer 200 linhas de comando isso acaba sendo inviálvel.

    Eu estava pensando mais em algo do tipo:
    IdFTP1.Get( ‘*.zip’, ‘C://*.zip’, True, True);

    mais sei que infelizmente o caracter especial ‘*’ não e aceitado pelo GET. Então a minha duvida e fazer justamente isso mais sem precisar estar repetindo linhas de codigo.

    1. Tem razão, Eltomarle. O comando Get não aceita curingas (ou wildcards). Uma alternativa é varrer o diretório FTP (usando o TIdFTP.List) e jogar em uma lista. Após isso, usar o Get em todos os itens da lista para baixar os arquivos.

      Por exemplo, suponha que você queira baixar os seguintes arquivos:
      – Biblioteca.dll
      – Pacote.zip
      – Texto.txt

      Em primeiro lugar, use o comando List para preencher uma lista com o nome desses 3 arquivos.
      Em seguida, faça um loop nessa lista e chame o método Get para baixá-los.

      Acredito que essa forma seja funcional.
      Abraço!

  7. Mais uma vez agradeço do fundo do meu coração, pois estou quase um mês tentando fazer isso, baixar vários arquivos. Mas sou novo no mundo maravilhoso da programação!!!
    Eu até entendi a sua explicação, mas o problema é que já tentei de todas as formas mas não consegui, e não sei como colocar em pratica o que o senhor explicou logo a cima. Se o senhor poder me ajudar na codificação eu lhe serei grato eternamente.

  8. Primeiramente muito bom o passo a passo.
    Segundamente hehe, quando faço executar o isql.exe passando os parametros, ele executa o sql tudo certinho, porém o arquivo de log fica em branco.
    Pode me dar uma ajuda por email?
    Grato

    1. Olá, Kelvin, tudo certo?
      Opa, posso ajudá-lo, sim! Abraço!

    1. Opa, muito obrigado, Saulo!
      Em breve pretendo atualizar alguns passos desse artigo para torná-lo mais fácil.
      Abraço!

  9. Olá André, gostaria de agradecer por colaborar com toda a comunidade Delphi com seu blog e com artigos que tendem a ajudar muita gente (inclusive eu rsrsrs).
    Fiz todo o procedimento de update e funcionou quase tudo. Fiquei na mesma dúvida que o amigo Kelvin ali em cima.
    A atualização ocorre normalmente, o ShellExecute roda o aplicativo, porém o log fica em branco e não executa o arquivo sql.
    A rotina do arquivo sql adiciona dois campos em uma tabela, porém isso não ocorre.
    Poderia dar um help fazendo um favor?
    Muito Obrigado!

    1. Olá, Marlon, tudo bem?
      Funcionou quase tudo? Vamos fazer funcionar tudo então, rsrs!
      Vou entrar em contato com você. Abraço!

  10. Ola andre estou com problema para entender o comando para descompactar
    ShellExecute(0, nil, ‘7z’, PWideChar(‘ e -aoa ‘ +
    sNomeArquivoAtualizacao +’ -o’ + ObterDiretorioDoExecutavel), ”, SW_SHOW); o que seguinifica esse 0, e esse e -aoa ,-o ? Pois aqui nao esta funcionando tenho a dll 7z mais nao funciona.

    1. Olá, Alexandre!
      Os comandos do 7z realmnete são um pouco confusos. Tive que ler a documentação para aprender a usá-los.
      Na documentação diz o seguinte:

      Switch -aoa:
      This switch overwrites all destination files.
      Use it when the new versions are preferred.
      Switch o:
      Use this to set the destination directory.

      Pode parecer estranho, mas o “-o” deve vir junto com o nome do diretório, sem espaços.
      Na prática, o comando ficaria dessa forma:

      7z e -aoa Atualizacao.rar -oC:\Aplicativo

      Onde:
      “7z” é o nome do descompactador;
      “e” comando de extração de arquivos;
      “-aoa” substui os arquivos no destino, caso existam;
      “-o” indica o diretório em que os arquivos serão descompactados.

      Mais informações aqui:
      http://www.dotnetperls.com/7-zip-examples

      Abraço!

  11. Caro André, obrigado pela explicação do 7z. Até ai entendi, mas meu problema continua, ate agora consigo baixar o Atualizacao.rar sendo que não consigo extrair. Dentro do meu Atualizacao.rar tem uma pasta com arquivos xlsm. Preciso que o arquivo rar seja descompactado substituindo a pasta com os arquivos xlsm.

    Agradeço pela ajuda!!!

    1. Olá, Alexandre!
      Ainda continuo desconfiando que é um erro de sintaxe do comando do 7z.
      Faça o seguinte: execute este comando pelo prompt do DOS. Se estiver ocorrendo um erro, o próprio console irá exibi-lo.
      Se não souber como trabalhar com o DOS, entre em contato pelo e-mail “contato@andrecelestino.com”.

      Abraço!

  12. Boa tarde!!
    e tentando e aprendendo!! esta tudo funcionando menos a descompactação.

    procedure TForm1.DescompactarAtualizacao;
    var
    sNomeArquivoAtualizacao: string;
    begin
    // deleta o backup anterior, ou melhor, da versão anterior,
    // evitando erro de arquivo já existente
    if FileExists(ObterDiretorioDoExecutavel + ‘prestringtable_Backup’) then
    DeleteFile(ObterDiretorioDoExecutavel + ‘prestringtable_Backup’);

    // Renomeia o executável atual (desatualizado) para “Sistema_Backup.exe”
    RenameFile(ObterDiretorioDoExecutavel + ‘prestringtable’,
    ObterDiretorioDoExecutavel + ‘prestringtable_Backup’);

    // armazena o nome do arquivo de atualização em uma variável
    sNomeArquivoAtualizacao := ObterDiretorioDoExecutavel + ‘Atualizacao.rar’;

    // executa a linha de comando do 7-Zip para descompactar o arquivo baixado
    ShellExecute(0, nil, ‘7z’, PWideChar(‘ e -aoa ‘ +
    sNomeArquivoAtualizacao +’ -o’ + ObterDiretorioDoExecutavel), ”, SW_SHOW);
    end;

    esse e o codigo mais continua nao descompactando

    1. Olá, Alexandre. Você tentou executar o comando do 7-Zip pelo DOS conforme sugeri no comentário anterior? Que erro ocorre?

      Abraço!

  13. Caro Andre me desculpe pela insistência!!, sim pelo dos funciona o comando 7z e -aoa Atualizacao -oprestringtable
    sendo que ele extrai errado .

    Extracting prestringtable\ru\LanguageData.xlsm
    Extracting prestringtable\ru\stringtable_cutscene_ru.xlsm
    Extracting prestringtable\ru\stringtable_ru.xlsm
    Extracting prestringtable\ru\symbolnostringtable_ru.xlsm
    Extracting prestringtable\ru <= essa linha não era para sair
    Extracting prestringtable <= essa linha não era para sair

    isso tudo pelo dos!!!

    Muito obrigado pela atenção.

  14. Primeiramente parabéns por disponibilizar tão rico e útil conhecimento! Bom, como poderíamos implementar uma forma de informar ao usuário de que existe uma versão nova? A exemplo do CCleanner que exibe uma janelinha no canto inferior do desktop quando de nova versão disponível.

    1. Olá, Sávio, tudo bem?

      Desculpe-me pela demora.
      Na parte 2 da série de artigos sobre atualização monetária, há 2 métodos: ObterNumeroVersaoLocal e ObterNumeroVersaoFTP. Poderíamos utilizá-los para notificar o usuário de que há uma versão nova disponível.
      Por exemplo, a aplicação, ao ser iniciada, compara a versão local com a versão que está no FTP. Se a versão do FTP for maior, significa que uma nova versão está disponível, então apresentaríamos uma mensagem, notificação ou um link (como o CCleaner) para alertá-lo da atualização.

      Esse cenário se encaixa mais em uma atualização manual do que automática, já que o próprio usuário teria que tomar a ação de atualizar o software. Se as alterações forem performáticas (envolvendo apenas usabilidade e/ou performance), então não vejo problemas em atualizações manuais. Porém, se forem alterações importantes, que corrijam erros ou tragam novas funcionalidades importantes para o usuário final, então a atualização automática é mais recomendada.

      Abraço!

  15. Bom dia, André!
    Parabéns por sua iniciativa e por sua magnífica colaboração. Parabéns!
    Professor, vou lhe fazer um pedido: Você não poderia disponibilizar este sistema, para que apenas alterássemos o que fosse necessário e pudéssemos incrementá-lo ao nosso sistema? Digo isto, não por comodismo, mas pelas dificuldades que, pessoas como eu, apaixonadas por Delphi mas praticamente leigas nesta e em todas as linguagens de programação, pudessem fazer uso deste recurso, tão útil.
    André, tenho 67 anos de idade e desfruto de alguns problemas que a idade carrega. Mas minha paixão por esta maravilha, chamada Delphi, não consegue terminar.
    Você é uma pessoa extremamente admirável e de uma grandeza imensurável.
    Mais uma vez, parabéns.
    Abraço

    1. Olá, Fábio, como vai?

      Devo dizer que fiquei sem palavras com o seu comentário! Primeiro, pelos seus elogios, que me motivam a continuar contribuindo para a comunidade de programadores Delphi com o blog. Segundo, pela satisfação em encontrar mais uma pessoa que é tão apaixonada pelo Delphi como eu sou. Fico muito feliz em notar este artigo foi de grande utilidade!
      Fábio, idade não significa nada! O que importa é a sua vontade de aprender e, antes que eu me esqueça, a sua capacidade admirável de escrita. Acho que é um dos melhores (senão o melhor) comentários que já recebi no blog. Muito obrigado!

      Bom, respondendo a sua pergunta, o módulo de atualização automática que tenho hoje é parte de um sistema de gestão de pedidos para representantes comerciais, composto por vários outros módulos. Pensei em disponibilizá-lo neste artigo, mas o arquivo ficaria muito grande, portanto, decidi elaborar um exemplo exclusivo com essa implementação, disponível neste link:

      http://www.andrecelestino.com/wp-content/files/AtualizacaoAutomatica-AndreCelestino.rar

      O exemplo tem toda a parte abordada nas 3 partes do artigo sobre essa funcionalidade. Para adaptá-lo a um sistema, basta somente alterar as propriedades do componente TIdFTP (configurando o nome, usuário e senha do servidor utilizado) e o nome dos arquivos.
      Mesmo assim, Fábio, estarei à disposição para qualquer dúvida!

      Um grande abraço!

  16. Boa noite André.

    Já faz tempo que eu quero falar sobre o quanto este artigo foi útil para mim, mas não foi na parte de atualização ainda, foi no licenciamento do software, com certeza também implementei a atualização, tudo funciona direitinho.
    Pode acreditar toda vez que eu instalo um software novo ou quando visito um cliente e vejo programa abrindo e verificando tudo que eu preciso, eu lembro que isso foi possível a partir deste artigos que você generosamente disponibilizou para nós.
    Eu implementei varias funções desde a liberação e o gerenciamento das licenças de uso que vendo até a dos distribuidores (tinha um mas parei por enquanto).
    Muito obrigado, não só pro essa aproveitei muitas dicas e orientações.

    Um abraço.

    1. Olá, Gerson!
      Suas palavras foram muito gratificantes e me motivam a continuar o trabalho no blog!
      Como você deve ter notado, ando meio ausente (quase 2 meses sem publicações), mas pretendo retornar em breve.
      Agradeço muito por ter deixado este comentário e também fico feliz em saber que os artigos lhe ajudaram.
      Desejo boa sorte nos seus projetos!

      Grande abraço!

  17. Parabéns André, procuro por algo assim há mais de um ano e nunca encontro, e os que encontro, geralmente não tem explicação de como funciona ou o nível “dos cara” é altíssimo, dificultando muito o entendimento de nós programadores mais leigos. Muitíssimo obrigado mesmo, você com toda certeza ajudou muito mais gente na mesma situação que eu, e não mediu esforços para responder aos comentários, é de gente assim, humilde e de bom coração que o mundo precisa, gente que não se gaba do seu intelecto e ajuda o próximo. Mais uma vez muitíssimo obrigado, que Deus lhe abençoe. Abraço.

    1. Boa noite, Diemes, tudo bem?
      Nem sei o que dizer sobre o seu comentário! Agradeço de coração pelas palavras. São pessoas como você que me motivam a continuar o trabalho no blog. Sinto que o meu objetivo em compartilhar o conhecimento está sendo cumprido! Muito obrigado, Diemes!

      Aproveitando, uma novidade para você: em breve pretendo escrever uma nova “versão” dos artigos sobre Atualização Automática. Recebi algumas sugestões e dicas bem interessantes de alguns leitores. Eu estimo que ficará mais fácil.

      Grande abraço!

  18. Bom dia, André.
    Primeiramente quero lhe agradecer por ter me ajudado e ajudado muitos programadores.
    Quero tirar uma duvida. Eu quero extrair todos os arquivos que estão dentro do arquivo Atualizacao.rar.
    Quem poder me Ajudar, agradeço.

    1. Olá, Luiz!
      Já estamos conversando por e-mail. 🙂
      Abraço!

  19. Olá, André!!

    Primeiramente meus parabéns , foi o melhor artigo que achei sobre atualização de sistemas. Sou programador desde o delphi 4, e nunca tinha criado nada para automatizar (é aquele negócio que sempre tem algo mais importante e você deixa para depois hehehe)
    Na verdade vejo como a maior dificuldade justamente a gestão dos scripts do banco de dados para atualizar cada cliente. Pelo que você mostrou, é simples fazer a chamada pelo isql (também uso Firebird), porém o script que deve ser rodado é que é o problema. Como você fez ou faria isso?
    Uma vontade que tive mas não consegui achar, foi utilizar alguma api automatica de alguma ferramenta de comparação. Com isso, teria um banco mestre vazio, e a ferramenta faria a comparação independente da versão do banco do meu cliente. Não sei se fui claro na explicação, se puder dar alguma luz, agradeço.
    Abraço!!

    1. Boa noite, Fernando, tudo bem?
      Ótima pergunta! A execução de scripts em atualizações automáticas realmente é algo que deve ser bem administrada.
      Uma alternativa bastante viável é armazenar o nome dos scripts que já foram rodados em alguma tabela de controle no banco de dados. Por exemplo, suponha que os scripts A, B e C estão no pacote de atualização. O cliente 1 tem os scripts A e B executados. Já o cliente 2 tem somente o script A, talvez por estar utilizando uma versão mais antiga do sistema. Na atualização automática, basta verificar os scripts que já foram executados (por meio de uma consulta nessa tabela) e rodar apenas os que faltam. Neste caso, o script C seria executado no cliente 1 e os scripts B e C no cliente 2. Em seguida, após executá-los, o próprio atualizador pode incluir os nomes na tabela de controle.

      Na verdade, essa é a lógica que utilizamos no sistema em que eu trabalho atualmente.

      Obrigado pelo comentário.
      Abraço!

  20. Boa noite André.
    No artigo você mencionou a dificuldade do upload do arquivo .exe no servidor ftp, seria desvantagem ter um servidor ftp próprio(residencial) para disponibilizar os arquivos para atualização?

    1. Boa tarde, Rafael, tudo bem?
      Desculpe-me pela demora. Acabei de chegar de uma viagem a trabalho.
      Rafael, ter um servidor FTP residencial é uma ótima opção, porém, em vista do custo dispendido, eu particularmente optaria por um servidor FTP pago. Dessa forma, o desenvolvedor não teria que se preocupar com a disponibilidade e desempenho do servidor, já que isso seria feito pela empresa contratada.

      Abraço!

  21. Boa Tarde Andre, muito bom, muito útil, muito didático esse tutorial. Parabéns.
    Tenho uma questão, como crio esse log de atualização que você mencionou??
    Obrigado.
    Att, Felipe.

    1. Boa noite, Felipe!
      O log de atualização pode ser criado com uma variável do tipo TextFile.
      A grosso modo, seria dessa forma:

      var
        Arquivo: TextFile;
        sCaminhoArquivo: string;
      begin
        // indica o caminho do arquivo de log
        sCaminhoArquivo := 'C:\Aplicativo\LogAtualizacao.txt';
        
        // aponta a variável TextFile para o arquivo
        AssignFile(Arquivo, sCaminhoArquivo);
        
        // se o arquivo não existir, é criado
        if not FileExists(sCaminhoArquivo) then
          Rewrite(Arquivo)
        // senão, ele é apenas aberto
        else
          Append(Arquivo);
          
        // escreve o texto no arquivo  
        WriteLn(Arquivo, 'Teste de log');
      
        // fecha o arquivo  
        CloseFile(Arquivo);
      end;

      No artigo sobre Singleton há um projeto de exemplo no qual faço uso de uma variável desse tipo:

      http://www.andrecelestino.com/delphi-design-patterns-singleton/

      Abraço!

    2. Olá, Felipe.
      Depende de qual informação você deseja escrever no log.
      No projeto pessoal que eu trabalho, por exemplo, adicionei as seguintes informações no arquivo de log:
      – Data e hora da atualização;
      – Nome do usuário que está conectado no sistema;
      – Número da versão atual do cliente;
      – Número da versão que será atualizada;
      – Resultado da execução do script no banco de dados;
      – Erros durante a atualização, caso existam.

      Portanto, distribui o código de gravação do log (WriteLn) em vários métodos diferentes.

      Abraço!

  22. Boa Noite André, obrigado pela atenção e respostas.
    Poderia ser exatamente como disse no comentário anterior, mas como sou bem iniciante e um entusiasta em Delphi e em pequenas e constantes evoluções, não sei como proceder com o tal nesse projeto, onde coloco, o que colocar, ficaria imensamente que me ajudasse mais uma vez.
    Obrigado.
    Att, Felipe.

    1. Certo, Felipe.
      Bom, em primeiro lugar, você precisa ter o projeto já desenvolvido para adicionarmos essa funcionalidade de criação de log.
      A partir daí, envie um e-mail para “contato@andrecelestino.com” para continuarmos conversando, ok?

      Abraço!

  23. Olá André, estou com o mesmo problema do Alexandre em relação a descompactar o arquivo baixado. Procurei algumas informações sobre a utilização do 7z com o Delphi, porém sem exito. Poderia me auxiliar nesse detalhe ?

    1. Claro, posso ajudá-lo, sim, Gabriel.
      Vou pedir mais informações por e-mail, ok?
      Abraço!

  24. Olá André, estou tentando lhe responder no e-mail, mas seu provedor esta recusando o envio. Segue abaixo o que foi solicitado:
    1) Delphi xe6.
    2) Windows 8.1
    3) O executável e a DLL estão na pasta junto ao executável do projeto corretamente.

    * Delphi, executável do projeto e 7z.exe estão estão sendo executado como administrador.
    Obrigado pela ajuda.

    1. Olá, Gabriel, acho que foi um problema temporário do servidor de e-mail.
      Vou entrar em contato com outra conta. Abraço!

  25. Olá André, passando para agradecer seu excelente post e deixar uma dica para galera!

    Fiz umas alterações e coloquei seu “atualizador” em produção para meus sistemas:
    – Atualização do .EXE
    – Envio de DLLs
    – Envio de layout de relatórios
    – Envio da Tabela do IBPT .CSV
    – Atualização de banco de dados

    Na questão do banco de dados, apesar de usar o Firebird instalado pelo meu próprio instalador, fiquei com receio em usar a ferramenta “isql”.
    Dei preferência em usar a combinação de componentes IBDataBase + IBScript, com isso mando um “script.sql” com todas as instruções e carrego no IBScript, depois é só mandar executar.

    Enfim, ficou bem tranquilo mesmo as atualizações, parabéns mais uma vez!

    1. Olá, William, como vai?
      Opa, fico feliz em saber que você conseguiu criar um atualizador que trabalha com vários arquivos! 🙂
      Muito obrigado pela sua colaboração! Tenho recebido algumas dúvidas sobre a execução de scripts na atualização automática e a sua dica será de grande valia! Na verdade, em um futuro breve pretendo “reescrever” essa série de artigos com algumas melhorias, e a execução de scripts está entre elas.

      Grande abraço!

    2. Parabéns pelo Autor e Site.
      Um post muito importante para qualquer aplicação.
      Com relação aos script, uso assim no meu ‘atualizador’ usando as dicas deste post.

      É simples mas resolve….

      /////FUNCAO PARA EXECUÇÃO DO SCRIPT
      function RunScript(aSQLConnection: TSQLConnection; aFileName: String): Boolean;
      var
        TD: TTransactionDesc;
        Arquivo: TextFile;
        Linha, StrTemp: String;
        Scripts: TStringList;
        i: integer;
      begin
        Result:= False;
        if not FileExists(aFileName) then Exit;
        
        Scripts:= TStringList.Create;
        StrTemp:= '';
        AssignFile(Arquivo, aFileName);
        Reset(Arquivo);
        while not Eof(Arquivo) do
        begin
          ReadLn(Arquivo, Linha);
          StrTemp:= StrTemp + Linha;
          if Pos(';', StrTemp) > 0 then
          begin
            Scripts.Add(StrTemp);
            StrTemp:= '';
          end
          else
            StrTemp:= StrTemp + ' ';
        end;
          CloseFile(Arquivo);
        
        TD.TransactionID:= 1;
        TD.IsolationLevel:= xilREADCOMMITTED;
        try
          if not aSQLConnection.Connected then
            aSQLConnection.Connected:= True;
        
          for i := 0 to (Scripts.Count – 1) do
          begin
            try
              aSQLConnection.StartTransaction(TD);
              aSQLConnection.ExecuteDirect(Scripts[i]);
              aSQLConnection.Commit(TD);
            except
              on E: Exception do
              begin
                aSQLConnection.Rollback(TD);
                ShowMessage(e.Message);
                Exit;
              end;
            end;
          end;
        except
          on E: Exception do
          begin
            ShowMessage(e.Message);
            Exit;
          end;
        end;
        Result:= True;
      end;
      
      ////// PROCEDIMENTO PARA EXECUTAR
      begin
         //SQLConnection1 vinculado a conexao da base
        if RunScript(SQLConnection1, 'C:\atualizacoes.sql') then
          ShowMessage(‘Atualização realizada com sucesso!’);
      end;
    3. Olá, Francisco, tudo bem?

      Muito obrigado pela sua contribuição.
      Há muitos desenvolvedores procurando orientações sobre como executar scripts dentro do atualizador. A sua dica irá ajudá-los!

      Grande abraço!

  26. Excelente!
    Um dos melhores artigos que já li. Parabéns!!
    Foi essencial para o que eu estava pensando em desenvolver.

    1. Olá, Diego! Opa, obrigado pelo feedback!
      Em breve vou fazer um remake desse artigo com algumas melhorias!
      Abraço!

  27. O melhor artigo que encontrei na internet, estou com duvida na questão ainda dos scripts de banco, estou estudando para criar ainda, mais desde já agradeço pelo tutorial excelente.

    1. Olá, William!
      Obrigado pelo feedback sobre o artigo!
      Em breve farei um “remake” dessa série de artigos com algumas melhorias, entre elas, uma orientação sobre a execução de scripts.

      Abraço!

  28. Amigo, boa tarde !
    Não consegui fazer o isql funcionar, será que poderia me ajudar?
    1)Criei um arquivo TESTE.BAT
    isql.exe C:\Banco\BANCO.GDB -i script.ql -u SYSDBA -p masterkey -o ArquivoLog.txt

    2)Criei um arquivo script.qsl
    UPDATE STATUS SET PCTRIB = 10;

    Executo o TESTE.BAT, não gera erro, mas também não atualiza o campo do scritp.

    Já coloquei um COMMIT no arquivo 1, no arquivo 2, e nem assim atualiza.

    Poderia me ajudar por favor?

    Obrigado

    1. Olá, Márcio, tudo bem?
      Notei que na linha de comando a extensão do arquivo está como “ql” ao invés de “sql”. Será que não pode ser isso?
      Aproveitando, em um dos meus projetos eu uso alguns parâmetros adicionais:

      isql Banco.fdb -m -b -i Script.sql -q -u SYSDBA -p masterkey -o Log.txt

      Experimente também dessa forma. Abraço!

  29. André, boa noite.

    Primeiramente, gostaria de lhe agradecer pelo retorno, muito bacana da sua parte.

    Apenas digitei errado no lhe enviar, o nome do arqui o script está escrito errado.

    Executando o comando que você me disse acima, ocorre a seguinte mensagem de erro:
    “Expected end of statement, encountered EOF”

    Você saberia o porquê desta mensagem?

    1. Olá, Elsimar, tudo bem?
      Infelizmente não tenho exemplos de execução de scripts em bases Microsoft Access. Trabalhei muito pouco com esse SGBD.
      Fiz uma pesquisa rápida e não encontrei uma forma de executar scripts por linha de comando no Access. Neste caso, o próprio atualizador automático deverá conectar-se à base de dados e executar as instruções desejadas através de um componente, como o TADOQuery.

      Abraço!

  30. Bom dia, mtu bom o post… só uma pergunta..

    antes do Get do .rar do ftp, n teria que colocar o IdFTP1.TransferType := ftBinary; ?

    t+

    1. Olá, Welson, tudo bem?
      Na segunda parte do artigo eu já menciono essa configuração em tempo de projeto.

      Abraço!

  31. Excelente artigo. Me ajudou bastante neste meu retorno ao Delphi depois de me aventurar em Java. Parabéns pelo excelente tutorial, mais mastigado impossível…. Parabéns.

    1. Olá, Jonismar!
      Agradeço muito pelo comentário! Fico feliz por saber que estou alcançando o meu principal objetivo com o blog, que é compartilhar conhecimento de uma forma descomplicada. 🙂
      A respeito dessa série de artigos, pretendo publicar uma atualização em breve, utilizando o protocolo HTTP e as classes nativas do Delphi para compactação e descompactação. Ficará bem melhor!

      Abraço!

    1. Obrigado, Luiz!
      Futuramente pretendo atualizar essa série de artigos para tirar proveito de algumas classes nativas das versões mais recentes do Delphi.
      Continue acompanhando!

      Abraço!

  32. Bela dica André, parabéns.
    Aqui na empresa temos um sistema que faz isso durante a abertura.
    Na propria tela de splash, ele verifica se há atualizações, e mostra o progresso e nome dos arquivos durante o download.

    Será que seria possivel fazer assim com o seu exemplo?

    1. Boa noite, Renan!
      Sim, é dessa forma que faço em um dos meus projetos. Acredito que a tela de inicialização do sistema é o “melhor momento” para executar a atualização automática.

      Abração!

  33. Boa tarde, André, tudo bem? Estou com um problema no arquivo “atualizacao.zip”. Eu coloquei um arquivo “Relatorio.rav” e o “Arquivo.Exe” dento dele.
    Porém, ao fazer a atualização, ele baixa o arquivo “atualizacao.zip” e extrai somente o “Relatorio.Rav”. E executável não está extraindo.
    Segue o código:

    // executa a linha de comando do 7zip para descompactar o arquivo baixado
    ShellExecute(0, nil, '7z',  PChar(' e -aoa ' +
      sNomeArquivoAtualizacao +' -o' + ObterDiretorioDoExecutavel), '', SW_SHOW);
    end;

    Poderia me ajudar por favor? Obrigado.

    1. Olá, Carlos, tudo bem?
      Por se tratar de um executável, acredito que o problema da descompactação possa estar relacionado com o serviço de segurança do Windows.
      Um teste que você pode fazer é executar este comando do 7-Zip diretamente no DOS (cmd). Se realmente estiver ocorrendo algum problema, o próprio comando vai retornar uma mensagem de erro na janela do DOS. Dessa forma será mais fácil identificar e solucionar o erro.

      Lembre-se também que existe a classe TZipFile nativa do Delphi que dispensa o uso de ferramentas de terceiros!

      Abraço!

  34. Olá, André. Meus parabéns pelo site e pela matéria. Eu estou tendo um probleminha, não com o código que funciona perfeitamente no meu computador de desenvolvimento que tem uma velocidade boa, mas no cliente onde o sistema é um windows XP. Na hora de checar se há atualização, ele fica quase uns 4 minutos depois da um erro de timeout.

    O que devo fazer?
    Muito Obrigado.

    1. Olá, Eduardo, tudo bem?
      Vou entrar em contato com você por e-mail.
      Abraço!

    2. André, muito obrigado.
      Eu desativei o antivírus, adicionei as exceções do firewall e mesmo assim não deu certo. Que mais posso tentar?
      Obrigado.

  35. André, Obrigado por todo conteúdo postado no seu blog,
    Estava comentando com um amigo, não tem um tutorial que o André poste que não consiga colocar pra funcionar / compilar sem erros, mais uma vez parabéns!

    Agora vamos lá, eu queria saber se você tem uma solução para comparar a estrutura de dois bancos de dados, a fim de incluir tabelas novas, procedures, triggers etc.

    Grato desde já;

    1. Bom dia, Joca, tudo bem?
      Em primeiro lugar, agradeço pelo seu feedback sobre os artigos! Muito obrigado mesmo!
      Joca, eu ainda não tive a oportunidade de desenvolver uma solução para comparar estruturas, mas, pelo que eu saiba, há duas formas:
      1) Por script, usando comandos como IF EXISTS / IF NOT EXISTS;
      2) Por codificação, através da criação de um wizard (um pequeno programa) para comparar cada tabela/coluna.

      Há ainda uma terceira forma, que consiste em armazenar os scripts que já foram executados no banco de dados. Na empresa em que trabalho, por exemplo, há uma tabela para registrar todos os scripts que são executados no cliente. Nós usamos essa tabela como parâmetro de comparação. Caso um script esteja na atualização, mas não consta nessa tabela, significa que é preciso executá-lo. Dessa forma, conseguimos manter todos os clientes na mesma “versão” do banco de dados.

      Abraço!

  36. Parabéns pela sequencia e estou aguardando ansioso pelo “remake” utilizando HTTP. Hoje eu ja utilizo classes nativas do Delphi para compactar e descompactar, mais tenho certeza que fará um ótimo novo artigo que terei o prazer de ler e “copiar” as boas ideias que você sempre tem.
    Gostaria de tirar uma dúvida contigo:
    Hoje eu baixo a sequencia de arquivos todas abertas, para não ficar “travado” muito tempo a conexão do GET e tals, porem estou tendo problema em alguns clientes. Uma pasta que tem muitos arquivos (exemplo Schemas de NFe que tem mais de 100) o idFTP baixa só uma parte deles, e depois desconecta e não segue com a atualização. Exemplo de um cliente que sempre baixa só os primeiros 50 arquivos de schema, depois ele não consegue baixar mais. Você já pegu um caso assim? Sabe me dizer se é configuração, problema ou comportamento normal e deveria compactar tudo antes de enviar?

    1. Olá, Emerson! Ótima pergunta!
      Acredito que o problema está relacionado com o timeout de leitura do componente TIdFTP.
      Vou pedir para que você altere as seguintes propriedades e refaça o teste:
      TransferType para ftBinary
      NATKeepAlive.UseKeepAlive para True
      NATKeepAlive.IdleTimeMS para 10000
      NATKeepAlive.IntervalMS para 1000

      Além disso, verifique também se a propriedade Readtimeout está com um valor muito baixo. O padrão é 60000 (60 segundos).

      Abraço!

  37. Boa Tarde, André, em primeiro lugar, preciso parabenizá-lo pelo artigo. Você ajuda muito aos mais perdidos no Delphi, alem é claro de ser um professor extremamente didático, fica fácil aprender assim.
    Bom, fiz uma teste com seu programa, e olha que curioso, na empresa onde trabalho o sistema funciona perfeitamente, já em casa não conecta de jeito algum, dá um erro chamado #10054 ao conectar. Fiz uma série de pesquisas na net, e cheguei a conclusão de que o problema seria com a minha internet pois não permite acesso a porta 21 (padrão). Interessante é que nem o FileZilla estava conectando até que mudei o campo criptografia no FileZilla para “USE SOMENTE FTP SIMPLES (INSEGURO)”, e pronto, o FileZilla começou a conectar ao FTP, já o Delphi mesmo assim não conecta.
    Alguma ideia do que eu possa fazer? pensei que o IdFtp tivesse essa configuração, mas não achei nada.
    Fico muito agradecido de você tiver alguma solução, mas se não tiver fico agradecido também.

    1. Olá, Márcio, tudo bem?
      Obrigado pelo feedback sobre o artigo 🙂
      A sua situação é interessante. Ainda não tenho uma resposta, mas vou entrar em contato com você, ok?
      Abraço!

  38. Muito bom o post e eu usei. Tudo funcionando perfeitamente até a parte de atualização do banco. Utilizei o RUNSCRIPT do amigo acima, só que ao executar script com SET TERM ^ ele dá erro. Teria alguma forma de contornar isso? Estou tentando criar triggers e necessito deste comando.

    Abraços e parabéns. No aguardo do novo POST.

    1. Olá, Pedro, boa noite!
      Na funcionalidade de atualização automática que codifiquei em um dos meus projetos, utilizei a ferramenta nativa de execução de scripts do Firebird, chamada ISQL, apresentada na dica 6 deste artigo. Para mais informações sobre este utilitário, acesse a documentação abaixo:

      https://www.firebirdsql.org/pdfmanual/html/isql.html

      Abraço!

  39. Ola André, seu trabalho é ótimo, gosto muito de acompanhar, mas uma coisa que ainda não achei nem na internet e que ainda não vi você colocar em pauta é a verificação de arquivos por CRC ou MD5.
    No caso meu projeto e de atualizar todo um aplicativo (vários arquivos), só que a verificação tinha q ser dessa maneira:
    Comparar MD5 ou CRC de um certo arquivo (teste.exe) que está no FTP, com o arquivo “texte.exe” que esta no cliente. Se MD5 for igual ele não baixa do FTP, caso for diferente ele baixa o arquivo do FTP substituindo o mesmo.
    Já tentei de tudo e ainda não tive resultado… queria saber se você tem alguma dica ou exemplo para isso.
    Agradeço deste já…
    Obrigado pela atenção, e uma ótima semana…

    1. Boa noite, Pedro. Ótima pergunta.
      Ainda não publiquei artigos sobre a verificação de hashes no Delphi, mas já vou anotar aqui.
      Bom, Pedro, a sua ideia é interessante, porém, para que você possa ler o MD5 de um arquivo que está no FTP, é preciso baixá-lo primeiro. Portanto, de qualquer forma, você sempre irá baixar o arquivo “teste.exe” para comparar os hashes. Analisando por este lado, talvez não seja uma boa solução.
      Por este motivo que, nos artigos dessa série, apresentei uma forma de utilizar apenas um arquivo texto. É um arquivo pequeno, rápido de ser baixado, e que contém a versão atual do sistema para fins de comparação. Somente quando as versões estiverem diferentes é que, de fato, o executável atualizado será baixado.

      Abraço!

  40. Boa noite André! Há anos venho desenvolvendo várias aplicações e só agora implementei essa parte de atualização automática, tenho meu próprio servidor de domínio, instalei o FileZilla Server, a comparação de versão faço via banco de dados, comparando a versão do aplicativo que está no cliente com a versão em uma tabela no banco de dados remoto em meu próprio servidor, e gerando as versões incrementais a cada build no próprio Delphi. Sua publicação ajudou muito, são pessoas como você que traz conhecimento pra todos, e assim cada desenvolvedor pode ir aprimorando seus sistemas e adquirir mais experiência, muito obrigado, abração!!!

    1. Olá, Matheus, boa noite!
      Sua solução ficou excelente! Comparar a versão com uma tabela no banco de dados é mais seguro e confiável do que um arquivo texto. Além disso, builds incrementais são um ótimo mecanismo neste contexto de atualização automática!
      Agradeço muito pelo seu comentário, Matheus. Espero continuar contribuindo para a comunidade Delphi sempre que possível!
      Abração!

  41. Parabéns pelo artigo, implementei em meu sistema. Só uma ressalva, tive que baixar a versão mais atual do 7z. A do link que você passou não funcionou no windows 10.

    1. Olá, Armando!
      Obrigado pela ressalva! Atualizei o artigo para que o link seja direcionado para a página oficial de downloads do 7-Zip.
      Grande abraço.

  42. Olá, mais uma vez André. Mais uma ressalva.
    Com as últimas atualizações do 7z existe o comando “x” que faz a extração completa do .rar, ou seja, podemos distribuir a pasta dos relatórios ou outras pastas do sistema tranquilamente.
    O meu comando ficou assim:

    ShellExecute(0, nil, '7z',  PWideChar(' x -aoa ' +   vArquivoAtualizacao + ' -o' + FDiretorioExe), '', SW_HIDE);
    1. Perfeito, Armando! Muito obrigado pela contribuição!
      Pretendo reescrever essa série de artigos em um futuro próximo para abordar essas melhorias. Essa dica que você enviou estará entre elas!
      Abração!

  43. Prezado André, boa noite!
    Você é uma pessoa especial, graças a Deus, a pessoa que compartilha conhecimentos, principalmente como meio de sobrevivência, é realmente uma virtude, uma nobreza, e por isso você se torna uma pessoa especial. Parabéns!!!

    Agora vamos à pergunta: caro André, desculpe minha ignorância na linguagem, poderia me informa qual a função que compara a versão local e a versão FTP para poder colocar no evento OnShow do formulário principal

    Desde já agradeço por todos os posts, tem ajudado bastante!!!

    1. Olá, José Raimundo! O código que faz a comparação é este:

      var
        nNumeroVersaoLocal, nNumeroVersaoFTP: smallint;
      begin
        nNumeroVersaoLocal := ObterNumeroVersaoLocal;
        nNumeroVersaoFTP := ObterNumeroVersaoFTP;
       
        if nNumeroVersaoLocal < nNumeroVersaoFTP then
        begin
          BaixarAtualizacao;
          DescompactarAtualizacao;
          AtualizarNumeroVersao;
       
          ShowMessage('O sistema foi atualizado com sucesso!');
        end
        else
          ShowMessage('O sistema já está atualizado.');
      end;

      A parte 2 dessa série de artigos tem mais detalhes!

      Abraço!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *