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

Дабы не бегать по своей работе к каждому человеку который использует мою программу разумно сделать автообновление, которое будет обновлять программу, если загрузить новую версию на сервер. Испробовав множество способов, нашел самый простой в использовании (хотя и не самый правильный)
Итак алгоритм:
  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, так как он используется, или же распаковать всё по отдельности как я писал выше и не распаковывать только этот файл.

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

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

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 18

    +3
    А использовать для этого MS SQL Server — это не overkill, нет?

    Ну и в целом, статья не очень глубокая, мягко говоря.
      0
      Оверкилл, конечно.
      0
      Я делаю по другому: храню в табличке на SQL сервере все файлы программы, а потом когда нужно поменять один шаблон например, то добавляю новую запись в эту таблицу, а старый файл деактивирую в таблице.
      В итоге у меня остается история версий и если что можно быстро откатить изменения в базе и клиенты обновятся обратно на старые версии файлов.
        0
        и как много времени уходит чтобы залить все файлы в базу если вдруг они все обновились? Я тоже об этом думал, но с архивом как то попроще выходит.
          0
          Немного. обычно обновляется само приложение и пара-тройка шаблонов.
          Все это делает через админский интерфейс за 1 минуту.
        +3
        За названия объектов, как-то ADOTable1 или adoquery4, надо бить канделябром. Форматирование исходника тоже, мягко говоря, повергает в уныние.
          0
          Ясненько, научусь так делать, но мне просто в цифрах легче ориентироваться чем в названиях.
          +1
          Жесть. А забирать по http/ftp уже не вариант? Самый простой вариант — прога при загрузке лезет в инет (в том числе через прокси), качает какой-нибудь xml, да хоть ini и при наличии новой версии качает инсталлятор, запускает его и закрывается сама. Инсталлятор по окончанию запускает обновленную программу.
            0
            Это в рабочей сетке, так что интернет не вариант, тем более там такие тети сидят что впадают в панику, увидев окно где больше одной кнопки. Поэтому хотел сделать обновление в тихом режиме всего лишь ответив на вопрос: «Обновить программу? Да/Нет»
              0
              А что, кроме интернета других http не бывает?
                0
                Это как? Рабочая сеть не пускает в интернет куда попало, тем более так быстрее явно будет обновляться, пока из интернета скачается на скорости 10 кб/с. И еще файл ведь где надо хранить чтобы к нему был прямо доступ.
                  0
                  Так я еще раз спрашиваю: кроме интернета, других http вы не знаете? Свой сервер в сети развернуть не судьба?
                    0
                    к серверам имею только доступ к Базам
                      0
                      Ну так получите больше. То, что вы делаете — все равно нарушение безопасности.
            0
            А для delphi правда нельзя использовать технологии вроде ClickOnce?
              +1
              Вообще освоить Local Update Publishing при наличии WSUS было бы быстрее, ну и плюс сохранилось бы некоторое единообразие установки для пользователя. Внутри ведь по сути — тоже SQL-база со ссылками на файлы и само хранилище файлов, но зато уже готовое.
                0
                Самые очевидные ляпы:

                1. Ни одна ошибка не обрабатывается (блоки finally/end пустые либо пишут в стиле КО). В случае, когда ошибка произойдет после переименования программы, но до полной установки обновления (свет мигнул и свич по дороге завис) — программа в следующий раз просто не запустится, ибо ее уже не будет.
                2. Именование переменных (точнее его отсутствие) — тихий ужас.
                3. В случае запуска программы с параметрами после обновления программа запускается уже без них.
                  +1
                  Школокод на хабре? No way!
                  Вопрос по сути — зачем нужна вот эта магия:
                    TVerInfo=packed record
                      Nevazhno: array[0..47] of byte; // ненужные нам 48 байт
                      Minor,Major,Build,Release: word; // а тут версия
                    end;
                  

                  Only users with full accounts can post comments. Log in, please.