Свой велосипед я сделал и опробовал. Пашет. Если кому это будет интересно, то
опишу свою технологию автообновления. Ничего необычного в ней конечно нет. Разве что есть некоторые тонкости, которые специфичны именно для моей программы.
Для начала опишу вкратце алгоритм пассивного автообновления.
В программе есть окно настроек, которое позволяет задавать некоторые параметры автообновления:
- включить/выключить автообновление;
- периодичность автообновления (каждый день, через неделю, через месяц);
Также в программе есть отдельная опция в меню "Помощь\Обновить". Это ручной вызов того же алгоритма автообновления. Используется, если автообновление в настройках выключено.
Далее, при вызове (автоматическом или ручном) функции автообновления программа делает следующее:
1) проверяет наличие доступа к сети (активного Интернет-соединения);
2) если соединение найдено, то скачивается с известного адреса файл "update";
3) при успешном скачивании, файл сохраняется во временном файле временной папки;
4) осуществляется парсинг файла на предмет наличия записи для конкретно нашей программы;
5) если метка программы найдена, то считываются поля версии доступного обновления;
6) делается сравнение, если новая версия больше текущей, то пользователю выпадает окно с вопросом о доступности обновления;
7) при положительном ответе, просим пользователя указать куда сохранить файл обновления;
8 ) скачиваем сам файл по ссылке, указанной в файле "update";
9) удаляем файл "update".
Что же в моём варианте специфичного. А это почти всё.
1. Я использую нестандартный для VB6 формат записи версии в виде: Major.Minor.Revision.Build. Для бейсика он нестандартный, а вот для всего остального мира это не так, поэтому я использую именно этот формат, хотя это требует дополнительного кода для обслуживания (и addin'а vbAdvance).
2. Я использую не совсем обычный опять же для VB6 формат файла "update". Опять же, уточню, что необычен он именно для бейсика, а в современном мире очень даже обычен. Формат таков (UTF-8, кстати):
- Код: Выделить всё
[
{
"ProgID": "{43CE9E0A-3657-4258-B573-8B18F6AC3B42}",
"Major": 1,
"Minor": 4,
"Revision": 0,
"Build": 0
"DownloadLink": "http:\/\/x-uni-x.chat.ru\/cop\/Configurator_1.3.0.160_setup.zip"
}
]
Это экспериментальный вариант. Цифры тут от балды для проверки. Как Вы возможно узнали, да, это
JSON. Простой универсальный формат для обмена данными в современной сети. Для VB6 есть специальный класс, который позволяет работать с этим форматом:
VBA JSON project. Нужна библиотека
scrrun.dll.
Файл может быть обнаружен тут:
update .
Дело в том, что в моей программке этот формат используется, поэтому я использовал его и для "update"-файла. Вы, конечно, можете использовать хоть ini-файлы, хоть даже одну строку, которую потом будете сплитить на части. Дело Ваше, не суть.
3. Для закачки используется местный кирпич
"Качалка".
----------------------------------
Теперь чуть детальнее. Как Вы видите, я присвоил своей программе GUID. Его можно получить самостоятельно:
- Код: Выделить всё
Public Declare Function CoCreateGuid Lib "ole32.dll" (buffer As Byte) As Long
Public Function getGUID() As String
Dim buffer(0 To 15) As Byte
Dim s As String
Dim ret As Long
s = String$(128, 0)
' получает численный код
ret = CoCreateGuid(buffer(0))
' преобразуем его в текст,
' используя недокументированную функцию StrPtr
ret = StringFromGUID2(buffer(0), StrPtr(s), 128)
getGUID = Left$(s, ret - 1) ' отсекаем "хвост"
End Function
' ...
Debug.Print getGUID
'...
Либо пользоваться стандартными средствами. В Visual Studio есть специальный генератор GUID, которым я не помню даже пользовался или нет. Не суть, смысл сего действа в том, что мой "update" должен быть расширяемым и запись для моего приложения может находиться среди кучи других таких же записей для других программ. Мне хотелось сделать универсальную вещь, т.к. пишу я не только на бейсике, точнее говоря, на бейсике я почти вообще не пишу
Так, для общего развития. В "update"-файле может находится целый список моих программок и их обновлений. Они будут там через запятую (см. формат JSON).
Прежде чем начать, мы должны проверить наличие соединения. Делаю я это так:
- Код: Выделить всё
' Local system uses a modem to connect to the Internet
Public Const INTERNET_CONNECTION_MODEM = 1
' Local system uses a local area network to connect to the Internet
Public Const INTERNET_CONNECTION_LAN = 2
' Local system uses a proxy server to connect to the Internet
Public Const INTERNET_CONNECTION_PROXY = 4
' Local system's modem is busy with a non-Internet connection
Public Const INTERNET_CONNECTION_MODEM_BUSY = 8
' ***********************************
' * ФУНКЦИИ ДЛЯ РАБОТЫ С ИНТЕРНЕТ
' * ~~~~~~~~ ~~~~~~~~~ ~ ~~~~~~~~
' ***********************************
Public Declare Function InternetGetConnectedState _
Lib "wininet.dll" (ByRef lpdwFlags As Long, _
ByVal dwReserved As Long) As Long
----------------
' Проверяем интернет-соединение
Dim InternetConnected As Boolean
Dim Result As Boolean
Dim dwConnectionTypes As Long
StatusBar.Panels(1).Text = "Проверяю доступ к сети..."
dwConnectionTypes = INTERNET_CONNECTION_MODEM + INTERNET_CONNECTION_LAN + _
INTERNET_CONNECTION_PROXY
InternetConnected = InternetGetConnectedState(dwConnectionTypes, 0)
' TODO: Отображать процесс автообновления в статус строке
' Если имеется подключение к Интернет, то проверяем доступность сервера и
' файла автообновления
If InternetConnected = True Then
' ...
' ...
End If
Это работает. Уже не помню как и почему, но работает. Портировал с паскаля, когда-то там использовал эту функцию для индикации состояния "Онлайн".
Если соединение есть, то скачиваем файл "update":
- Код: Выделить всё
Private Function DoAutoUpdate(UpdateFileLink As String) As Boolean
'<EhHeader>
On Error GoTo DoAutoUpdate_Err
'</EhHeader>
Dim Result As Boolean
Result = False
' Создаём временный файл
Dim szBuffer As String, szTempFileName As String
Dim MAX_PATH As Long
Dim length As Integer
MAX_PATH = 255
szBuffer = Space(255)
' Получаем путь к временной папке
length = GetTempPath(MAX_PATH, szBuffer)
' Формируем путь к временному файлу
szTempFileName = Space(255)
GetTempFileName szBuffer, "cop", 0, szTempFileName
' Пытаемся скачать файл описания с сервера
Kachalka.DownloadToFile UpdateFileLink, szTempFileName
...
Конечно, не так просто скачиваем, а по особому. Нам нужно где-то сохранить файл. В принципе в данном варианте гораздо проще было бы сохранить файл в строку, но мы лёгких путей не ищем, мало ли что потом придумаем, а временный файл, который мы запрашиваем у системы - это вполне нормальное решение.
Файл закачали, теперь будем его парсить. Тут уж у кого как будет. Всё зависит от содержимого. У меня это выглядит так (черновой, но рабочий вариант):
- Код: Выделить всё
' Обрабатываем скачанный файл
Dim I As Integer
Dim CurrMajor As Integer, CurrMinor As Integer, CurrRevision As Integer, CurrBuild As Integer
Dim Major As Integer, Minor As Integer, Revision As Integer, Build As Integer
Dim sInputJson As String
Dim Version As String
Dim DownloadLink As String
Dim VerArr() As String
Dim p As Object
' Версия программы
Dim strFile As String
Dim udtFileInfo As FILEINFO
strFile = String(255, 0)
GetModuleFileName 0, strFile, 255
If DesignMode Then szTempFileName = "D:\Projects\vbasic\Projects\Configurator\update"
' Считываем файл и декодируем его
sInputJson = FromUTF8(LoadFromJSONFile(szTempFileName))
' Производим разбор данных из файла
Set p = JSON.parse(sInputJson)
Debug.Print "p.Count = " & CStr(p.Count)
' Ищем запись, имеющую необходимый GUID в поле ProgID
For I = 1 To p.Count
If (ProgramGUID = p.Item(I).Item("ProgID")) Then
' Считываем информацию о версии
Major = p.Item(I).Item("Major")
Minor = p.Item(I).Item("Minor")
Revision = p.Item(I).Item("Revision")
Build = p.Item(I).Item("Build")
DownloadLink = p.Item(I).Item("DownloadLink")
Debug.Print "Major = " & CStr(Major)
Debug.Print "Minor = " & CStr(Minor)
Debug.Print "Revision = " & CStr(Revision)
Debug.Print "Build = " & CStr(Build)
Debug.Print "DownloadLink = " & DownloadLink
' Узнаём свою текущую версию
If GetFileVersionInformation(strFile, udtFileInfo) = eNoVersion Then
CurrMajor = Major
CurrMinor = Minor
CurrRevision = Revision
CurrBuild = Build
Else
VerArr = Split(udtFileInfo.FileVersion, ".")
' Косвенно проверяем формат своей версии
If (UBound(VerArr) = 3) Then
CurrMajor = CInt(VerArr(0))
CurrMinor = CInt(VerArr(1))
CurrRevision = CInt(VerArr(2))
CurrBuild = CInt(VerArr(3))
Else
CurrMajor = Major
CurrMinor = Minor
CurrRevision = Revision
CurrBuild = Build
End If
End If
Dim NeedUpdate As Boolean
NeedUpdate = False
' Если текущая версия устарела, то выводим сообщение об этом
If CurrMajor >= Major Then
If CurrMinor >= Minor Then
If CurrRevision >= Revision Then
If CurrBuild >= Build Then
Else
NeedUpdate = True
End If
Else
NeedUpdate = True
End If
Else
NeedUpdate = True
End If
Else
NeedUpdate = True
End If
' Спрашиваем и качаем дистрибутив
If NeedUpdate = True Then
Dim vbRes As Integer
vbRes = MsgBox("Доступно обновление:" & _
vbCrLf & vbCrLf & _
"Новая версия: " & CStr(Major) & "." & CStr(Minor) & "." & CStr(Revision) & "." & CStr(Build) & vbCrLf & _
"Текущая версия: " & CStr(CurrMajor) & "." & CStr(CurrMinor) & "." & CStr(CurrRevision) & "." & CStr(CurrBuild) & _
vbCrLf & "Загрузить обновление?", _
vbYesNo + vbQuestion, APP_NAME)
Select Case vbRes
Case vbYes
Dim FileName As String
SaveFileDialog.FileName = MiscExtractPathName(DownloadLink, False, "/")
SaveFileDialog.DialogTitle = "Сохранить файл..."
SaveFileDialog.DefaultExt = ""
SaveFileDialog.Filter = "Все файлы (*.*)|*.*"
SaveFileDialog.FilterIndex = 1
SaveFileDialog.MaxFileSize = 32767
SaveFileDialog.InitDir = CurrentDir
SaveFileDialog.CancelError = True
SaveFileDialog.ShowSave
FileName = SaveFileDialog.FileName
If FileName <> "" Then
' Пытаемся скачать файл
Kachalka.DownloadToFile DownloadLink, FileName
End If
Case vbNo
End Select
End If
Result = True
End If
Next
' Удаляем временный файл
If DoesFileExist(szTempFileName) Then DeleteFile szTempFileName
Set p = Nothing
DoAutoUpdate = Result
Парсим, сверяем GUID, считываем поля, узнаём собственные поля версии, проверяем корректность формата версии, сравниваем версии, спрашиваем пользователя о дальнейшем действии, спрашиваем место куда сохранить, скачиваем.
Вот собственно и всё, что я хотел знать. За кадром я оставил многие функции. Когда отлажу свой вариант автообновления, то, наверное, выложу свой "кирпич", который бы моделировал это дело. Хотелось бы, конечно, освоить запуск скачанного файла. Также у меня использован нехороший вариант скачивания в главном потоке, но эти мелочи каждый может доделать сам, используя тему про "качалку".
У меня также остались мысли по доработке формата файла "upgrade". Дело в том, что хранить версию можно по-разному и не плохо было бы иметь поле типа версии и обрабатывать остальные поля в зависимости от типа (бывает в версии включают дату). Если делать свой класс, то обработку сравнения особых случаев можно отдавать пользователю, но это на будущее.
П.С. Я забыл сказать, что у меня на форме есть специальный таймер, с помощью которого я запускаю автообновление. Не знаю как у других, у меня пробуется скачка каждый час, если включено автообновление и выбрана периодичность - каждый день. Для других периодичностей технология другя - мы запоминаем дату попытки и при каждом запуске приложения проверяем разницу между текущей и сохранённой датами. Превысили период - запускаем таймер. И так по кругу. Хотя до сравнения дат я ещё не дошёл.