From 2d08482c4ce4b9631f14fee99be3a7ad033c6169 Mon Sep 17 00:00:00 2001 From: hewenfei Date: Tue, 12 Dec 2023 10:12:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E4=BD=BF=E7=94=A8model=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 + src/libappdata/app-database-interface.cpp | 334 ++++++++++++++++++++++ src/libappdata/app-database-interface.h | 85 ++++++ src/libappdata/basic-app-model.cpp | 164 +++++++++++ src/libappdata/basic-app-model.h | 49 ++++ 5 files changed, 634 insertions(+) create mode 100644 src/libappdata/app-database-interface.cpp create mode 100644 src/libappdata/app-database-interface.h create mode 100644 src/libappdata/basic-app-model.cpp create mode 100644 src/libappdata/basic-app-model.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6482168..1b74783 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,6 +129,8 @@ set(SOURCE_FILES src/extension/favorite/favorite-extension-plugin.cpp src/extension/favorite/favorite-extension-plugin.h src/extension/favorite/favorite-context-menu.cpp src/extension/favorite/favorite-context-menu.h src/extension/favorite/favorite-widget.cpp src/extension/favorite/favorite-widget.h + src/libappdata/basic-app-model.cpp src/libappdata/basic-app-model.h + src/libappdata/app-database-interface.cpp src/libappdata/app-database-interface.h ) diff --git a/src/libappdata/app-database-interface.cpp b/src/libappdata/app-database-interface.cpp new file mode 100644 index 0000000..5951e03 --- /dev/null +++ b/src/libappdata/app-database-interface.cpp @@ -0,0 +1,334 @@ +// +// Created by hxf on 23-12-11. +// + + +#include "app-database-interface.h" +#include "user-config.h" +#include "settings.h" + +#include +#include + +#define APP_ICON_PREFIX "image://appicon/" + +namespace UkuiMenu { + +class AppDatabaseWorkerPrivate : public QObject +{ + Q_OBJECT + friend class AppDatabaseInterface; + +public: + explicit AppDatabaseWorkerPrivate(AppDatabaseInterface *parent = nullptr); + DataEntityVector getAllApps(); + + // 数据库操作函数 + void setAppProperty(const QString &appid, const UkuiSearch::ApplicationPropertyMap &propertyMap); + void setAppProperty(const QString &appid, const UkuiSearch::ApplicationProperty::Property &property, const QVariant &value); + +private Q_SLOTS: + /** + * 应用数据库的添加信号处理函数 + * @param infos 新增应用的id列表 + */ + void onAppDatabaseAdded(const QStringList &infos); + void onAppDatabaseUpdate(const UkuiSearch::ApplicationInfoMap &infoMap); + void onAppDatabaseUpdateAll(const QStringList &infos); + void onAppDatabaseDeleted(const QStringList &infos); + +private: + static void addInfoToApp(const QMap &info, DataEntity &app); + bool isFiltered(const UkuiSearch::ApplicationPropertyMap &appInfo) const; + +private: + AppDatabaseInterface *q {nullptr}; + + UkuiSearch::ApplicationInfo *appDatabase {nullptr}; + UkuiSearch::ApplicationProperties properties; + UkuiSearch::ApplicationPropertyMap filter; +}; + +AppDatabaseWorkerPrivate::AppDatabaseWorkerPrivate(AppDatabaseInterface *parent) : QObject(parent), q(parent) +{ + // 注册需要在信号和槽函数中使用的数据结构 + qRegisterMetaType("DataEntityVector"); + + // 初始化应用数据库链接 + appDatabase = new UkuiSearch::ApplicationInfo(this); + + // 首次启动时,为某些应用设置标志位 + if (UserConfig::instance()->isFirstStartUp()) { + // 默认收藏应用 + for (const auto &appid : GlobalSetting::instance()->defaultFavoriteApps()) { + appDatabase->setAppToFavorites(appid); + } + } + + // 设置从数据库查询哪些属性 + properties << UkuiSearch::ApplicationProperty::Property::Top + << UkuiSearch::ApplicationProperty::Property::Lock + << UkuiSearch::ApplicationProperty::Property::Favorites + << UkuiSearch::ApplicationProperty::Property::LaunchTimes + << UkuiSearch::ApplicationProperty::Property::DesktopFilePath + << UkuiSearch::ApplicationProperty::Property::Icon + << UkuiSearch::ApplicationProperty::Property::LocalName + << UkuiSearch::ApplicationProperty::Property::Category + << UkuiSearch::ApplicationProperty::Property::FirstLetterAll + << UkuiSearch::ApplicationProperty::Property::DontDisplay + << UkuiSearch::ApplicationProperty::Property::AutoStart + << UkuiSearch::ApplicationProperty::Property::InsertTime + << UkuiSearch::ApplicationProperty::Property::Launched; + + // 需要从数据库过滤的属性 + filter.insert(UkuiSearch::ApplicationProperty::Property::DontDisplay, 0); + filter.insert(UkuiSearch::ApplicationProperty::Property::AutoStart, 0); + + // 链接数据库信号 + connect(appDatabase, &UkuiSearch::ApplicationInfo::appDBItems2BAdd, this, &AppDatabaseWorkerPrivate::onAppDatabaseAdded); + connect(appDatabase, &UkuiSearch::ApplicationInfo::appDBItems2BUpdate, this, &AppDatabaseWorkerPrivate::onAppDatabaseUpdate); + connect(appDatabase, &UkuiSearch::ApplicationInfo::appDBItems2BUpdateAll, this, &AppDatabaseWorkerPrivate::onAppDatabaseUpdateAll); + connect(appDatabase, &UkuiSearch::ApplicationInfo::appDBItems2BDelete, this, &AppDatabaseWorkerPrivate::onAppDatabaseDeleted); + connect(appDatabase, &UkuiSearch::ApplicationInfo::DBOpenFailed, q, &AppDatabaseInterface::appDatabaseOpenFailed); +} + +void AppDatabaseWorkerPrivate::addInfoToApp(const UkuiSearch::ApplicationPropertyMap &info, DataEntity &app) +{ + app.setTop(info.value(UkuiSearch::ApplicationProperty::Property::Top).toInt()); + app.setLock(info.value(UkuiSearch::ApplicationProperty::Property::Lock).toInt() == 1); + app.setFavorite(info.value(UkuiSearch::ApplicationProperty::Property::Favorites).toInt()); + app.setLaunchTimes(info.value(UkuiSearch::ApplicationProperty::Property::LaunchTimes).toInt()); + app.setId(info.value(UkuiSearch::ApplicationProperty::Property::DesktopFilePath).toString()); + app.setIcon(APP_ICON_PREFIX + info.value(UkuiSearch::ApplicationProperty::Property::Icon).toString()); + app.setName(info.value(UkuiSearch::ApplicationProperty::Property::LocalName).toString()); + app.setCategory(info.value(UkuiSearch::ApplicationProperty::Property::Category).toString()); + app.setFirstLetter(info.value(UkuiSearch::ApplicationProperty::Property::FirstLetterAll).toString()); + app.setInsertTime(info.value(UkuiSearch::ApplicationProperty::Property::InsertTime).toString()); + app.setLaunched(info.value(UkuiSearch::ApplicationProperty::Property::Launched).toInt()); +} + +bool AppDatabaseWorkerPrivate::isFiltered(const UkuiSearch::ApplicationPropertyMap &appInfo) const +{ + QMapIterator iterator(filter); + while (iterator.hasNext()) { + iterator.next(); + if (appInfo.value(iterator.key()) == iterator.value()) { + return true; + } + } + + return false; +} + +DataEntityVector AppDatabaseWorkerPrivate::getAllApps() +{ + UkuiSearch::ApplicationInfoMap appInfos = appDatabase->getInfo(properties, filter); + if (appInfos.isEmpty()) { + return {}; + } + + DataEntityVector apps; + for (const auto &info : appInfos) { + DataEntity app; + AppDatabaseWorkerPrivate::addInfoToApp(info, app); + apps.append(app); + } + + return apps; +} + +void AppDatabaseWorkerPrivate::onAppDatabaseAdded(const QStringList &infos) +{ + if (infos.isEmpty()) { + return; + } + + DataEntityVector apps; + for (const QString &appid : infos) { + const UkuiSearch::ApplicationPropertyMap appInfo = appDatabase->getInfo(appid, properties); + if (isFiltered(appInfo)) { + continue; + } + + DataEntity app; + addInfoToApp(appInfo, app); + apps.append(app); + } + + if (apps.isEmpty()) { + return; + } + + Q_EMIT q->appAdded(apps); +} + +void AppDatabaseWorkerPrivate::onAppDatabaseUpdate(const UkuiSearch::ApplicationInfoMap &infoMap) +{ + if (infoMap.isEmpty()) { + return; + } + + QVector > > updates; + QMapIterator iterator(infoMap); + while (iterator.hasNext()) { + iterator.next(); + + DataEntity app; + QVector roles; + + QMapIterator it(iterator.value()); + while (it.hasNext()) { + it.next(); + + switch (it.key()) { + case UkuiSearch::ApplicationProperty::LocalName: + app.setName(it.value().toString()); + roles.append(DataEntity::Name); + break; + case UkuiSearch::ApplicationProperty::FirstLetterAll: + app.setFirstLetter(it.value().toString()); + roles.append(DataEntity::FirstLetter); + break; + case UkuiSearch::ApplicationProperty::Icon: + app.setIcon(it.value().toString()); + roles.append(DataEntity::Icon); + break; + case UkuiSearch::ApplicationProperty::InsertTime: + app.setInsertTime(it.value().toString()); + roles.append(DataEntity::Icon); + break; + case UkuiSearch::ApplicationProperty::Category: + app.setCategory(it.value().toString()); + roles.append(DataEntity::Category); + break; + case UkuiSearch::ApplicationProperty::LaunchTimes: + app.setLaunchTimes(it.value().toInt()); + roles.append(DataEntity::LaunchTimes); + break; + case UkuiSearch::ApplicationProperty::Favorites: + app.setFavorite(it.value().toInt()); + roles.append(DataEntity::Favorite); + break; + case UkuiSearch::ApplicationProperty::Launched: + app.setLaunched(it.value().toInt()); + roles.append(DataEntity::IsLaunched); + break; + case UkuiSearch::ApplicationProperty::Top: + app.setTop(it.value().toInt()); + roles.append(DataEntity::Top); + break; + case UkuiSearch::ApplicationProperty::Lock: + app.setLock(it.value().toBool()); + roles.append(DataEntity::IsLocked); + break; + default: + break; + } + } + + // 这个函数中,没有更新列不会发送信号 + if (roles.isEmpty()) { + continue; + } + + app.setId(iterator.key()); + updates.append({app, roles}); + } + + Q_EMIT q->appUpdated(updates); +} + +void AppDatabaseWorkerPrivate::onAppDatabaseDeleted(const QStringList &infos) +{ + if (infos.empty()) { + return; + } + + Q_EMIT q->appDeleted(infos); +} + +void AppDatabaseWorkerPrivate::onAppDatabaseUpdateAll(const QStringList &infos) +{ + if (infos.isEmpty()) { + return; + } + + QVector > > updates; + DataEntityVector apps; + + for (const auto &appid : infos) { + const UkuiSearch::ApplicationPropertyMap appInfo = appDatabase->getInfo(appid, properties); + DataEntity app; + addInfoToApp(appInfo, app); + apps.append(app); + + updates.append({app, {}}); + } + + Q_EMIT q->appUpdated(updates); +} + +void AppDatabaseWorkerPrivate::setAppProperty(const QString &appid, const UkuiSearch::ApplicationPropertyMap &propertyMap) +{ + QMapIterator iterator(propertyMap); + while (iterator.hasNext()) { + iterator.next(); + setAppProperty(appid, iterator.key(), iterator.value()); + } +} + +void AppDatabaseWorkerPrivate::setAppProperty(const QString &appid, const UkuiSearch::ApplicationProperty::Property &property, + const QVariant &value) +{ + switch (property) { + case UkuiSearch::ApplicationProperty::Favorites: + appDatabase->setFavoritesOfApp(appid, value.toInt()); + break; + case UkuiSearch::ApplicationProperty::Launched: + appDatabase->setAppLaunchedState(appid, value.toBool()); + break; + case UkuiSearch::ApplicationProperty::Top: + appDatabase->setTopOfApp(appid, value.toInt()); + break; + default: + break; + } +} + +// ====== AppDatabaseInterface ====== // +AppDatabaseInterface::AppDatabaseInterface(QObject *parent) : QObject(parent), d(new AppDatabaseWorkerPrivate(this)) +{ + +} + +DataEntityVector AppDatabaseInterface::apps() const +{ + return d->getAllApps(); +} + +void AppDatabaseInterface::fixAppToTop(const QString &appid, int index) +{ + if (index < 0) { + index = 0; + } + + d->setAppProperty(appid, UkuiSearch::ApplicationProperty::Top, index); +} + +void AppDatabaseInterface::fixAppToFavorite(const QString &appid, int index) +{ + if (index < 0) { + index = 0; + } + + d->setAppProperty(appid, UkuiSearch::ApplicationProperty::Favorites, index); +} + +void AppDatabaseInterface::updateApLaunchedState(const QString &appid, bool state) +{ + d->setAppProperty(appid, UkuiSearch::ApplicationProperty::Launched, state); +} + +} // UkuiMenu + +#include "app-database-interface.moc" diff --git a/src/libappdata/app-database-interface.h b/src/libappdata/app-database-interface.h new file mode 100644 index 0000000..efcf745 --- /dev/null +++ b/src/libappdata/app-database-interface.h @@ -0,0 +1,85 @@ +// +// Created by hxf on 23-12-11. +// + +#ifndef UKUI_MENU_APP_DATABASE_INTERFACE_H +#define UKUI_MENU_APP_DATABASE_INTERFACE_H + +#include +#include +#include "data-entity.h" + +namespace UkuiMenu { + +typedef QVector DataEntityVector; + +/** + * 封装与应用数据库的链接 + */ +class AppDatabaseWorkerPrivate; + +class AppDatabaseInterface : public QObject +{ + Q_OBJECT +public: + explicit AppDatabaseInterface(QObject *parent = nullptr); + + /** + * 从数据库获取全部应用 + * @return + */ + DataEntityVector apps() const; + + /** + * 置顶应用 + * @param appid 应用id + * @param index 置顶后的位置,小于或等于0将会取消置顶 + */ + void fixAppToTop(const QString &appid, int index); + + /** + * 收藏应用 + * @param appid 应用id + * @param index 收藏后的位置,小于或等于0将会取消收藏 + */ + void fixAppToFavorite(const QString &appid, int index); + + /** + * 设置应用是否已经被启动过 + * @param state 状态值,true已被启动,false未被启动 + */ + void updateApLaunchedState(const QString &appid, bool state = true); + +Q_SIGNALS: + /** + * 新增应用信号 + * @param apps 新增加的应用 + */ + void appAdded(const UkuiMenu::DataEntityVector &apps); + + /** + * 更新的应用及其属性列表 + * @param updates + */ + void appUpdated(const QVector > > &updates); + + /** + * 应用被删除的信号 + * @param apps 返回被删除应用的id列表 + */ + void appDeleted(const QStringList &apps); + + /** + * 应用数据库打开失败信号 + */ + void appDatabaseOpenFailed(); + +private: + AppDatabaseWorkerPrivate *d {nullptr}; +}; + +} // UkuiMenu + +Q_DECLARE_METATYPE(UkuiMenu::DataEntityVector) + +#endif //UKUI_MENU_APP_DATABASE_INTERFACE_H diff --git a/src/libappdata/basic-app-model.cpp b/src/libappdata/basic-app-model.cpp new file mode 100644 index 0000000..796c3d4 --- /dev/null +++ b/src/libappdata/basic-app-model.cpp @@ -0,0 +1,164 @@ +// +// Created by hxf on 23-12-11. +// + +#include "basic-app-model.h" + +#include + +namespace UkuiMenu { + +BasicAppModel::BasicAppModel(QObject *parent) : QAbstractListModel(parent) + , m_databaseInterface(new AppDatabaseInterface(this)) +{ + connect(m_databaseInterface, &AppDatabaseInterface::appDatabaseOpenFailed, this, [this] { + qWarning() << "BasicAppModel: app database open failed."; + m_apps.clear(); + // TODO: 显示错误信息到界面 + }); + + m_apps = m_databaseInterface->apps(); + + connect(m_databaseInterface, &AppDatabaseInterface::appAdded, this, &BasicAppModel::onAppAdded); + connect(m_databaseInterface, &AppDatabaseInterface::appUpdated, this, &BasicAppModel::onAppUpdated); + connect(m_databaseInterface, &AppDatabaseInterface::appDeleted, this, &BasicAppModel::onAppDeleted); +} + +int BasicAppModel::rowCount(const QModelIndex &parent) const +{ + return m_apps.count(); +} + +int BasicAppModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QVariant BasicAppModel::data(const QModelIndex &index, int role) const +{ + if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { + return {}; + } + + const DataEntity &app = m_apps[index.row()]; + return app.getValue(static_cast(role)); +} + +bool BasicAppModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { + return false; + } + + DataEntity &app = m_apps[index.row()]; + DataEntity::PropertyName propertyName; + switch (propertyName) { + case DataEntity::Group: + app.setGroup(value.toString()); + return true; + case DataEntity::IsLaunched: { + if (value.toBool() == app.launched()) { + return false; + } + m_databaseInterface->updateApLaunchedState(app.id(), value.toBool()); + return true; + } + case DataEntity::Favorite: + if (value.toInt() == app.favorite()) { + return false; + } + m_databaseInterface->fixAppToFavorite(app.id(), value.toInt()); + return true; + case DataEntity::Top: + if (value.toInt() == app.top()) { + return false; + } + m_databaseInterface->fixAppToTop(app.id(), value.toInt()); + return true; + case DataEntity::RecentInstall: + app.setRecentInstall(value.toBool()); + return true; + default: + break; + } + + return QAbstractItemModel::setData(index, value, role); +} + +QHash BasicAppModel::roleNames() const +{ + return DataEntity::AppRoleNames(); +} + +AppDatabaseInterface *BasicAppModel::databaseInterface() const +{ + return m_databaseInterface; +} + +void BasicAppModel::onAppAdded(const DataEntityVector &apps) +{ + beginInsertRows(QModelIndex(), m_apps.size(), m_apps.size() + apps.size() - 1); + m_apps.append(apps); + endInsertRows(); +} + +void BasicAppModel::onAppUpdated(const QVector > > &updates) +{ + for (const auto &pair : updates) { + int index = indexOfApp(pair.first.id()); + if (index < 0) { + continue; + } + + DataEntity &app = m_apps[index]; + QVector roles = pair.second; + if (!roles.isEmpty()) { + for (const auto &role : roles) { + app.setValue(static_cast(role), static_cast(role)); + } + } else { + app = pair.first; + } + + dataChanged(QAbstractListModel::index(index), QAbstractListModel::index(index), roles); + } +} + +void BasicAppModel::onAppDeleted(const QStringList &apps) +{ + for (const auto &appid : apps) { + int index = indexOfApp(appid); + if (index < 0) { + continue; + } + + beginRemoveRows(QModelIndex(), index, index); + m_apps.takeAt(index); + endRemoveRows(); + } +} + +int BasicAppModel::indexOfApp(const QString &appid) const +{ + auto it = std::find_if(m_apps.constBegin(), m_apps.constEnd(), [&appid] (const DataEntity &app) { + return app.id() == appid; + }); + + if (it == m_apps.constEnd()) { + return -1; + } + + return std::distance(m_apps.constBegin(), it); +} + +DataEntity BasicAppModel::appOfIndex(int row) const +{ + QModelIndex idx = QAbstractListModel::index(row); + if (!checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { + return {}; + } + + return m_apps.at(row); +} + +} // UkuiMenu diff --git a/src/libappdata/basic-app-model.h b/src/libappdata/basic-app-model.h new file mode 100644 index 0000000..5ff93cd --- /dev/null +++ b/src/libappdata/basic-app-model.h @@ -0,0 +1,49 @@ +// +// Created by hxf on 23-12-11. +// + +#ifndef UKUI_MENU_BASIC_APP_MODEL_H +#define UKUI_MENU_BASIC_APP_MODEL_H + +#include +#include + +#include "data-entity.h" +#include "app-database-interface.h" + +namespace UkuiMenu { + +class BasicAppModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit BasicAppModel(QObject *parent = nullptr); + + /** + * 数据库访问接口 + * @return @AppDatabaseInterface * + */ + AppDatabaseInterface *databaseInterface() const; + DataEntity appOfIndex(int row) const; + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QHash roleNames() const override; + +private Q_SLOTS: + void onAppAdded(const UkuiMenu::DataEntityVector &apps); + void onAppUpdated(const QVector > > &updates); + void onAppDeleted(const QStringList &apps); + +private: + int indexOfApp(const QString &appid) const; + AppDatabaseInterface *m_databaseInterface {nullptr}; + + QVector m_apps; +}; + +} // UkuiMenu + +#endif //UKUI_MENU_BASIC_APP_MODEL_H