2026-07-14 08:39:41 +00:00
#include "UpdateLogic.h"
2026-07-10 02:45:13 +00:00
#include "ConfigHelper.h"
#include <QDebug>
2026-07-14 08:39:41 +00:00
#include <QJsonArray>
#include <QApplication>
#include <QDir>
#include <QSaveFile>
#include "PolicyHelper.h"
#include "LocalStateHelper.h"
2026-07-10 02:45:13 +00:00
UpdateLogic :: UpdateLogic ( QObject * parent )
: QObject ( parent )
{
ConfigHelper & cfg = ConfigHelper :: instance ();
m_serverAddr = cfg . getValue ( "Server" , "api_base_url" );
m_appId = cfg . getValue ( "App" , "app_id" );
m_curVer = cfg . getValue ( "App" , "current_version" );
m_channel = cfg . getValue ( "App" , "channel" );
// Debug print config
qDebug () << "Read server addr:" << m_serverAddr ;
qDebug () << "Read app id:" << m_appId ;
}
void UpdateLogic :: checkUpdate ()
{
if ( m_serverAddr . isEmpty () || m_appId . isEmpty () || m_curVer . isEmpty () || m_channel . isEmpty ())
{
qDebug () << "Config incomplete, abort update check" ;
m_needUpdate = false ;
return ;
}
QString url = m_serverAddr + "/api/v1/update/check" ;
QJsonObject body ;
body [ "app_id" ] = m_appId ;
body [ "current_version" ] = m_curVer ;
body [ "channel" ] = m_channel ;
const int configuredProtocol = ConfigHelper :: instance (). getValue ( "App" , "client_protocol" ). toInt ();
body [ "client_protocol" ] = qMax ( 3 , configuredProtocol );
m_http . postRequest ( url , body , [ this ]( int code , const QJsonObject & resp )
{
qDebug () << "Check update HTTP code:" << code ;
m_lastStatusCode = code ;
m_checkResp = resp ;
m_networkOk = ( code == 200 );
if ( code == 200 )
{
const QString appDir = QApplication :: applicationDirPath ();
PolicyHelper onlinePolicy ( appDir );
LocalStateHelper state ( appDir );
const bool stateLoaded = state . loadState ();
const bool policyValid = onlinePolicy . loadPolicyObject (
resp . value ( "policy" ). toObject (), resp . value ( "policy_text" ). toString ());
if ( ! policyValid || ! stateLoaded
|| state . isPolicySeqRolledBack ( onlinePolicy . policySeq ())
|| ! onlinePolicy . savePolicy ())
{
qDebug () << "Online policy rejected:" << onlinePolicy . errorString ();
m_networkOk = false ;
m_needUpdate = false ;
return ;
}
state . updateOnlineVerified ( onlinePolicy . policySeq ());
if ( ! state . saveState ())
{
qDebug () << "Cannot persist online policy state" ;
m_networkOk = false ;
m_needUpdate = false ;
return ;
}
m_needUpdate = resp [ "need_update" ]. toBool ();
m_latestVer = resp [ "latest_version" ]. toString ();
qDebug () << "Need update:" << m_needUpdate ;
qDebug () << "Latest version:" << m_latestVer ;
qDebug () << "Policy seq:" << onlinePolicy . policySeq ();
}
else
{
m_needUpdate = false ;
qDebug () << "Check update api failed" ;
}
});
}
2026-07-14 08:39:41 +00:00
void UpdateLogic :: getDownloadUrl ( const QString & appId , const QString & channel , const QString & ver , int verId )
{
2026-07-10 02:45:13 +00:00
QString url = m_serverAddr + "/api/v1/update/download-url" ;
QJsonObject body ;
body [ "app_id" ] = appId ;
body [ "channel" ] = channel ;
body [ "version" ] = ver ;
body [ "version_id" ] = verId ;
QJsonArray files ;
body [ "files" ] = files ;
m_http . postRequest ( url , body , []( int code , const QJsonObject & resp )
{
qDebug () << " \n ========== File Download Url ==========" ;
qDebug () << resp [ "files" ]. toArray ();
});
2026-07-14 08:39:41 +00:00
}
2026-07-20 06:43:46 +00:00
bool UpdateLogic :: cacheManifest ( const QString & appId , const QString & channel , const QString & version ,
int versionId , const QString & cacheDir )
2026-07-14 08:39:41 +00:00
{
2026-07-21 03:02:42 +00:00
m_error . clear ();
if ( appId . isEmpty () || channel . isEmpty () || version . isEmpty () || versionId <= 0 ) {
m_error = QCoreApplication :: translate ( "UpdateLogic" ,
"Current version manifest identity is incomplete. Stage: cache current version manifest. App: %1, channel: %2, version: %3, version id: %4." )
. arg ( appId , channel , version , QString :: number ( versionId ));
return false ;
}
2026-07-14 08:39:41 +00:00
QJsonObject response ;
int statusCode = 0 ;
QJsonObject body ;
body [ "app_id" ] = appId ;
body [ "channel" ] = channel ;
body [ "version" ] = version ;
body [ "version_id" ] = versionId ;
m_http . postRequest ( m_serverAddr + "/api/v1/update/manifest" , body ,
[ & ]( int code , const QJsonObject & resp ) {
statusCode = code ;
response = resp ;
});
2026-07-21 03:02:42 +00:00
if ( statusCode != 200 ) {
const QJsonValue detail = response . value ( QStringLiteral ( "detail" ));
const QString message = detail . isObject ()
? detail . toObject (). value ( QStringLiteral ( "msg" )). toString ()
: detail . toString ();
m_error = QCoreApplication :: translate ( "UpdateLogic" ,
"Cannot download signed manifest for the current local version. Stage: cache current version manifest. HTTP status: %1. App: %2, channel: %3, version: %4, version id: %5.%6" )
. arg ( QString :: number ( statusCode ), appId , channel , version , QString :: number ( versionId ),
message . isEmpty () ? QString () : QCoreApplication :: translate ( "UpdateLogic" , " \n Server message: %1" ). arg ( message ));
return false ;
}
2026-07-14 08:39:41 +00:00
2026-07-21 03:02:42 +00:00
const QJsonObject manifest = response . value ( "manifest" ). toObject ();
const QString manifestText = response . value ( "manifest_text" ). toString ();
if ( manifest . isEmpty () || manifestText . isEmpty ()) {
m_error = QCoreApplication :: translate ( "UpdateLogic" ,
"The signed manifest response for the current local version is incomplete. Stage: cache current version manifest. App: %1, channel: %2, version: %3, version id: %4." )
. arg ( appId , channel , version , QString :: number ( versionId ));
return false ;
}
2026-07-14 08:39:41 +00:00
if ( manifest . value ( "app_id" ). toString () != appId
2026-07-21 03:02:42 +00:00
|| manifest . value ( "channel" ). toString () != channel
|| manifest . value ( "version" ). toString () != version ) {
m_error = QCoreApplication :: translate ( "UpdateLogic" ,
"The signed manifest identity does not match the current local version. Stage: cache current version manifest. Expected app/channel/version: %1 / %2 / %3. Manifest app/channel/version: %4 / %5 / %6." )
. arg ( appId , channel , version ,
manifest . value ( "app_id" ). toString (),
manifest . value ( "channel" ). toString (),
manifest . value ( "version" ). toString ());
return false ;
}
2026-07-14 08:39:41 +00:00
2026-07-21 03:02:42 +00:00
QDir dir ( cacheDir );
if ( ! dir . exists () && ! dir . mkpath ( "." )) {
m_error = QCoreApplication :: translate ( "UpdateLogic" ,
"Cannot create manifest cache directory. Stage: cache current version manifest. Directory: %1." )
. arg ( cacheDir );
return false ;
}
2026-07-14 08:39:41 +00:00
QJsonObject wrapper ;
wrapper [ "manifest" ] = manifest ;
wrapper [ "manifest_text" ] = manifestText ;
2026-07-21 03:02:42 +00:00
QSaveFile file ( dir . filePath ( "manifest_" + version + ".json" ));
const QByteArray bytes = QJsonDocument ( wrapper ). toJson ( QJsonDocument :: Indented );
if ( ! file . open ( QIODevice :: WriteOnly ) || file . write ( bytes ) != bytes . size () || ! file . commit ()) {
m_error = QCoreApplication :: translate ( "UpdateLogic" ,
"Cannot save signed manifest cache. Stage: cache current version manifest. File: %1. Error: %2." )
. arg ( file . fileName (), file . errorString ());
return false ;
}
2026-07-14 08:39:41 +00:00
qDebug () << "Current version manifest cached to" << file . fileName ();
2026-07-20 06:43:46 +00:00
return true ;
}
bool UpdateLogic :: refreshGitTagsFile ( const QString & outputPath )
{
m_error . clear ();
if ( m_serverAddr . isEmpty ()) {
m_error = QCoreApplication :: translate ( "UpdateLogic" , "Server address is empty." );
return false ;
}
QJsonObject response ;
int statusCode = 0 ;
QJsonObject body ;
body [ "app_id" ] = m_appId ;
body [ "channel" ] = m_channel ;
m_http . postRequest ( m_serverAddr + "/api/v1/git/tags" , body ,
[ & ]( int code , const QJsonObject & resp ) {
statusCode = code ;
response = resp ;
});
if ( statusCode != 200 ) {
const QJsonValue detail = response . value ( "detail" );
const QString message = detail . isObject ()
? detail . toObject (). value ( "msg" ). toString ()
: detail . toString ();
m_error = message . isEmpty ()
? QCoreApplication :: translate ( "UpdateLogic" , "Git tags request failed (HTTP %1)." ). arg ( statusCode )
: message ;
return false ;
}
const QString tagsText = response . value ( "tags_text" ). toString ();
if ( tagsText . isEmpty ()) {
m_error = QCoreApplication :: translate ( "UpdateLogic" , "Git tags response is empty." );
return false ;
}
QString writeError ;
if ( ! ConfigHelper :: writeFileWithElevationIfNeeded ( outputPath , tagsText . toUtf8 (), & writeError )) {
m_error = QCoreApplication :: translate ( "UpdateLogic" , "Cannot write Git tags file: %1" ). arg ( writeError );
return false ;
}
qDebug () << "Git tags file refreshed:" << outputPath ;
return true ;
}
void UpdateLogic :: reportUpdateResult ( const QString & deviceId , const QString & fromVer , const QString & toVer , bool success )
{
2026-07-10 02:45:13 +00:00
QString url = m_serverAddr + "/api/v1/update/report" ;
QJsonObject body ;
body [ "app_id" ] = m_appId ;
body [ "device_id" ] = deviceId ;
body [ "from_version" ] = fromVer ;
body [ "to_version" ] = toVer ;
body [ "result" ] = success ? "success" : "fail" ;
m_http . postRequest ( url , body , []( int code , const QJsonObject & resp )
{
qDebug () << " \n ========== Update Report Result ==========" ;
qDebug () << resp ;
});
2026-07-14 08:39:41 +00:00
}