Версия для печати


Обновление программы самой программой
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1138

Евгений Бабенко
дата публикации 31-05-2005 04:41

Обновление программы самой программой
Условия работы:
Локальная сеть(Win2000, WinNt4), сервер СУБД (Oracle, SQL Server ...), множество клиентов, работающих с базой.
Постановка задачи:
Автоматическое обновление программы клиента при внесении каких либо изменений в эти программы.

У администраторов - программистов, которые впервые сталкиваются с подобной проблемой, первое желание - заставить программу саму себя обновлять. Это законное желание. Нет промежуточного программного обеспечения, которое, в свою очередь, необходимо администрировать. Проблема в том, что программа не может удалить сама себя. В свое время, столкнувшись с этой проблемой, мы у себя на предприятии решили эту проблему стандартным способом - скриптами. А недавно я обнаружил интересную особенность ОСей и сделал это способом, максимально приближенным к "идеалу".

В обоих случаях, для ускорения закачки файла с сервера, программы были перекомпилированы с использование Runtime Packages и после этого упакованы упаковщиком Exe - файлов. В итоге размеры полученных файлов не превышал 300 К.

Способ №1. Обновление с помощью VB скрипта.

Создаем на сервере папку с доступом для группы пользователей, использующих наши программы. Например \\Server1\ExeLib$ (приставка с $ позволит скрыть папку из сетевого окружения. Для того, чтобы не привязываться к конкретному имени сервера, мы создали линк с помощью Distributed File System (Dfs) : \\VirtualServer\ExeLib. Теперь физическое местоположение программ не имеет значение. При падении сервера мы просто копируем папку на другой сервер и делаем соответствующие изменения в Active Directory. Или это делает наш администратор домена. В папку ExeLib складываем exe-файлы. Там же создаем папку VBS, в которую поместим наши скрипты. Для каждого exe - файла свой скрипт. Вот пример скрипта:

Dim fs, f1, f2
Dim WshShell 

const SPatch = "\\VirtualServer\ExeLib\"
const DPatch = "C:\Client\"
const SExe = "Prog1.Exe"

Set fs = CreateObject("Scripting.FileSystemObject")
Set f1 = fs.getfile(SPatch+SExe)
Set WshShell = WScript.CreateObject("WScript.Shell") 
    
 If fs.FileExists(DPatch+SExe) Then
     Set f2 = fs.getfile(DPatch+SExe)
     If f1.DateLastModified > f2.DateLastModified Then
         fs.CopyFile SPatch+SExe, DPatch, True
     End If
Else
    fs.CopyFile SPatch+SExe, DPatch, True
End If
WshShell.Run (DPatch+SExe)

Скрипт проверяет дату последнего изменения exe - файла. Если файл локально не существует или на сервере лежит более новый файл, скрипт копирует файл на клиентский компьютер. После этого программа запускается на выполнение. На клиенте все наши программы должны лежать в одной папке. Желательно на диске C:, т.к. в домене могут встречаться машины с дисками малого размера. Для WinNt4 необходимо проверить наличие WScript.exe. У клиентов должны быть права на запуск WScript.exe. Скрипт запускается из папки \\VirtualServer\ExeLib\VBS, т.е. сервера. Это позволит нам легко модифицировать скрипт и не заботиться об обновлении самого скрипта. Например, добавить код для вызова другого скрипта, в котором мы прописываем обновление bpl - файлов.

Способ №2. Обновление программы самой программой.

Если вы запустите программу и после этого попытаетесь переименовать exe - файл, никаких возражений со стороны операционный системы не последует. Программа после такой операции продолжает работать без видимых проблем. Я, во всяко случае, проблем не обнаружил. Этот факт и используется в коде, приведенном ниже.

unit uUpdateApp;

interface
uses Classes, Windows, SysUtils, Forms;

type
  TUpdateApp=class(TThread)
  protected
     procedure Execute; override;
  end;

implementation

{ TUpdateApp }

procedure TUpdateApp.Execute;
var iFileName: String;
     iFileNameBak: String;
     iFileNameR: String;
     iLDate, iRDate: TDateTime;
begin
  Sleep(100);
  iFileName:=Application.ExeName;
  iFileNameBak:=iFileName+'.bak';
  if FileExists(iFileNameBak) then DeleteFile(iFileNameBak);
  if FileExists(iFileNameBak+'1') then DeleteFile(iFileNameBak+'1');
  iFileNameR:='\\VirtualServer\ExeLib\+ExtractFileName(iFileName);
  if not FileExists(iFileNameR) then Exit;
  iLDate:=FileDateToDateTime(FileAge(iFileName));
  iRDate:=FileDateToDateTime(FileAge(iFileNameR));
  if iLDate>=iRDate then Exit;
  if not CopyFile(PChar(iFileNameR),PChar(iFileNameBak),false) then  Exit;
  Sleep(1000);
  if not MoveFile(PChar(iFileName),PChar(iFileNameBak+'1')) then  Exit;
  Sleep(100);
  if not MoveFile(PChar(iFileNameBak),PChar(iFileName)) then  Exit;
  Sleep(100);
  if not MoveFile(PChar(iFileNameBak+'1'),PChar(iFileNameBak)) then  Exit;
end;

initialization
   TUpdateApp.Create(false);
end.

Модуль просто подключается к проекту. В секции инициализации запускаем поток. В потоке делаем следующее:
  1. Проверяем наличие bak файла, который мог остаться от предыдущего обновления и если находим - удаляем.
  2. Сравниваем дату изменения локального файла и файла, находящегося на сервере.
  3. Если необходимо обновление - копируем файл с сервера в файл "bak1". Еще один промежуточный файл используется для уменьшения вероятности нарваться на сбой в программе, когда у нас не окажется в папке файла с оригинальным названием.
    1. Переименовываем оригинал в файл с расширением "bak" .
    2. Восстанавливаем оригинал из файла "bak1"

Далее - на любителя. Можно попытаться предупредить пользователя и заставить его перезапустить программу. Или оставить все, как есть. Этот способ проверен на WinNt4 и Win2000. Прав особых пользователям не надо.