Введение
Приложение обновляется, и вместе с ним должна обновляться справка — но пользователь может пропустить несколько версий, обновляясь сразу с очень старой на текущую, и нужно гарантировать, что после обновления приложения он видит актуальную справку, а не устаревшую версию, оставшуюся от предыдущей установки. В статье разберём стратегии версионирования namespace и практический механизм обновления зарегистрированной справки при запуске новой версии приложения.
Концепция
Поскольку namespace в .qhp/.qch уникально идентифицирует справочник для QHelpEngine, есть два основных подхода к версионированию: включать номер версии прямо в namespace (com.mycompany.myapp.1.0, com.mycompany.myapp.2.0), что делает справку разных версий технически разными, независимыми справочниками, которые могут существовать в коллекции одновременно; либо использовать постоянный namespace без версии (com.mycompany.myapp) и явно заменять зарегистрированный .qch на новый при каждом обновлении приложения, используя unregisterDocumentation() перед повторной registerDocumentation().
Второй подход проще для большинства приложений (где старые версии справки пользователю не нужны после обновления) и избегает накопления неиспользуемых записей в файле коллекции — именно его мы детально реализуем в примере.
Пример кода: Версионирование и обновление
#pragma once
#include <QHelpEngine>
#include <QSettings>
#include <QCoreApplication>
#include <QDebug>
class HelpUpdater
{
public:
static bool ensureUpToDateHelp(QHelpEngine &engine, const QString &qchResourcePath,
const QString &expectedNamespace)
{
QSettings settings;
const QString lastRegisteredVersion = settings.value("help/registeredVersion").toString();
const QString currentAppVersion = QCoreApplication::applicationVersion();
const bool alreadyRegistered = engine.registeredDocumentations().contains(expectedNamespace);
if (alreadyRegistered && lastRegisteredVersion == currentAppVersion) {
qDebug() << "Справка актуальна (версия" << currentAppVersion << ")";
return true;
}
// Если справка уже зарегистрирована, но устарела — снимаем регистрацию
// перед добавлением новой версии того же namespace
if (alreadyRegistered) {
qDebug() << "Обновление справки с версии" << lastRegisteredVersion
<< "на" << currentAppVersion;
if (!engine.unregisterDocumentation(expectedNamespace)) {
qWarning() << "Не удалось снять регистрацию старой версии справки:"
<< engine.error();
return false;
}
}
if (!engine.registerDocumentation(qchResourcePath)) {
qWarning() << "Не удалось зарегистрировать справку:" << engine.error();
return false;
}
settings.setValue("help/registeredVersion", currentAppVersion);
qDebug() << "Справка успешно обновлена до версии" << currentAppVersion;
return true;
}
};
// Использование при старте приложения
#include "helpupdater.h"
#include <QCoreApplication>
int main(int argc, char *argv[])
{
QCoreApplication::setApplicationVersion("2.3.0");
QCoreApplication app(argc, argv);
QHelpEngine helpEngine(getCollectionFilePath());
helpEngine.setupData();
HelpUpdater::ensureUpToDateHelp(helpEngine, ":/docs/myapp.qch",
"com.mycompany.myapp");
return app.exec();
}
Пояснения к коду
ensureUpToDateHelp() хранит версию приложения, для которой справка была последний раз зарегистрирована, в QSettings — при следующем запуске сравнивается текущая версия приложения (QCoreApplication::applicationVersion(), которую важно устанавливать явно при сборке релиза) с сохранённой. Если версии совпадают, повторная регистрация не требуется — это избегает ненужной работы при каждом обычном запуске приложения между обновлениями.
Если версии различаются (приложение было обновлено) и справочник уже зарегистрирован под тем же постоянным namespace, старая запись явно снимается через unregisterDocumentation() перед регистрацией новой — это гарантирует, что в коллекции остаётся ровно одна, актуальная версия справки для данного namespace, а не накопление устаревших записей или конфликт версий одного и того же идентификатора.
Подводные камни
- Несоответствие
QCoreApplication::applicationVersion()фактической версии .qch-файла, если разработчик обновил содержимое справки, но забыл синхронно поднять версию приложения (или наоборот) — логика обновления в примере опирается именно на версию приложения как индикатор необходимости обновления справки, и рассинхронизация этих двух версионных схем может привести к тому, что обновлённый контент справки не будет переустановлен, хотя файл .qch уже изменился. - Параллельный запуск нескольких экземпляров приложения сразу после обновления, каждый из которых пытается одновременно выполнить
unregisterDocumentation()/registerDocumentation()для общего файла коллекции — это может привести к гонке при обращении к одному файлу .qhc с диска, особенно если коллекция расположена в общей для всех пользователей системы директории, а не в пользовательском профиле. - Откат версии приложения (даунгрейд) без соответствующей логики откатывания справки. Если пользователь по какой-то причине переустанавливает более старую версию приложения после того, как уже использовал более новую, сохранённое в
QSettingsзначение версии «новее», чем фактическая версия выполняемого приложения, и логикаlastRegisteredVersion == currentAppVersionопределит несовпадение и попытается зарегистрировать .qch, поставляемый именно с этой (более старой) версией инсталлятора — что в большинстве случаев корректно, но стоит явно протестировать сценарий даунгрейда, а не только обновления вперёд. - Хранение версии в
QSettingsбез учёта того, что несколько разных профилей пользователей на одной машине могут иметь разные значения настроек — если коллекция справки общая на уровне системы, а версия вQSettings— на уровне пользователя, разные пользователи могут получить противоречивую картину состояния регистрации справки между параллельными профилями.