From 10fda4e5e133f0e491b7e3bbb5901b777e967fae Mon Sep 17 00:00:00 2001 From: keke <243768648@qq.com> Date: Tue, 7 Jun 2022 01:03:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8D=B8=E8=BD=BD=E3=80=81=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E5=92=8C=E6=9B=B4=E6=96=B0=E4=BF=A1=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appmanagercommon.h | 7 ++ appmanagerjob.cpp | 231 ++++++++++++++++++++++++++++++++++----- appmanagerjob.h | 20 +++- appmanagermodel.cpp | 5 + appmanagermodel.h | 4 + appmanagerwidget.cpp | 253 ++++++++++++++++++++++++++++++++++++++++++- appmanagerwidget.h | 17 +++ ccc-app-manager.pro | 6 +- pkgmonitor.cpp | 195 +++++++++++++++++++++++++++++++++ pkgmonitor.h | 34 ++++++ 10 files changed, 736 insertions(+), 36 deletions(-) create mode 100644 pkgmonitor.cpp create mode 100644 pkgmonitor.h diff --git a/appmanagercommon.h b/appmanagercommon.h index 8001b69..d962919 100644 --- a/appmanagercommon.h +++ b/appmanagercommon.h @@ -6,6 +6,9 @@ #define X_DEEPIN_VENDOR_STR "deepin" #define APP_THEME_ICON_DEFAULT "application-x-executable" +// 列表数据角色定义 +#define AM_LIST_VIEW_ITEM_DATA_ROLE_PKG_NAME Qt::ItemDataRole::UserRole + 1 + namespace AM { struct PkgInfo { QString infosFilePath; // 包信息文件路径 @@ -55,6 +58,10 @@ struct AppInfo { { isInstalled = false; } + + bool operator== (const AppInfo &info) { + return this->pkgName == info.pkgName; + } }; // 拼音信息 diff --git a/appmanagerjob.cpp b/appmanagerjob.cpp index 9a64201..c1147c5 100644 --- a/appmanagerjob.cpp +++ b/appmanagerjob.cpp @@ -92,7 +92,11 @@ ComPressError zlibUnCompress(const char *srcName, const char *destName) AppManagerJob::AppManagerJob(QObject *parent) : QObject(parent) , m_isInitiallized(false) + , m_downloadingFile(nullptr) + , m_netManager(nullptr) + , m_netReply(nullptr) , m_listViewModel(nullptr) + , m_pkgMonitor(nullptr) { m_downloadDirPath = QString("%1/Desktop/downloadedPkg").arg(QDir::homePath()); m_pkgBuildCacheDirPath = "/tmp/pkg-build-cache"; @@ -158,6 +162,11 @@ void AppManagerJob::init() reloadAppInfos(); m_netManager = new QNetworkAccessManager(this); + // 包监视器 + m_pkgMonitor = new PkgMonitor(this); + + initConnection(); + m_isInitiallized = true; } @@ -178,7 +187,7 @@ void AppManagerJob::reloadAppInfos() } } - loadInstalledAppInfosFromFile(m_appInfosMap, "/var/lib/dpkg/status"); + loadAllPkgInstalledAppInfos(); Q_EMIT loadAppInfosFinished(); } @@ -395,6 +404,48 @@ void AppManagerJob::installProcInfoPlugin() Q_EMIT installProcInfoPluginFinished(successed); } +void AppManagerJob::onPkgInstalled(const QString &pkgName) +{ + PkgInfo pkgInfo; + if (getInstalledPkgInfo(pkgInfo, pkgName)) { + loadPkgInstalledAppInfo(pkgInfo); + Q_EMIT appInstalled(m_appInfosMap.value(pkgName)); + qInfo() << Q_FUNC_INFO << pkgName; + } +} + +void AppManagerJob::onPkgUpdated(const QString &pkgName) +{ + PkgInfo pkgInfo; + if (getInstalledPkgInfo(pkgInfo, pkgName)) { + loadPkgInstalledAppInfo(pkgInfo); + Q_EMIT appInstalled(m_appInfosMap.value(pkgName)); + qInfo() << Q_FUNC_INFO << pkgName; + } +} + +void AppManagerJob::onPkgUninstalled(const QString &pkgName) +{ + m_mutex.lock(); // m_appInfosMap为成员变量,加锁 + AppInfo *appInfo = &m_appInfosMap[pkgName]; + appInfo->isInstalled = false; + appInfo->installedPkgInfo = {}; + appInfo->desktopInfo = {}; + m_mutex.unlock(); // 解锁 + + PkgInfo pkgInfo; + pkgInfo.pkgName = pkgName; + Q_EMIT appUninstalled(*appInfo); + qInfo() << Q_FUNC_INFO << pkgName; +} + +void AppManagerJob::initConnection() +{ + connect(m_pkgMonitor, &PkgMonitor::pkgInstalled, this, &AppManagerJob::onPkgInstalled); + connect(m_pkgMonitor, &PkgMonitor::pkgUpdated, this, &AppManagerJob::onPkgUpdated); + connect(m_pkgMonitor, &PkgMonitor::pkgUninstalled, this, &AppManagerJob::onPkgUninstalled); +} + QList AppManagerJob::readSourceUrlList(const QString &filePath) { QList sourceUrlList; @@ -476,13 +527,12 @@ bool AppManagerJob::getPkgInfoListFromFile(QList &pkgInfoList, const QS } PkgInfo pkgInfo; - QTextStream txtStrem(&pkgInfosFile); - qint64 lastPkgContentOffset = 0; - qint64 contentOffset = 0; + bool isInstalled = false; bool isReadingDescription = false; - // 是否获取简洁信息 if (isCompact) { + qint64 lastPkgContentOffset = 0; + qint64 contentOffset = 0; while (!pkgInfosFile.atEnd()) { const QByteArray ba = pkgInfosFile.readLine(); contentOffset += ba.size(); @@ -493,6 +543,11 @@ bool AppManagerJob::getPkgInfoListFromFile(QList &pkgInfoList, const QS continue; } + if (lineText.startsWith("Status: ")) { + isInstalled = lineText.split(": ").last().startsWith("install"); + continue; + } + // 检测到下一包信息 if (lineText.isEmpty()) { pkgInfo.infosFilePath = pkgInfosFilePath; @@ -500,14 +555,15 @@ bool AppManagerJob::getPkgInfoListFromFile(QList &pkgInfoList, const QS pkgInfo.contentOffset = contentOffset; pkgInfo.contentSize = contentOffset - lastPkgContentOffset; lastPkgContentOffset = contentOffset; - pkgInfoList.append(pkgInfo); + if (isInstalled) { + pkgInfoList.append(pkgInfo); + } pkgInfo = {}; } } } else { while (!pkgInfosFile.atEnd()) { const QByteArray ba = pkgInfosFile.readLine(); - contentOffset += ba.size(); QString lineText = QString::fromUtf8(ba).remove("\n"); if (lineText.startsWith("Package: ")) { @@ -515,6 +571,11 @@ bool AppManagerJob::getPkgInfoListFromFile(QList &pkgInfoList, const QS continue; } + if (lineText.startsWith("Status: ")) { + isInstalled = lineText.split(": ").last().startsWith("install"); + continue; + } + if (lineText.startsWith("Installed-Size: ")) { pkgInfo.installedSize = lineText.split(": ").last().toInt(); continue; @@ -570,20 +631,127 @@ bool AppManagerJob::getPkgInfoListFromFile(QList &pkgInfoList, const QS if (lineText.isEmpty()) { pkgInfo.infosFilePath = pkgInfosFilePath; pkgInfo.depositoryUrl = depositoryUrlStr; - pkgInfo.contentOffset = contentOffset; - pkgInfo.contentSize = contentOffset - lastPkgContentOffset; - lastPkgContentOffset = contentOffset; - pkgInfoList.append(pkgInfo); + if (isInstalled) { + pkgInfoList.append(pkgInfo); + } pkgInfo = {}; } } } pkgInfosFile.close(); + // 判断循环中最后一个包信息是否没添加到列表 + // 防止最后一个包信息的最后一行不是换行符,而忽略了该包信息 + if (!pkgInfo.pkgName.isEmpty()) { + if (isInstalled) { + pkgInfoList.append(pkgInfo); + } + } + qInfo() << Q_FUNC_INFO << "end"; return true; } +bool AppManagerJob::getInstalledPkgInfo(PkgInfo &pkgInfo, const QString &pkgName) +{ + const QString &localPkgInfosFilePath = "/var/lib/dpkg/status"; + QFile file(localPkgInfosFilePath); + if (!file.open(QIODevice::OpenModeFlag::ReadOnly)) { + qDebug() << Q_FUNC_INFO << "open" << file.fileName() << "failed!"; + return false; + } + + bool isInstalled = false; + bool isReadingDescription = false; + while (!file.atEnd()) { + const QByteArray ba = file.readLine(); + QString lineText = QString::fromUtf8(ba).remove("\n"); + + // 不分析与目标包无关的信息 + if (!pkgInfo.pkgName.isEmpty() && pkgName != pkgInfo.pkgName) { + if (lineText.isEmpty()) { + pkgInfo.pkgName.clear(); + } + continue; + } + + if (lineText.startsWith("Package: ")) { + pkgInfo.pkgName = lineText.split(": ").last(); + continue; + } + + if (lineText.startsWith("Status: ")) { + isInstalled = lineText.split(": ").last().startsWith("install"); + continue; + } + + if (lineText.startsWith("Installed-Size: ")) { + pkgInfo.installedSize = lineText.split(": ").last().toInt(); + continue; + } + if (lineText.startsWith("Maintainer: ")) { + pkgInfo.maintainer = lineText.split(": ").last(); + continue; + } + if (lineText.startsWith("Architecture: ")) { + pkgInfo.arch = lineText.split(": ").last(); + continue; + } + if (lineText.startsWith("Version: ")) { + pkgInfo.version = lineText.split(": ").last(); + continue; + } + if (lineText.startsWith("Depends: ")) { + pkgInfo.depends = lineText.split(": ").last(); + continue; + } + if (lineText.startsWith("Filename: ")) { + const QString downloadFileName = lineText.split(": ").last(); + pkgInfo.downloadUrl = QString("%1/%2").arg(pkgInfo.depositoryUrl).arg(downloadFileName); + continue; + } + if (lineText.startsWith("Size: ")) { + pkgInfo.pkgSize = lineText.split(": ").last().toInt(); + continue; + } + + if (lineText.startsWith("Homepage: ")) { + pkgInfo.homepage = lineText.split(": ").last(); + continue; + } + + if (lineText.startsWith("Description: ")) { + pkgInfo.description = lineText.split(": ").last(); + pkgInfo.description.append("\n"); + isReadingDescription = true; + continue; + } + if (lineText.startsWith(" ") && isReadingDescription) { + pkgInfo.description += lineText; + continue; + } + if (lineText.startsWith("Build-Depends: ")) { + isReadingDescription = false; + continue; + } + + // 检测到下一包信息 + if (lineText.isEmpty()) { + pkgInfo.infosFilePath = localPkgInfosFilePath; + if (pkgName == pkgInfo.pkgName) { + if (isInstalled) { + break; + } + } + pkgInfo = {}; + } + } + file.close(); + + // 判断循环中得到的最后一个包信息是否目标包信息 + return (pkgName == pkgInfo.pkgName); +} + // 从包信息列表中加载应用信息列表 void AppManagerJob::loadSrvAppInfosFromFile(QMap &appInfosMap, const QString &pkgInfosFilePath) { @@ -599,27 +767,34 @@ void AppManagerJob::loadSrvAppInfosFromFile(QMap &appInfosMap, } } +void AppManagerJob::loadPkgInstalledAppInfo(const AM::PkgInfo &pkgInfo) +{ + m_mutex.lock(); // m_appInfosMap为成员变量,加锁 + AppInfo *appInfo = &m_appInfosMap[pkgInfo.pkgName]; + if (appInfo->pkgName.isEmpty()) { + appInfo->pkgName = pkgInfo.pkgName; + } + appInfo->isInstalled = true; + appInfo->installedPkgInfo = pkgInfo; + + // 获取安装文件路径列表 + appInfo->installedPkgInfo.installedFileList = getAppInstalledFileList(appInfo->installedPkgInfo.pkgName); + // 获取desktop + appInfo->desktopInfo.desktopPath = getAppDesktopPath(appInfo->installedPkgInfo.installedFileList, + appInfo->installedPkgInfo.pkgName); + appInfo->desktopInfo = getDesktopInfo(appInfo->desktopInfo.desktopPath); + + m_mutex.unlock(); // 解锁 +} + // 从包信息列表中加载已安装应用信息列表 -void AppManagerJob::loadInstalledAppInfosFromFile(QMap &appInfosMap, const QString &pkgInfosFilePath) +void AppManagerJob::loadAllPkgInstalledAppInfos() { QList pkgInfoList; - getPkgInfoListFromFile(pkgInfoList, pkgInfosFilePath); + getPkgInfoListFromFile(pkgInfoList, "/var/lib/dpkg/status"); for (const PkgInfo &pkgInfo : pkgInfoList) { - m_mutex.lock(); // appInfosMap为成员变量,加锁 - AppInfo *appInfo = &appInfosMap[pkgInfo.pkgName]; - appInfo->pkgName = pkgInfo.pkgName; - appInfo->isInstalled = true; - appInfo->installedPkgInfo = pkgInfo; - - // 获取安装文件路径列表 - appInfo->installedPkgInfo.installedFileList = getAppInstalledFileList(appInfo->installedPkgInfo.pkgName); - // 获取desktop - appInfo->desktopInfo.desktopPath = getAppDesktopPath(appInfo->installedPkgInfo.installedFileList, - appInfo->installedPkgInfo.pkgName); - appInfo->desktopInfo = getDesktopInfo(appInfo->desktopInfo.desktopPath); - - m_mutex.unlock(); // 解锁 + loadPkgInstalledAppInfo(pkgInfo); } } @@ -773,6 +948,8 @@ QStandardItemModel *AppManagerJob::getItemModelFromAppInfoList(const QListsetIcon(QIcon::fromTheme(APP_THEME_ICON_DEFAULT)); } + + item->setData(info.pkgName, AM_LIST_VIEW_ITEM_DATA_ROLE_PKG_NAME); model->appendRow(QList {item}); } diff --git a/appmanagerjob.h b/appmanagerjob.h index 40f167e..9984cb3 100644 --- a/appmanagerjob.h +++ b/appmanagerjob.h @@ -1,6 +1,7 @@ #pragma once #include "appmanagercommon.h" +#include "pkgmonitor.h" #include #include @@ -54,6 +55,12 @@ public Q_SLOTS: void installOhMyDDE(); void installProcInfoPlugin(); +private Q_SLOTS: + // 包安装变动 + void onPkgInstalled(const QString &pkgName); + void onPkgUpdated(const QString &pkgName); + void onPkgUninstalled(const QString &pkgName); + Q_SIGNALS: void loadAppInfosFinished(); void downloadPkgFinished(const QString &pkgName); @@ -70,17 +77,26 @@ Q_SIGNALS: void buildPkgTaskFinished(bool successed, const AM::AppInfo &info); void installOhMyDDEFinished(bool successed); void installProcInfoPluginFinished(bool successed); + // 软件安装变动 + void appInstalled(const AM::AppInfo &appInfo); + void appUpdated(const AM::AppInfo &appInfo); + void appUninstalled(const AM::AppInfo &appInfo); private: + void initConnection(); QList readSourceUrlList(const QString &filePath); void reloadSourceUrlList(); // 从包信息列表文件中获取包信息列表 bool getPkgInfoListFromFile(QList &pkgInfoList, const QString &pkgInfosFilePath, bool isCompact = false); + // 从本地包信息列表文件中获取某个包信息 + bool getInstalledPkgInfo(AM::PkgInfo &pkgInfo, const QString &pkgName); // 从包信息列表中加载仓库应用信息列表 void loadSrvAppInfosFromFile(QMap &appInfosMap, const QString &pkgInfosFilePath); + // 加载包的已安装软件信息 + void loadPkgInstalledAppInfo(const AM::PkgInfo &pkgInfo); // 从包信息列表中加载已安装应用信息列表 - void loadInstalledAppInfosFromFile(QMap &appInfosMap, const QString &pkgInfosFilePath); + void loadAllPkgInstalledAppInfos(); QStringList getAppInstalledFileList(const QString &pkgName); QString getAppDesktopPath(const QStringList &list, const QString &pkgName); @@ -115,4 +131,6 @@ private: QString m_pkgBuildCacheDirPath; // deb构建目录 QString m_pkgBuildDirPath; + // 包监视器 + PkgMonitor *m_pkgMonitor; }; diff --git a/appmanagermodel.cpp b/appmanagermodel.cpp index 8f1086e..70364d9 100644 --- a/appmanagermodel.cpp +++ b/appmanagermodel.cpp @@ -280,6 +280,11 @@ void AppManagerModel::initConnection() connect(this, &AppManagerModel::notifyThreadInstallProcInfoPlugin, m_appManagerJob, &AppManagerJob::installProcInfoPlugin); // 安装proc-info-plugin完成 connect(m_appManagerJob, &AppManagerJob::installProcInfoPluginFinished, this, &AppManagerModel::installProcInfoPluginFinished); + + // 包安装变动 + connect(m_appManagerJob, &AppManagerJob::appInstalled, this, &AppManagerModel::appInstalled); + connect(m_appManagerJob, &AppManagerJob::appUpdated, this, &AppManagerModel::appUpdated); + connect(m_appManagerJob, &AppManagerJob::appUninstalled, this, &AppManagerModel::appUninstalled); } void AppManagerModel::postInit() diff --git a/appmanagermodel.h b/appmanagermodel.h index 8142d7f..d32e998 100644 --- a/appmanagermodel.h +++ b/appmanagermodel.h @@ -66,6 +66,10 @@ Q_SIGNALS: void notifyThreadInstallProcInfoPlugin(); // 安装proc-info-plugin完成 void installProcInfoPluginFinished(bool successed); + // 软件安装变动 + void appInstalled(const AM::AppInfo &appInfo); + void appUpdated(const AM::AppInfo &appInfo); + void appUninstalled(const AM::AppInfo &appInfo); private: void initData(); diff --git a/appmanagerwidget.cpp b/appmanagerwidget.cpp index 3569f06..2331790 100644 --- a/appmanagerwidget.cpp +++ b/appmanagerwidget.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -40,6 +39,7 @@ AppManagerWidget::AppManagerWidget(AppManagerModel *model, QWidget *parent) , m_showInstalledAppAction(nullptr) , m_showGuiAppAction(nullptr) , m_showSearchedAppAction(nullptr) + , m_displayRangeType(DisplayRangeType::All) , m_appListModel(nullptr) , m_appListView(nullptr) , m_appCountLabel(nullptr) @@ -373,6 +373,11 @@ AppManagerWidget::AppManagerWidget(AppManagerModel *model, QWidget *parent) connect(m_model, &AppManagerModel::searchTaskFinished, this, &AppManagerWidget::onSearchTaskFinished); connect(m_model, &AppManagerModel::createListViewModeFinished, this, &AppManagerWidget::onCreateListViewModeFinished); + // 包安装变动 + connect(m_model, &AppManagerModel::appInstalled, this, &AppManagerWidget::onAppInstalled); + connect(m_model, &AppManagerModel::appUpdated, this, &AppManagerWidget::onAppUpdated); + connect(m_model, &AppManagerModel::appUninstalled, this, &AppManagerWidget::onAppUninstalled); + setLoading(true); } @@ -469,14 +474,206 @@ void AppManagerWidget::onCreateListViewModeFinished() } // 更新应用个数标签 - QString showingTypeStr; - for (QAction *action : m_filterMenu->actions()) { - if (action->isChecked()) { - showingTypeStr = action->text(); + updateAppCountLabel(); +} + +void AppManagerWidget::onAppInstalled(const AM::AppInfo &appInfo) +{ + for (QList::iterator iter = m_appInfoList.begin(); + iter != m_appInfoList.end(); ++iter) { + if (appInfo.pkgName == iter->pkgName) { + *iter = appInfo; break; } } - m_appCountLabel->setText(QString("%1:共%2个").arg(showingTypeStr).arg(m_showingInfoList.size())); + + switch (m_displayRangeType) { + case All: + case Searched: { + for (QList::iterator iter = m_showingInfoList.begin(); + iter != m_showingInfoList.end(); ++iter) { + if (appInfo.pkgName == iter->pkgName) { + *iter = appInfo; + break; + } + } + + // 更新界面数据 + for (int i = 0; i < m_appListModel->rowCount(); ++i) { + QStandardItem *item = m_appListModel->item(i, 0); + if (appInfo.pkgName == item->data(AM_LIST_VIEW_ITEM_DATA_ROLE_PKG_NAME).toString()) { + updateItemFromAppInfo(item, appInfo); + break; + } + } + break; + } + case Installed: { + m_showingInfoList.insert(0, appInfo); + m_appListModel->insertRow(0, createViewItemList(appInfo)); + break; + } + case Gui: { + // 有desktop文件 + if (!appInfo.desktopInfo.desktopPath.isEmpty()) { + m_showingInfoList.insert(0, appInfo); + m_appListModel->insertRow(0, createViewItemList(appInfo)); + } + break; + } + } + + // 刷新右侧显示内容 + if (appInfo.pkgName == m_showingAppInfo.pkgName) { + showAppInfo(appInfo); + } + // 更新应用个数标签 + updateAppCountLabel(); +} + +void AppManagerWidget::onAppUpdated(const AM::AppInfo &appInfo) +{ + for (QList::iterator iter = m_appInfoList.begin(); + iter != m_appInfoList.end(); ++iter) { + if (appInfo.pkgName == iter->pkgName) { + *iter = appInfo; + break; + } + } + + switch (m_displayRangeType) { + case All: + case Searched: + case Installed: { + for (QList::iterator iter = m_showingInfoList.begin(); + iter != m_showingInfoList.end(); ++iter) { + if (appInfo.pkgName == iter->pkgName) { + *iter = appInfo; + break; + } + } + + // 更新界面数据 + for (int i = 0; i < m_appListModel->rowCount(); ++i) { + QStandardItem *item = m_appListModel->item(i, 0); + if (appInfo.pkgName == item->data(AM_LIST_VIEW_ITEM_DATA_ROLE_PKG_NAME).toString()) { + updateItemFromAppInfo(item, appInfo); + break; + } + } + break; + } + case Gui: { + int operateType = 0; // 0 - add, 1 - remove, 2 - update + for (QList::iterator iter = m_showingInfoList.end() - 1; + iter != m_showingInfoList.begin() - 1; --iter) { + if (appInfo.pkgName != iter->pkgName) { + continue; + } + + if (appInfo.desktopInfo.desktopPath.isEmpty()) { + operateType = 1; + m_showingInfoList.removeOne(*iter); + } else { + operateType = 2; + *iter = appInfo; + } + break; + } + + // 根据操作类型,更新界面和缓存列表 + if (0 == operateType) { + m_appInfoList.insert(0, appInfo); + m_appListModel->insertRow(0, createViewItemList(appInfo)); + } else if (1 == operateType) { + for (int i = m_appListModel->rowCount() - 1; i >= 0 ; --i) { + QStandardItem *item = m_appListModel->item(i, 0); + if (appInfo.pkgName == item->data(AM_LIST_VIEW_ITEM_DATA_ROLE_PKG_NAME).toString()) { + m_appListModel->removeRow(i); + break; + } + } + } else if (2 == operateType) { + for (int i = m_appListModel->rowCount() - 1; i >= 0 ; --i) { + QStandardItem *item = m_appListModel->item(i, 0); + if (appInfo.pkgName == item->data(AM_LIST_VIEW_ITEM_DATA_ROLE_PKG_NAME).toString()) { + updateItemFromAppInfo(item, appInfo); + break; + } + } + } + break; + } + } + + // 刷新右侧显示内容 + if (appInfo.pkgName == m_showingAppInfo.pkgName) { + showAppInfo(appInfo); + } + // 更新应用个数标签 + updateAppCountLabel(); +} + +void AppManagerWidget::onAppUninstalled(const AM::AppInfo &appInfo) +{ + for (QList::iterator iter = m_appInfoList.begin(); + iter != m_appInfoList.end(); ++iter) { + if (appInfo.pkgName == iter->pkgName) { + *iter = appInfo; + break; + } + } + + switch (m_displayRangeType) { + case All: + case Searched: { + for (QList::iterator iter = m_showingInfoList.begin(); + iter != m_showingInfoList.end(); ++iter) { + if (appInfo.pkgName == iter->pkgName) { + *iter = appInfo; + break; + } + } + + // 更新界面数据 + for (int i = 0; i < m_appListModel->rowCount(); ++i) { + QStandardItem *item = m_appListModel->item(i, 0); + if (appInfo.pkgName == item->data(AM_LIST_VIEW_ITEM_DATA_ROLE_PKG_NAME).toString()) { + updateItemFromAppInfo(item, appInfo); + break; + } + } + break; + } + case Installed: + case Gui: { + for (QList::iterator iter = m_showingInfoList.end() - 1; + iter != m_showingInfoList.begin() - 1; --iter) { + if (appInfo.pkgName == iter->pkgName) { + m_showingInfoList.removeOne(*iter); + break; + } + } + // 更新界面数据 + for (int i = m_appListModel->rowCount() - 1; i >= 0 ; --i) { + QStandardItem *item = m_appListModel->item(i, 0); + if (appInfo.pkgName == item->data(AM_LIST_VIEW_ITEM_DATA_ROLE_PKG_NAME).toString()) { + m_appListModel->removeRow(i); + break; + } + } + break; + } + } + + // 刷新右侧显示内容 + if (appInfo.pkgName == m_showingAppInfo.pkgName) { + // 删除后,显示列表当前选中应用 + AppInfo currentAppInfo = m_showingInfoList.at(m_appListView->currentIndex().row()); + showAppInfo(currentAppInfo); + } + // 更新应用个数标签 + updateAppCountLabel(); } QString AppManagerWidget::formateAppInfo(const AppInfo &info) @@ -520,11 +717,13 @@ QString AppManagerWidget::formateAppInfo(const AppInfo &info) void AppManagerWidget::showAllAppInfoList() { + m_displayRangeType = All; Q_EMIT m_model->notifyThreadCreateListViewMode(m_appInfoList); } void AppManagerWidget::onlyShowInstalledAppInfoList() { + m_displayRangeType = Installed; QList installedAppInfoList; for (const AppInfo &info : m_appInfoList) { if (!info.isInstalled) { @@ -538,6 +737,7 @@ void AppManagerWidget::onlyShowInstalledAppInfoList() void AppManagerWidget::onlyShowUIAppInfoList() { + m_displayRangeType = Gui; QList uiAppInfoList; for (const AppInfo &info : m_appInfoList) { if (info.desktopInfo.desktopPath.isEmpty()) { @@ -551,6 +751,7 @@ void AppManagerWidget::onlyShowUIAppInfoList() void AppManagerWidget::showSearchedAppInfoList() { + m_displayRangeType = Searched; QList searchedList = m_model->getSearchedAppInfoList(); Q_EMIT m_model->notifyThreadCreateListViewMode(searchedList); @@ -568,3 +769,43 @@ void AppManagerWidget::setLoading(bool loading) m_contentWidget->show(); } } + +QList AppManagerWidget::createViewItemList(const AM::AppInfo &appInfo) +{ + // 更新列表显示数据 + QStandardItem *item = new QStandardItem(); + updateItemFromAppInfo(item, appInfo); + QList itemList; + itemList << item; + return itemList; +} + +void AppManagerWidget::updateItemFromAppInfo(QStandardItem *item, const AppInfo &appInfo) +{ + QString appName = appInfo.desktopInfo.appName; + if (appName.isEmpty()) { + appName = appInfo.pkgName; + } + item->setText(appName); + + if (!appInfo.desktopInfo.themeIconName.isEmpty()) { + item->setIcon(QIcon::fromTheme(appInfo.desktopInfo.themeIconName)); + } else { + item->setIcon(QIcon::fromTheme(APP_THEME_ICON_DEFAULT)); + } + + item->setData(appInfo.pkgName, AM_LIST_VIEW_ITEM_DATA_ROLE_PKG_NAME); +} + +// 更新应用个数标签 +void AppManagerWidget::updateAppCountLabel() +{ + QString showingTypeStr; + for (QAction *action : m_filterMenu->actions()) { + if (action->isChecked()) { + showingTypeStr = action->text(); + break; + } + } + m_appCountLabel->setText(QString("%1:共%2个").arg(showingTypeStr).arg(m_showingInfoList.size())); +} diff --git a/appmanagerwidget.h b/appmanagerwidget.h index d2ab21f..79ba543 100644 --- a/appmanagerwidget.h +++ b/appmanagerwidget.h @@ -4,6 +4,7 @@ #include "appmanagermodel.h" #include +#include class AppManagerModel; @@ -28,6 +29,13 @@ class AppManagerWidget : public QWidget { Q_OBJECT public: + enum DisplayRangeType { + All = 0, + Installed, + Gui, + Searched + }; + AppManagerWidget(AppManagerModel *model, QWidget *parent = nullptr); virtual ~AppManagerWidget() override; @@ -37,6 +45,10 @@ public Q_SLOTS: void onSearchEditingFinished(); void onSearchTaskFinished(); void onCreateListViewModeFinished(); + // 软件安装变动 + void onAppInstalled(const AM::AppInfo &appInfo); + void onAppUpdated(const AM::AppInfo &appInfo); + void onAppUninstalled(const AM::AppInfo &appInfo); private: QString formateAppInfo(const AM::AppInfo &info); @@ -47,6 +59,10 @@ private: void showSearchedAppInfoList(); void setLoading(bool loading); + QList createViewItemList(const AM::AppInfo &appInfo); + void updateItemFromAppInfo(QStandardItem *item, const AM::AppInfo &appInfo); + // 更新应用个数标签 + void updateAppCountLabel(); private: QList m_appInfoList; @@ -64,6 +80,7 @@ private: QAction *m_showInstalledAppAction; QAction *m_showGuiAppAction; QAction *m_showSearchedAppAction; + DisplayRangeType m_displayRangeType; // 显示范围类型 QStandardItemModel *m_appListModel; DListView *m_appListView; diff --git a/ccc-app-manager.pro b/ccc-app-manager.pro index ecebfda..fb65b18 100644 --- a/ccc-app-manager.pro +++ b/ccc-app-manager.pro @@ -32,7 +32,8 @@ SOURCES += \ appmanagerwidget.cpp \ appmanagercommon.cpp \ pkgdownloaddlg.cpp \ - appmanagermodel.cpp + appmanagermodel.cpp \ + pkgmonitor.cpp HEADERS += \ mainwindow.h \ @@ -40,7 +41,8 @@ HEADERS += \ appmanagerwidget.h \ appmanagercommon.h \ pkgdownloaddlg.h \ - appmanagermodel.h + appmanagermodel.h \ + pkgmonitor.h # Default rules for deployment. target.path = /opt/apps/com.github.ccc-app-manager/files diff --git a/pkgmonitor.cpp b/pkgmonitor.cpp new file mode 100644 index 0000000..f19d748 --- /dev/null +++ b/pkgmonitor.cpp @@ -0,0 +1,195 @@ +#include "pkgmonitor.h" + +#include +#include + +#define DPKG_INSTALL_APP_DIR_PATH "/var/lib/dpkg/info" +#define DPKG_INSTALL_STATUS_FILE_PATH "/var/lib/dpkg/status" + +PkgMonitor::PkgMonitor(QObject *parent) + : QObject(parent) + , m_fileWatcher(nullptr) + , m_statusFileWatcher(nullptr) + +{ + // /var/lib/dpkg/info监视器 + m_fileWatcher = new QFileSystemWatcher(this); + + m_fileWatcher->addPath(DPKG_INSTALL_APP_DIR_PATH); + + // 添加文件路径监控 + QDir fileDir(DPKG_INSTALL_APP_DIR_PATH); + QList fileInfo = fileDir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files, QDir::DirsFirst); + for (int i = 0; i < fileInfo.count(); ++i) { + QString filePath = QDir::cleanPath(fileInfo.at(i).filePath()); + // 只监控desktop文件 + if (!filePath.endsWith(".list")) { + continue; + } + + QFileInfo fileInfo(filePath); + if (0 == fileInfo.size()) { + continue; + } + m_monitoringFilePathList.append(filePath); + m_fileWatcher->addPath(filePath); + } + + // /var/lib/dpkg/status监视器 + m_statusFileWatcher = new QFileSystemWatcher(this); + m_statusFileWatcher->addPath(DPKG_INSTALL_STATUS_FILE_PATH); + m_lastInstalledPkgNameSet = getCurrentInstalledPkgNameSet(); + + // 初始化连接 + connect(m_fileWatcher, &QFileSystemWatcher::directoryChanged, this, &PkgMonitor::onDPkgDirChanged); + connect(m_fileWatcher, &QFileSystemWatcher::fileChanged, this, &PkgMonitor::onDPkgFileChanged); + + connect(m_statusFileWatcher, &QFileSystemWatcher::fileChanged, this, &PkgMonitor::onDPkgStatusFileChanged); +} + +PkgMonitor::~PkgMonitor() +{ +} + +void PkgMonitor::onDPkgDirChanged(const QString &path) +{ + qInfo() << Q_FUNC_INFO << path; + + QDir dir(path); + QStringList currentFileList; + + QList fileInfo = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files, QDir::DirsFirst); + for (int i = 0; i < fileInfo.count(); ++i) { + QString filePath = QDir::cleanPath(fileInfo.at(i).filePath()); + // 只监控list文件 + if (!filePath.endsWith(".list")) { + continue; + } + + QFileInfo fileInfo(filePath); + if (0 == fileInfo.size()) { + continue; + } + currentFileList.append(filePath); + } + + // 创建集合,用于比较 + QSet currentFileSet = QSet::fromList(currentFileList); + QSet lastFileSet = QSet::fromList(m_monitoringFilePathList); + + // 找到新添加文件 + QStringList newFileList = (currentFileSet - lastFileSet).toList(); + + // 找到已删除文件 + QStringList deletedFileList = (lastFileSet - currentFileSet).toList(); + + if (!newFileList.isEmpty()) { + qInfo() << Q_FUNC_INFO << "files added:" << newFileList; + + for (QStringList::const_iterator iter = newFileList.cbegin(); iter != newFileList.cend(); ++iter) { + m_fileWatcher->addPath(*iter); + } + } + + if (!deletedFileList.isEmpty()) { + qInfo() << Q_FUNC_INFO << "files deleted:" << deletedFileList; + for (QStringList::const_iterator iter = deletedFileList.cbegin(); iter != deletedFileList.cend(); ++iter) { + m_fileWatcher->removePath(*iter); + } + } + + // 更新监控的文件列表缓存 + m_monitoringFilePathList = currentFileList; +} + +void PkgMonitor::onDPkgFileChanged(const QString &path) +{ + const QString pkgName = getPkgNameFromListFilePath(path); + // 重新监控文件 + m_fileWatcher->removePath(path); + m_fileWatcher->addPath(path); + + qInfo() << Q_FUNC_INFO << "pkgUpdated" << pkgName; + Q_EMIT pkgUpdated(pkgName); +} + +void PkgMonitor::onDPkgStatusFileChanged(const QString &path) +{ + qInfo() << Q_FUNC_INFO << path; + const QSet ¤tInstallPkgNameSet = getCurrentInstalledPkgNameSet(); + QSet newPkgNameSet = currentInstallPkgNameSet - m_lastInstalledPkgNameSet; + QSet deletedPkgNameSet = m_lastInstalledPkgNameSet - currentInstallPkgNameSet; + + m_lastInstalledPkgNameSet = currentInstallPkgNameSet; + + if (!newPkgNameSet.isEmpty()) { + qInfo() << Q_FUNC_INFO << "pkg installed:" << newPkgNameSet; + + for (QSet::const_iterator iter = newPkgNameSet.cbegin(); iter != newPkgNameSet.cend(); ++iter) { + Q_EMIT pkgInstalled(*iter); + } + } + + if (!deletedPkgNameSet.isEmpty()) { + qInfo() << Q_FUNC_INFO << "pkg uninstalled:" << deletedPkgNameSet; + for (QSet::const_iterator iter = deletedPkgNameSet.cbegin(); iter != deletedPkgNameSet.cend(); ++iter) { + Q_EMIT pkgUninstalled(*iter); + } + } + + // 重新监控文件 + m_statusFileWatcher->removePath(path); + m_statusFileWatcher->addPath(path); +} + +QString PkgMonitor::getPkgNameFromListFilePath(const QString &path) const +{ + // 获取包名,如/var/lib/dpkg/info/com.github.aaa.list -> com.github.aaa + QString pkgName = path.split("/").last().remove(".list").split(":").first(); + return pkgName; +} + +QSet PkgMonitor::getCurrentInstalledPkgNameSet() const +{ + QSet pkgNameSet; + + QFile pkgInfosFile(DPKG_INSTALL_STATUS_FILE_PATH); + if (!pkgInfosFile.open(QIODevice::OpenModeFlag::ReadOnly)) { + qDebug() << Q_FUNC_INFO << "open" << pkgInfosFile.fileName() << "failed!"; + return pkgNameSet; + } + + QString pkgName; + bool isInstalled = false; + while (!pkgInfosFile.atEnd()) { + const QByteArray &ba = pkgInfosFile.readLine(); + + QString lineText = QString::fromUtf8(ba).remove("\n"); + if (lineText.startsWith("Package: ")) { + pkgName = lineText.split(": ").last(); + continue; + } + + if (lineText.startsWith("Status: ")) { + isInstalled = lineText.split(": ").last().startsWith("install"); + continue; + } + + // 检测到下一包信息 + if (lineText.isEmpty()) { + if (isInstalled) { + pkgNameSet.insert(pkgName); + } + pkgName.clear(); + } + } + + if (!pkgName.isEmpty()) { + if (isInstalled) { + pkgNameSet.insert(pkgName); + } + } + + pkgInfosFile.close(); + return pkgNameSet; +} diff --git a/pkgmonitor.h b/pkgmonitor.h new file mode 100644 index 0000000..59f8f3c --- /dev/null +++ b/pkgmonitor.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +class PkgMonitor : public QObject +{ + Q_OBJECT +public: + explicit PkgMonitor(QObject *parent = nullptr); + virtual ~PkgMonitor() override; + +Q_SIGNALS: + void pkgInstalled(const QString &pkgName); + void pkgUpdated(const QString &pkgName); + void pkgUninstalled(const QString &pkgName); + +public Q_SLOTS: + void onDPkgDirChanged(const QString &path); + void onDPkgFileChanged(const QString &path); + void onDPkgStatusFileChanged(const QString &path); + +private: + QString getPkgNameFromListFilePath(const QString &path) const; + QSet getCurrentInstalledPkgNameSet() const; + +private: + QFileSystemWatcher *m_fileWatcher; + QStringList m_monitoringFilePathList; + + QFileSystemWatcher *m_statusFileWatcher; + QSet m_lastInstalledPkgNameSet; +}; -- Gitee