#!/usr/bin/env python3 """ Patch PKTransaction.cpp to add verbose logging so we can diagnose why installs hang on "Installing..." status. """ import sys def replace_once(src, old, new, label): count = src.count(old) if count != 1: print(f"ERROR: '{label}' matched {count} times (expected 1)", file=sys.stderr) sys.exit(1) return src.replace(old, new) path = sys.argv[1] with open(path) as f: src = f.read() # 1. trigger(): log what phase we're entering and with what flags src = replace_once(src, 'void PKTransaction::trigger(PackageKit::Transaction::TransactionFlags flags)\n{', 'void PKTransaction::trigger(PackageKit::Transaction::TransactionFlags flags)\n{\n' ' qWarning() << "[DISCOVER] trigger(): flags=" << flags << "role=" << role();', 'trigger() header' ) # 2. statusChanged(): log the raw PK status instead of the collapsed UI status src = replace_once(src, 'void PKTransaction::statusChanged()\n{' '\n setStatus(m_trans->status() == PackageKit::Transaction::StatusDownload ? Transaction::DownloadingStatus : Transaction::CommittingStatus);', 'void PKTransaction::statusChanged()\n{' '\n qWarning() << "[DISCOVER] statusChanged(): pk_status=" << m_trans->status()' ' << "percentage=" << m_trans->percentage()' ' << "lastPackage=" << m_trans->lastPackage();' '\n setStatus(m_trans->status() == PackageKit::Transaction::StatusDownload ? Transaction::DownloadingStatus : Transaction::CommittingStatus);', 'statusChanged() body' ) # 3. progressChanged(): log when percentage updates (or fails to) src = replace_once(src, ' auto percent = m_trans->percentage();\n if (percent == 101) {\n qWarning() << "percentage cannot be calculated";', ' auto percent = m_trans->percentage();\n' ' qWarning() << "[DISCOVER] progressChanged(): raw_pct=" << percent << "pk_status=" << m_trans->status();\n' ' if (percent == 101) {\n qWarning() << "percentage cannot be calculated";', 'progressChanged() body' ) # 4. cleanup(): log the exit/cancel/failed/simulate flags src = replace_once(src, 'void PKTransaction::cleanup(PackageKit::Transaction::Exit exit, uint runtime)\n{', 'void PKTransaction::cleanup(PackageKit::Transaction::Exit exit, uint runtime)\n{\n' ' const bool _simulate_flag = m_trans && (m_trans->transactionFlags() & PackageKit::Transaction::TransactionFlagSimulate);\n' ' qWarning() << "[DISCOVER] cleanup(): exit=" << exit << "runtime=" << runtime' ' << "simulate=" << _simulate_flag' ' << "proceedFunctions=" << m_proceedFunctions.size();', 'cleanup() header' ) # 5. errorFound(): log every error, including ones currently silently swallowed src = replace_once(src, 'void PKTransaction::errorFound(PackageKit::Transaction::Error err, const QString &error)\n{' '\n if (err == PackageKit::Transaction::ErrorNoLicenseAgreement || err == PackageKit::Transaction::ErrorTransactionCancelled' '\n || err == PackageKit::Transaction::ErrorNotAuthorized) {' '\n return;\n }', 'void PKTransaction::errorFound(PackageKit::Transaction::Error err, const QString &error)\n{' '\n qWarning() << "[DISCOVER] errorFound(): err=" << err << "detail=" << error;' '\n if (err == PackageKit::Transaction::ErrorNoLicenseAgreement || err == PackageKit::Transaction::ErrorTransactionCancelled' '\n || err == PackageKit::Transaction::ErrorNotAuthorized) {' '\n return;\n }', 'errorFound() body' ) # 6. LocalFilePKResource path: log the .deb path being installed src = replace_once(src, ' m_trans = PackageKit::Daemon::installFile(QUrl(app->packageName()).toLocalFile(), flags);', ' qWarning() << "[DISCOVER] installFile():" << QUrl(app->packageName()).toLocalFile() << "flags=" << flags;\n' ' m_trans = PackageKit::Daemon::installFile(QUrl(app->packageName()).toLocalFile(), flags);', 'installFile() call' ) with open(path, 'w') as f: f.write(src) print(f"Patched {path}")