diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8a0479f..98ed6d4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -83,6 +83,7 @@ set(SOURCE_FILES
src/model/model-manager.cpp src/model/model-manager.h
src/appdata/app-icon-provider.cpp src/appdata/app-icon-provider.h
src/windows/menu-main-window.cpp src/windows/menu-main-window.h
+ src/appdata/app-folder-helper.cpp src/appdata/app-folder-helper.h
src/extension/menu-extension.cpp src/extension/menu-extension.h
src/extension/menu-extension-iface.h
src/appdata/data-provider-plugin-iface.h
diff --git a/src/appdata/app-folder-helper.cpp b/src/appdata/app-folder-helper.cpp
new file mode 100644
index 0000000..276203e
--- /dev/null
+++ b/src/appdata/app-folder-helper.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2022, KylinSoft Co., Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "app-folder-helper.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define FOLDER_FILE_PATH ".config/ukui-menu/"
+#define FOLDER_FILE_NAME "folder.json"
+
+namespace UkuiMenu {
+
+QString AppFolderHelper::s_folderConfigFile = QDir::homePath() + "/" + FOLDER_FILE_PATH + FOLDER_FILE_NAME;
+
+AppFolderHelper *AppFolderHelper::instance()
+{
+ static AppFolderHelper appFolderHelper;
+ return &appFolderHelper;
+}
+
+AppFolderHelper::AppFolderHelper()
+{
+ if (!QFile::exists(s_folderConfigFile)) {
+ QDir dir;
+ QString folderConfigDir(QDir::homePath() + "/" + FOLDER_FILE_PATH);
+ if (!dir.exists(folderConfigDir)) {
+ if (!dir.mkdir(folderConfigDir)) {
+ qWarning() << "Unable to create profile folder.";
+ return;
+ }
+ }
+
+ QFile file(s_folderConfigFile);
+ file.open(QFile::WriteOnly);
+ file.close();
+ }
+
+ readData();
+}
+
+AppFolderHelper::~AppFolderHelper()
+{
+ saveData();
+}
+
+void AppFolderHelper::insertFolder(const Folder &folder)
+{
+ QMutexLocker locker(&m_mutex);
+ m_folders.append(folder);
+}
+
+void AppFolderHelper::addAppToFolder(const QString& appName, const QString& folderName)
+{
+ if (appName.isEmpty() || folderName.isEmpty()) {
+ return;
+ }
+
+ {
+ QMutexLocker locker(&m_mutex);
+ Folder *folder = findFolder(folderName);
+ if (folder) {
+ if (!folder->apps.contains(appName)) {
+ folder->apps.append(appName);
+ }
+ return;
+ }
+ }
+
+ // 新文件夹放最后
+ Folder folder;
+ folder.name = folderName;
+ folder.apps.append(appName);
+ if (m_folders.isEmpty()) {
+ folder.index = 1;
+ } else {
+ folder.index = m_folders.last().index + 1;
+ }
+
+ insertFolder(folder);
+}
+
+void AppFolderHelper::removeAppFromFolder(const QString &appName, const QString &folderName)
+{
+ if (appName.isEmpty() || folderName.isEmpty()) {
+ return;
+ }
+
+ QMutexLocker locker(&m_mutex);
+ Folder *folder = findFolder(folderName);
+ if (!folder) {
+ return;
+ }
+
+ if (folder->apps.contains(appName)) {
+ folder->apps.removeOne(appName);
+ }
+}
+
+bool AppFolderHelper::deleteFolder(const QString &folderName)
+{
+ if (folderName.isEmpty()) {
+ return false;
+ }
+
+ QMutexLocker locker(&m_mutex);
+ QVector::iterator iterator = m_folders.begin();
+ for (; iterator != m_folders.end(); ++iterator) {
+ if (folderName == iterator->name) {
+ m_folders.erase(iterator);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+QVector AppFolderHelper::folderData()
+{
+ QMutexLocker locker(&m_mutex);
+ return m_folders;
+}
+
+const Folder *AppFolderHelper::searchFolder(const QString &folderName)
+{
+ QMutexLocker locker(&m_mutex);
+ return findFolder(folderName);
+}
+
+const Folder *AppFolderHelper::searchFolderByAppName(const QString &appName)
+{
+ QMutexLocker locker(&m_mutex);
+ return findFolderByAppName(appName);
+}
+
+Folder *AppFolderHelper::findFolder(const QString &folderName)
+{
+ if (m_folders.empty()) {
+ return nullptr;
+ }
+
+ QVector::iterator iterator = m_folders.begin();
+ for (; iterator != m_folders.end(); ++iterator) {
+ if (folderName == iterator->name) {
+ // 如果定义了 QT_STRICT_ITERATORS 宏,那么迭代器为:class iterator; 否则迭代器为:typedef T* iterator;
+ // kylin上的qt库没有定义QT_STRICT_ITERATORS宏,需要注意该处可能出现的编译错误,参见QVector源码。
+ // return &*iterator;
+ return iterator;
+ }
+ }
+
+ return nullptr;
+}
+
+Folder *AppFolderHelper::findFolderByAppName(const QString &appName)
+{
+ if (m_folders.empty()) {
+ return nullptr;
+ }
+
+ QVector::iterator iterator = m_folders.begin();
+ for (; iterator != m_folders.end(); ++iterator) {
+ if (iterator->apps.contains(appName)) {
+ return iterator;
+ }
+ }
+
+ return nullptr;
+}
+
+void AppFolderHelper::forceSync()
+{
+ saveData();
+ //readData();
+}
+
+void AppFolderHelper::readData()
+{
+ QFile file(s_folderConfigFile);
+ if (!file.open(QFile::ReadOnly)) {
+ return;
+ }
+
+ // 读取json数据
+ QByteArray byteArray = file.readAll();
+ file.close();
+
+ QJsonDocument jsonDocument(QJsonDocument::fromJson(byteArray));
+ if (jsonDocument.isNull() || jsonDocument.isEmpty() || !jsonDocument.isArray()) {
+ qWarning() << "AppFolderHelper: Incorrect configuration files are ignored.";
+ return;
+ }
+
+ {
+ QMutexLocker locker(&m_mutex);
+ m_folders.clear();
+ }
+
+ // 遍历json数据节点
+ QJsonArray jsonArray = jsonDocument.array();
+ QJsonArray::iterator iterator = jsonArray.begin();
+ while (iterator != jsonArray.end()) {
+ QJsonObject object = (*iterator).toObject();
+
+ if (object.contains("name") && object.contains("index") && object.contains("apps")) {
+ Folder folder;
+
+ folder.name = object.value(QLatin1String("name")).toString();
+ folder.index = object.value(QLatin1String("index")).toInt();
+
+ QJsonArray apps = object.value(QLatin1String("apps")).toArray();
+ for (const auto &app : apps) {
+ folder.apps.append(app.toString());
+ }
+
+ if (!folder.apps.isEmpty()) {
+ insertFolder(folder);
+ }
+ }
+
+ ++iterator;
+ }
+}
+
+void AppFolderHelper::saveData()
+{
+ QFile file(s_folderConfigFile);
+ if (!file.open(QFile::WriteOnly)) {
+ return;
+ }
+
+ // 读取json数据
+ QJsonDocument jsonDocument;
+ QJsonArray folderArray;
+
+ {
+ QMutexLocker locker(&m_mutex);
+ for (const auto &folder : m_folders) {
+ QJsonObject object;
+ QJsonArray apps;
+
+ for (const auto &app : folder.apps) {
+ apps.append(app);
+ }
+
+ object.insert("name", folder.name);
+ object.insert("index", folder.index);
+ object.insert("apps", apps);
+
+ folderArray.append(object);
+ }
+ }
+
+ jsonDocument.setArray(folderArray);
+
+ if (file.write(jsonDocument.toJson()) == -1) {
+ qWarning() << "Error saving configuration file.";
+ }
+ file.flush();
+ file.close();
+}
+
+} // UkuiMenu
diff --git a/src/appdata/app-folder-helper.h b/src/appdata/app-folder-helper.h
new file mode 100644
index 0000000..775fd74
--- /dev/null
+++ b/src/appdata/app-folder-helper.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022, KylinSoft Co., Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#ifndef UKUI_MENU_APP_FOLDER_HELPER_H
+#define UKUI_MENU_APP_FOLDER_HELPER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace UkuiMenu {
+
+class Folder
+{
+ Q_GADGET
+ Q_PROPERTY(QString index READ getIndex)
+ Q_PROPERTY(QString name READ getName)
+ Q_PROPERTY(QStringList apps READ getApps)
+public:
+ int getIndex() { return index; }
+ QString getName() { return name; }
+ QStringList getApps() { return apps; }
+
+public:
+ int index;
+ QString name;
+ QStringList apps;
+};
+
+class AppFolderHelper : public QObject
+{
+ Q_OBJECT
+public:
+ static AppFolderHelper *instance();
+ ~AppFolderHelper() override;
+
+ AppFolderHelper(const AppFolderHelper& obj) = delete;
+ AppFolderHelper &operator=(const AppFolderHelper& obj) = delete;
+ AppFolderHelper(AppFolderHelper&& obj) = delete;
+ AppFolderHelper &operator=(AppFolderHelper&& obj) = delete;
+
+ // TODO 修改文件夹信息
+ // xxxx
+ const Folder *searchFolder(const QString& folderName);
+ const Folder *searchFolderByAppName(const QString& appName);
+ QVector folderData();
+
+ void addAppToFolder(const QString& appName, const QString& folderName);
+ void removeAppFromFolder(const QString& appName, const QString& folderName);
+ bool deleteFolder(const QString& folderName);
+
+ void forceSync();
+
+Q_SIGNALS:
+ void folderDataChanged();
+
+private:
+ AppFolderHelper();
+ void readData();
+ void saveData();
+ void insertFolder(const Folder& folder);
+ Folder *findFolder(const QString& folderName);
+ Folder *findFolderByAppName(const QString& appName);
+ // TODO 配置文件监听
+
+private:
+ QMutex m_mutex;
+ QVector m_folders;
+ static QString s_folderConfigFile;
+};
+
+} // UkuiMenu
+
+#endif //UKUI_MENU_APP_FOLDER_HELPER_H