Введение
Не все пользователи должны получать одно и то же обновление одновременно — часто нужно разделить аудиторию на каналы: стабильный (stable) канал для большинства пользователей, получающих только тщательно протестированные релизы, и бета-канал (beta) для энтузиастов или внутренней команды, готовых тестировать более свежую, потенциально менее стабильную функциональность раньше остальных. В статье разберём, как организовать несколько каналов обновлений поверх механизма QtIFW, разобранного в предыдущей статье.
Концепция
QtIFW не имеет встроенной «из коробки» концепции множественных каналов обновления — реализация строится на организации нескольких отдельных репозиториев (один на канал) и переключении между ними через настройку RemoteRepositories в конфигурации приложения, обновляемой самим приложением программно в зависимости от того, к какому каналу подписан конкретный пользователь. Переключение канала пользователем обычно реализуется через настройку в самом приложении, которая модифицирует список используемых репозиториев maintenance tool перед следующей проверкой обновлений.
Пример кода: Проверка версий и
# Генерация ОТДЕЛЬНЫХ репозиториев для каждого канала из соответствующего набора пакетов
repogen -p packages_stable --update-new-components repo_stable
repogen -p packages_beta --update-new-components repo_beta
# Каждый репозиторий размещается на сервере по своему собственному пути
rsync -avz repo_stable/ user@updates.mycompany.com:/var/www/myapp-updates/stable/
rsync -avz repo_beta/ user@updates.mycompany.com:/var/www/myapp-updates/beta/
// updatechannelmanager.h — переключение канала обновлений из самого приложения
#pragma once
#include <QSettings>
#include <QProcess>
#include <QCoreApplication>
class UpdateChannelManager
{
public:
enum class Channel { Stable, Beta };
static Channel currentChannel()
{
const QString saved = QSettings().value("updates/channel", "stable").toString();
return (saved == "beta") ? Channel::Beta : Channel::Stable;
}
static void setChannel(Channel channel)
{
QSettings settings;
settings.setValue("updates/channel", channel == Channel::Beta ? "beta" : "stable");
// Перенастраиваем maintenance tool на использование нужного репозитория
const QString repositoryUrl = (channel == Channel::Beta)
? "https://updates.mycompany.com/myapp-updates/beta"
: "https://updates.mycompany.com/myapp-updates/stable";
const QString maintenanceToolPath = QCoreApplication::applicationDirPath()
+ "/MyAppUpdater";
QProcess::execute(maintenanceToolPath,
{"--addRepository", repositoryUrl, "--removeRepository", oppositeRepositoryUrl(channel)});
}
private:
static QString oppositeRepositoryUrl(Channel channel)
{
return (channel == Channel::Beta)
? "https://updates.mycompany.com/myapp-updates/stable"
: "https://updates.mycompany.com/myapp-updates/beta";
}
};
// Использование в настройках приложения
void SettingsDialog::onChannelComboBoxChanged(int index)
{
const auto newChannel = (index == 1) ? UpdateChannelManager::Channel::Beta
: UpdateChannelManager::Channel::Stable;
if (newChannel != UpdateChannelManager::currentChannel()) {
UpdateChannelManager::setChannel(newChannel);
QMessageBox::information(this, "Канал обновлений изменён",
"Изменение вступит в силу при следующей проверке обновлений.");
}
}
Пояснения к коду
Каждый канал обновлений генерируется как полностью независимый репозиторий через отдельный вызов repogen с собственным набором пакетов (packages_stable/packages_beta) — это позволяет, например, иметь в бета-репозитории компонент с более новой версией, чем в стабильном, при том что оба репозитория физически независимы и размещены по разным путям на сервере, не конфликтуя друг с другом.
UpdateChannelManager::setChannel() сохраняет выбор канала в QSettings для последующего отображения текущего состояния в интерфейсе настроек, и одновременно вызывает maintenance tool с флагами --addRepository/--removeRepository, программно изменяя список репозиториев, которые maintenance tool будет опрашивать при следующей проверке обновлений — этот вызов происходит через QProcess::execute() синхронно, поскольку операция достаточно быстрая (просто изменение локальной конфигурации, без сетевого обращения на этом этапе).
Подводные камни
- Переключение с бета на стабильный канал не означает автоматический «откат» уже установленных бета-версий компонентов до стабильной версии. Если пользователь уже обновился до бета-версии 1.2.0-beta, а затем переключился на стабильный канал, где максимальная доступная версия — 1.1.0, maintenance tool, скорее всего, не предложит «понижение» версии автоматически (зависит от конкретной настройки версионирования) — для поддержки реального откат-сценария нужна отдельная, явно спроектированная логика, не предоставляемая автоматически базовым механизмом QtIFW.
- Несогласованность номеров версий между каналами, если бета-версии не следуют чёткой, однозначно сравнимой схеме версионирования относительно стабильных релизов (например, использование суффикса
-beta, который не все инструменты сравнения версий корректно интерпретируют как «более новую, но менее стабильную» версию) — нужна явная, продуманная схема версионирования (semver с пре-релизными суффиксами), совместимая с тем, как именно QtIFW сравнивает строки версий внутриUpdates.xml. - Раздельное поддержание двух (или более) параллельных наборов пакетов для разных каналов увеличивает нагрузку на процесс релиза. Команде нужно явно решить, является ли бета-канал просто «более ранним» срезом того же кода (что технически проще поддерживать, синхронизируя пакеты автоматически из CI) или полноценной отдельной веткой с собственной независимой логикой возможных отличий — выбор архитектуры процесса релиза существенно влияет на сложность поддержки множественных каналов в долгосрочной перспективе.
- Отсутствие явного предупреждения пользователю о потенциальной нестабильности бета-канала перед переключением — пользователь, случайно выбравший бета-канал из любопытства, может столкнуться с реальными багами и составить негативное мнение о качестве продукта в целом, не понимая, что находится на тестовом канале — UI переключения каналов должен явно и недвусмысленно предупреждать о характере и рисках выбора нестабильного канала, как частично показано во всплывающем сообщении в примере, но в реальном продукте формулировка должна быть заметно более явной о потенциальных рисках.