Автообновление программы через MSSQL server

Источник: habrahabr
Zerpico

Дабы не бегать по своей работе к каждому человеку который использует мою программу разумно сделать автообновление, которое будет обновлять программу, если загрузить новую версию на сервер. Испробовав множество способов, нашел самый простой в использовании (хотя и не самый правильный)
Итак алгоритм:

  1. Программа при включении проверяет на сервере последнюю версию.
  2. Если на сервере выше текушей то скачиваем Zip-архив c программой.
  3. Переименовываем файл приложения на другое (ради бэкапа и доступности к файлу), например с program.exe на program.backup.
  4. Распаковываем архив заменяя файлы в папке.
  5. Удаляем архив с обновлением.
  6. Перезапускам программу.


Но перед всем этим сначала создадим таблицу Updater (например) на MSSQL с полями:
name - тип varchar;
version -тип varchar;
files - тип varbinary(max) (или любое другое blob-поле).
Лучше сразу создать в таблице запись, где в поле name будет program.
Добавить эту процедуру в код для извлечения номера версии программы:

function TForm1.GetMyVersion: string;
type
  TVerInfo=packed record
    Nevazhno: array[0..47] of byte; // ненужные нам 48 байт
    Minor,Major,Build,Release: word; // а тут версия
  end;
var
  s:TResourceStream;
  v:TVerInfo;
begin
  result:='';
  try
    s:=TResourceStream.Create(HInstance,'#1',RT_VERSION); // достаём ресурс
    if s.Size>0 then begin
      s.Read(v,SizeOf(v)); // читаем нужные нам байты
      result:=IntToStr(v.Major)+IntToStr(v.Minor)+ // вот и версия...
              IntToStr(v.Release)+IntToStr(v.Build);
    end;
  s.Free;
  except; end;
end;

Теперь нам понадобится библиотека для распаковки архивов, ведь я буду хранить обновление в zip архиве, использую библиотеку SevenZip, добавляем в Delphi в library путь до исходников библиотеки.
Теперь нам надо закачать сам архив с новой версией программы в базу данных. Я сделал форму с загрузкой обновления на сервер:
image
по открытию OpenDialog думаю всё понятно переписываем путь в edit

if OpenDialog1.Execute then
cxButtonEdit1.Text:=OpenDialog1.FileName;

Номер версии лучше вписывать в MaskEdit с маской - !9.9.9.0;1;_
По кнопке сохранить закачиваем файл процедурой:

var blobF: TBlobField;
begin
if not FileExists(OpenDialog1.FileName) then
 begin
  ShowMessage('Файл не найден!');
  exit;
 end else cxButtonEdit1.Text:=OpenDialog1.FileName;

try

  ADOTable1.TableName:=Updater; //выбираем таблицу в которой будем хранить архив
  ADOTable1.Close;
  ADOTable1.Open;
  //ищем поле name с записью program
  ADOTable1.Filtered := False;
  ADOTable1.Filter := 'name='+#39+'program'+#39; 
  ADOTable1.Filtered := True;
  ADOTable1.Edit;

  blobF := ADOTable1.FieldByName('files') as TBlobField;
  blobF.LoadFromFile(OpenDialog1.FileName);
  ADOTable1.FieldByName('version').AsString:=cxMaskEdit1.Text;
  ADOTable1.Post;
except
   Showmessage('Ошибка загрузки!');
end;

Это немного неправильный метод, лучше всего сделать отдельным потоком, особенно если архив большого размера.

Необходимо добавить в Uses - SevenZip и ShellAPI.
Теперь создадим самую важную процедуру обновления, назовав её Update:

Procedure TForm1.Update;
var path,fullpath,Ourversion,LastVersion:string;
blobF: TBlobField;
begin
//получем номер версии на сервере
adoquery4.Active:=false;
adoquery4.sql.text:='SELECT version FROM [dbo].[Updater] WHERE name='+#39+'program'+#39;
adoquery4.Active:=true;

//добавляем номера версии на сервере и текушей в переменые, и убираем точки
Ourversion:=GetMyVersion;

LastVersion:=adoquery4.FieldByName('version').Value;
while pos('.',LastVersion)<>0 do
delete(LastVersion,pos('.',LastVersion),1);

//сравниваем версии если текушая меньше чем на сервере то спрашиваем обновлять или нет
if strtoint(LastVersion)>strtoint(Ourversion) then
If messageBox(Handle,'Появилось свежая версия программы. Обновить?','Обновить?',
mb_YesNo or mb_iconquestion)=mrYes then

 try
  path:=ExtractFileDir(ParamStr(0));
  if  FileExists(path+'\Program.backup') then DeleteFile(path+'\Project2.backup');
  RenameFile(path+'\Program.exe', path+'\Program.backup'); //переменовываем оригинальный файл
  //фильтруем
  //ADOTable1 указывает на таблицу Updater
  ADOTable1.Close;
  ADOTable1.Open;
  ADOTable1.Filtered := False;
  ADOTable1.Filter := 'name='+#39+'program'+#39;
  ADOTable1.Filtered := True;
  ADOTable1.Active:=true;
  //скачиваем файл
  blobF := ADOTable1.FieldByName('files') as TBlobField;
  if blobF.Value = nil then Exit;
  blobF.SaveToFile(path+'\Update_ARMTitan.zip');
  ADOTable1.Active:=false;

  // Распаковывает файлы
  with CreateInArchive(CLSID_CFormatZip) do
   begin
     OpenFile(ExtractFilePath(ParamStr(0)) + 'Update_ARMTitan.zip');
     ExtractTo(ExtractFilePath(ParamStr(0)));
     Close;
   end;
    //удаляем архив который скачали
    DeleteFile(path+'\Update_ARMTitan.zip');
   //перезапуск программы
   fullpath:=path+'\Project2.exe';
   ShellExecute(0, 'open', PWideChar(fullpath), '', nil, SW_SHOW);
   //WinExec(PAnsiChar(fullpath), SW_SHOW); 
   Application.Terminate; // or: Close;
 finally
 end;
end;

Здесь тоже есть недочет, распаковывать лучше в отдельности каждый файл и проверять на наличие ошибки, а не весь архив целиком как сделал я.
Тут есть 2 мелочи которые надо соблюдать: 
1. В директории с программой должен быть файл 7z.dll, поэтому лучше сделать проверку:

if  FileExists(path+'\7z.dll') then 
begin
   ShowMessage('Отсутствует файл 7z.dll');
   exit;
end;

или еще как то по другому.
2. В архиве с обновлением не должен присутствовать файл 7z.dll, так как он используется, или же распаковать всё по отдельности как я писал выше и не распаковывать только этот файл.

Вот и всё. Главное не забыть перед компиляцией готовой программы для обновления прежде чем добавить её в архив, изменить номер версии программы.

В этом способе есть некоторые недочёты и я это знаю. Но пока что я им пользуюсь и проблем не испытывал, хотя доработать всё же следует.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=31294