Автообновление программы через 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=35619