ukui-search/ukui-search-app-data-service/app-db-manager.cpp

1198 lines
44 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "app-db-manager.h"
#include "file-utils.h"
#include "convert-winid-to-desktop.h"
#include <qt5xdg/XdgDesktopFile>
#include <QMutexLocker>
#include <QCryptographicHash>
#include <QFile>
#define GENERAL_APP_DESKTOP_PATH "/usr/share/applications"
#define ANDROID_APP_DESKTOP_PATH QDir::homePath() + "/.local/share/applications"
#define SNAPD_APP_DESKTOP_PATH "/var/lib/snapd/desktop/applications"
#define AUTOSTART_APP_DESKTOP_PATH "/etc/xdg/autostart"
#define LAST_LOCALE_NAME QDir::homePath() + "/.config/org.ukui/ukui-search/appdata/last-locale-name.conf"
#define LOCALE_NAME_VALUE "CurrentLocaleName"
#define APP_DATABASE_VERSION_CONFIG QDir::homePath() + "/.config/org.ukui/ukui-search/appdata/app-database-version.conf"
#define APP_DATABASE_VERSION_VALUE "AppDatabaseVersion"
using namespace UkuiSearch;
static AppDBManager *global_instance;
QMutex AppDBManager::s_mutex;
AppDBManager *AppDBManager::getInstance()
{
if (!global_instance) {
global_instance = new AppDBManager();
}
return global_instance;
}
AppDBManager::AppDBManager(QObject *parent) : QThread(parent), m_database(QSqlDatabase())
{
//链接数据库
if (openDataBase()) {
//建数据库
buildAppInfoDB();
//监听系统语言变化
m_lastLocaleNameQsettings = new QSettings(LAST_LOCALE_NAME, QSettings::IniFormat);
m_localeChanged = false;
m_lastLocaleNameQsettings->beginGroup(LOCALE_NAME_VALUE);
QString lastLocale = m_lastLocaleNameQsettings->value(LOCALE_NAME_VALUE).toString();
if (QLocale::system().name().compare(lastLocale)) {
qDebug() << "I'm going to update the locale name in conf file.";
if (!lastLocale.isEmpty()) {
m_localeChanged = true;
}
m_lastLocaleNameQsettings->setValue(LOCALE_NAME_VALUE, QLocale::system().name());
}
m_lastLocaleNameQsettings->endGroup();
//检查数据库版本
m_dbVersionQsettings = new QSettings(APP_DATABASE_VERSION_CONFIG, QSettings::IniFormat);
m_dbVersionQsettings->beginGroup(APP_DATABASE_VERSION_VALUE);
QString dbVersion = m_dbVersionQsettings->value(APP_DATABASE_VERSION_VALUE).toString();
if (dbVersion.isEmpty()) {
m_dbVersionNeedUpdate = true;
} else if (dbVersion != APP_DATABASE_VERSION) {
if (dbVersion.toDouble() < APP_DATABASE_VERSION.toDouble()) {
m_dbVersionNeedUpdate = true;
} else {
qDebug() << "app db version old version:" << dbVersion.toDouble() << "new version:" << APP_DATABASE_VERSION.toDouble();
}
}
m_dbVersionQsettings->endGroup();
if (m_dbVersionNeedUpdate) {
qDebug() << "app db version need update! old version:" << dbVersion.toDouble() << "new version:" << APP_DATABASE_VERSION.toDouble();
}
//版本号改变更新数据库字段
if (m_dbVersionNeedUpdate) {
for (auto iter = m_namesOfAppinfoTable.constBegin(); iter!= m_namesOfAppinfoTable.constEnd(); iter++) {
this->addItem2BackIfNotExist(iter.key(), iter.value());
}
}
//刷新应用数据
QStringList appPaths;
appPaths << GENERAL_APP_DESKTOP_PATH << ANDROID_APP_DESKTOP_PATH
<< SNAPD_APP_DESKTOP_PATH << AUTOSTART_APP_DESKTOP_PATH;
refreshAllData2DB(appPaths);
if (m_dbVersionNeedUpdate) {
m_dbVersionQsettings->beginGroup(APP_DATABASE_VERSION_VALUE);
m_dbVersionQsettings->setValue(APP_DATABASE_VERSION_VALUE, APP_DATABASE_VERSION);
m_dbVersionQsettings->endGroup();
m_dbVersionNeedUpdate = false;
}
//初始化FileSystemWatcher
initFileSystemWatcher();
/*
//初始化FileSystemWatcher
m_watchAppDir = new QFileSystemWatcher(this);
m_watchAppDir->addPath(GENERAL_APP_DESKTOP_PATH);
QDir androidPath(ANDROID_APP_DESKTOP_PATH);
if(!androidPath.exists()) {
androidPath.mkpath(ANDROID_APP_DESKTOP_PATH);
}
m_watchAppDir->addPath(ANDROID_APP_DESKTOP_PATH);
QDir snapdPath(SNAPD_APP_DESKTOP_PATH);
if(!snapdPath.exists()) {
snapdPath.mkpath(SNAPD_APP_DESKTOP_PATH);
}
m_watchAppDir->addPath(SNAPD_APP_DESKTOP_PATH);
//初始化timer
this->start();
m_timer = new QTimer();
m_maxProcessTimer = new QTimer();
m_timer->setInterval(2*1000);
m_maxProcessTimer->setInterval(5*1000);
m_timer->setSingleShot(true);
m_maxProcessTimer->setSingleShot(true);
m_timer->moveToThread(this);
m_maxProcessTimer->moveToThread(this);
connect(this, SIGNAL(startTimer()), m_timer, SLOT(start()));
connect(this, SIGNAL(maxProcessTimerStart()), m_maxProcessTimer, SLOT(start()));
connect(this, &AppDBManager::stopTimer, m_timer, &QTimer::stop);
connect(this, &AppDBManager::stopTimer, m_maxProcessTimer, &QTimer::stop);
//监听desktop文件所在目录由于directoryChange会发多次信号使用计时器阻塞
connect(m_watchAppDir, &QFileSystemWatcher::directoryChanged, this, [ = ](const QString & path) {
qDebug() << "m_watchAppDir directoryChanged:" << path;
Q_EMIT this->startTimer();
if (!m_maxProcessTimer->isActive()) {
Q_EMIT this->maxProcessTimerStart();
}
});
//计时器超时统一进行更新操作
connect(m_timer, &QTimer::timeout, this, [ & ] {
qDebug() << "Time out! Now I can update the database!";
Q_EMIT this->stopTimer();
this->refreshAllData2DB();
}, Qt::DirectConnection);
connect(m_maxProcessTimer, &QTimer::timeout, this, [ & ] {
qDebug() << "I've waited too lang, I have to update the database now!";
Q_EMIT this->stopTimer();
this->refreshAllData2DB();
}, Qt::DirectConnection);
*/
//监控应用进程开启
connect(KWindowSystem::self(), &KWindowSystem::windowAdded, [ = ](WId id) {
KWindowInfo info = KWindowSystem::windowInfo(id, 0, NET::WM2AllProperties);
if (info.valid()) {
QString desktopfp;
desktopfp = this->tranPidToDesktopFp(info.pid());
if (!desktopfp.isEmpty()) {
this->updateLaunchTimes(desktopfp);
}
}
});
} else {
qDebug() << "App-db-manager does nothing.";
}
}
AppDBManager::~AppDBManager()
{
if (m_watcher) {
delete m_watcher;
}
m_watcher = NULL;
if (m_snapdWatcher) {
delete m_snapdWatcher;
}
m_snapdWatcher = NULL;
// if(m_watchAppDir) {
// delete m_watchAppDir;
// }
// m_watchAppDir = NULL;
closeDataBase();
}
void AppDBManager::buildAppInfoDB()
{
qDebug() << "I'm going to build app info database.";
QSqlQuery sql(m_database);
QString cmd = QString("CREATE TABLE IF NOT EXISTS appInfo(%1, %2, %3, %4, %5, %6, %7, %8,%9, %10, %11, %12, %13, %14, %15, %16, %17, %18, %19, %20, %21)")
// .arg("ID INT")//自增id
.arg("DESKTOP_FILE_PATH TEXT PRIMARY KEY NOT NULL")//desktop文件路径
.arg("MODIFYED_TIME TEXT")//YYYYMMDDHHmmSS 修改日期
.arg("INSERT_TIME TEXT")//YYYYMMDDHHmmSS 插入日期
.arg("LOCAL_NAME TEXT")//本地名称,跟随系统语言
.arg("NAME_EN TEXT")//应用英文名称
.arg("NAME_ZH TEXT")//应用中文名称
.arg("PINYIN_NAME TEXT")//中文拼音
.arg("FIRST_LETTER_OF_PINYIN TEXT")//中文拼音首字母
.arg("FIRST_LETTER_ALL TEXT")//拼音和英文全拼
.arg("ICON TEXT")//图标名称(或路径)
.arg("TYPE TEXT")//应用类型
.arg("CATEGORY TEXT")//应用分类
.arg("EXEC TEXT")//应用命令
.arg("COMMENT TEXT")//应用注释
.arg("MD5 TEXT")//desktop文件内容md5值
.arg("LAUNCH_TIMES INT")//应用打开次数, 等比例缩减
.arg("FAVORITES INT")//收藏顺序0:为收藏,>0的数字表示收藏顺序
.arg("LAUNCHED INT")//应用安装后是否打开过0:未打开过;1:打开过
.arg("TOP INT")//置顶顺序 0:未置顶;>0的数字表示置顶顺序
.arg("LOCK INT")//应用是否锁定管控0未锁定1锁定
.arg("DONT_DISPLAY INT");//应用隐藏(NoDisplay, NotShowIn或位于autostart)
if (!sql.exec(cmd)) {
qWarning() << m_database.lastError() << cmd;
return;
}
}
void AppDBManager::initFileSystemWatcher()
{
m_watcher = new FileSystemWatcher;
m_watcher->addWatch(GENERAL_APP_DESKTOP_PATH);
QDir androidDir(ANDROID_APP_DESKTOP_PATH);
if(!androidDir.exists()) {
androidDir.mkpath(ANDROID_APP_DESKTOP_PATH);
}
m_watcher->addWatch(ANDROID_APP_DESKTOP_PATH);
m_snapdDir = new QDir(SNAPD_APP_DESKTOP_PATH);
if(!m_snapdDir->exists()) {
m_snapdWatcher = new FileSystemWatcher(false);
QDir dir("/var/lib/snapd");
if (!dir.exists()) {
dir.setPath("/var/lib");
}
m_snapdPath = dir.absolutePath();
m_snapdWatcher->addWatch(m_snapdPath);
} else {
m_watcher->addWatch(SNAPD_APP_DESKTOP_PATH);
}
m_watcher->addWatch(AUTOSTART_APP_DESKTOP_PATH);
connect(m_snapdWatcher, &FileSystemWatcher::created, this, [ = ] (const QString &path, bool isDir) {
if (isDir) {
//监测新增目录为/var/lib/snapd时将其替换为snapdWatcher的watchpath
if (path == "/var/lib/snapd") {
m_snapdWatcher->removeWatch(m_snapdPath);
m_snapdWatcher->addWatch(path);
qDebug() << "~~~~~~~add watch" << path << "~~~~~remove watch" << m_snapdPath;
m_snapdPath = path;
//snapd下的desktop目录可能在还没替换监听目录为/var/lib/snapd时就已经被创建因此需要特别判断
QDir dir("/var/lib/snapd/desktop");
if (dir.exists()) {
if (m_snapdDir->exists()) {
m_watcher->addWatch(SNAPD_APP_DESKTOP_PATH);
m_snapdWatcher->removeWatch(m_snapdPath);
qDebug() << "======add watch" << SNAPD_APP_DESKTOP_PATH << "======remove watch" << m_snapdPath;
}
}
}
//检测到/var/lib/snapd/desktop被创建则将监听目录替换为/var/lib/snapd/desktop/applications
if (path == "/var/lib/snapd/desktop" and m_snapdDir->exists()) {
m_watcher->addWatch(SNAPD_APP_DESKTOP_PATH);
m_snapdWatcher->removeWatch(m_snapdPath);
qDebug() << "======add watch" << SNAPD_APP_DESKTOP_PATH << "======remove watch" << m_snapdPath;
}
}
});
connect(m_watcher, &FileSystemWatcher::created, this, [ = ] (const QString &desktopfp, bool isDir) {
//event is IN_CREATE
if (!isDir and desktopfp.endsWith(".desktop")) {
this->insertDBItem(desktopfp);
}
});
connect(m_watcher, &FileSystemWatcher::moveTo, this, [ = ] (const QString &desktopfp, bool isDir) {
//event is IN_MOVED_TO
if (!isDir and desktopfp.endsWith(".desktop")) {
QStringList appPaths(desktopfp.left(desktopfp.lastIndexOf("/")));
this->refreshAllData2DB(appPaths);
}
});
connect(m_watcher, &FileSystemWatcher::modified, this, [ = ] (const QString &desktopfp) {
//event is IN_MODIFY
if (desktopfp.endsWith(".desktop")) {
this->updateDBItem(desktopfp);
}
});
connect(m_watcher, &FileSystemWatcher::moved, this, [ = ] (const QString &desktopfp, bool isDir) {
//event is IN_MOVED_FROM
if (!isDir) {
if (desktopfp.endsWith(".desktop")) {
this->deleteDBItem(desktopfp);
}
} else {
//event is IN_MOVE_SELF
qWarning() << "Dir:" << desktopfp << "has been moved to other place! Stop the watching of the desktop files in it!";
}
});
connect(m_watcher, &FileSystemWatcher::deleted, this, [ = ] (const QString &desktopfp, bool isDir) {
//event is IN_DELETE
if (!isDir) {
if (desktopfp.endsWith(".desktop")) {
this->deleteDBItem(desktopfp);
}
} else {
//event is IN_DELETE_SELF
qWarning() << "Dir:" << desktopfp << "has been deleted! Stop the watching of the desktop files in it!";
}
});
}
void AppDBManager::loadDesktopFilePaths(QString path, QFileInfoList &infolist)
{
QDir dir(path);
dir.setSorting(QDir::DirsFirst);
dir.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
infolist.append(dir.entryInfoList());
}
bool AppDBManager::addItem2BackIfNotExist(QString itemName, QString itemDataType, QVariant defult)
{
bool res(true);
QSqlQuery sql(m_database);
sql.setForwardOnly(true);
QString cmd = QString("SELECT * FROM sqlite_master WHERE name = 'appInfo' AND sql like '%%1%' ").arg(itemName);
if (!sql.exec(cmd)) {
qWarning() << m_database.lastError() << cmd;
res = false;
return res;
}
if (sql.next()) {
qDebug() << itemName << "is exist!";
return res;
}
cmd = QString("ALTER TABLE appInfo ADD '%0' %1 ").arg(itemName)
.arg(itemDataType);
if (defult != QVariant()) {
//TODO 根据数据类型将初始值转化为对应格式数据
if (itemDataType == "INT(4)") {
cmd += QString("DEFAULT %1").arg(defult.toInt());
} else if (itemDataType == "TEXT") {
cmd += QString("DEFAULT %1").arg(QString(defult.toByteArray()));
}
}
if (!sql.exec(cmd)) {
qWarning() << m_database.lastError() << cmd;
res = false;
} else {
qDebug() << "Add item" << itemName << "successful.";
}
if (!res) {
qDebug() << "Fail to addItem2Back :" << itemName;
}
return res;
}
void AppDBManager::refreshAllData2DB(const QStringList &appPaths)
{
PendingAppInfo items;
items.setHandleType(PendingAppInfo::RefreshDataBase);
items.setPathsNeedRefreshData(appPaths);
PendingAppInfoQueue::getAppInfoQueue().enqueue(items);
}
bool AppDBManager::handleLocaleDataUpdate(const QString &desktopFilePath)
{
bool res(true);
XdgDesktopFile desktopFile;
desktopFile.load(desktopFilePath);
QString localName = desktopFile.localizedValue("Name", "NULL").toString();
QString firstLetter2All = localName;
if (localName.contains(QRegExp("[\\x4e00-\\x9fa5]+"))) {
firstLetter2All = FileUtils::findMultiToneWords(localName).at(0);
}
QSqlQuery sql(m_database);
sql.prepare("UPDATE appInfo SET LOCAL_NAME=:localName, FIRST_LETTER_ALL=:firstOfLetter2All WHERE DESKTOP_FILE_PATH=:desktopFilePath");
sql.bindValue(":localName", localName);
sql.bindValue(":firstOfLetter2All", firstLetter2All);
sql.bindValue(":desktopFilePath", desktopFilePath);
if (!sql.exec()) {
qWarning() << m_database.lastError() << sql.lastQuery();
res = false;
}
if (res) {
ApplicationInfoMap appInfo;
appInfo[desktopFilePath].insert(ApplicationProperty::LocalName, QVariant(localName));
appInfo[desktopFilePath].insert(ApplicationProperty::FirstLetterAll, QVariant(firstLetter2All));
Q_EMIT this->appDBItemUpdate(appInfo);
qDebug() << "Update the locale data of " << desktopFilePath;
} else {
qDebug() << "Fail to update the locale data of " << desktopFilePath;
}
return res;
}
void AppDBManager::run()
{
exec();
}
void AppDBManager::refreshDataBase()
{
// if (m_database.transaction()) {
// this->updateAllData2DB();
// if (!m_database.commit()) {
// qWarning() << "Failed to commit !";
// m_database.rollback();
// } else if (!m_dbChanged) {
// qDebug() << "app DataBase has no changes!";
// } else {
// Q_EMIT this->finishHandleAppDB();
// }
// } else {
// qWarning() << "Failed to start transaction mode!!!";
// }
}
bool AppDBManager::openDataBase()
{
bool res(true);
QDir dir;
if (!dir.exists(APP_DATABASE_PATH)) {
dir.mkpath(APP_DATABASE_PATH);
}
if (QSqlDatabase::contains(CONNECTION_NAME)) {
m_database = QSqlDatabase::database(CONNECTION_NAME);
} else {
m_database = QSqlDatabase::addDatabase("QSQLITE", CONNECTION_NAME);
m_database.setDatabaseName(APP_DATABASE_PATH + APP_DATABASE_NAME);
}
if(!m_database.open()) {
qWarning() << "Fail to open AppDataBase, because" << m_database.lastError();
res = false;
}
return res;
}
void AppDBManager::closeDataBase()
{
m_database.close();
// delete m_database;
QSqlDatabase::removeDatabase(CONNECTION_NAME);
}
QString AppDBManager::getAppDesktopMd5(const QString &desktopfd)
{
QString res;
QFile file(desktopfd);
file.open(QIODevice::ReadOnly);
res = QString::fromStdString(QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5).toHex().toStdString());
file.close();
return res;
}
bool AppDBManager::startTransaction()
{
if (m_database.transaction()) {
return true;
} else {
qWarning() << "Failed to start transaction mode!!!";
return false;
}
}
bool AppDBManager::startCommit()
{
if (!m_database.commit()) {
qWarning() << "Failed to commit !";
m_database.rollback();
return false;
} else {
return true;
}
}
bool AppDBManager::handleDBItemInsert(const QString &desktopFilePath)
{
bool res(true);
QSqlQuery sql(m_database);
XdgDesktopFile desktopfile;
desktopfile.load(desktopFilePath);
QString hanzi, pinyin, firstLetterOfPinyin;
QString localName = desktopfile.localizedValue("Name", "NULL").toString();
QString firstLetter2All = localName;
bool isHanzi = true;
if (localName.contains(QRegExp("[\\x4e00-\\x9fa5]+"))) {
firstLetter2All = FileUtils::findMultiToneWords(localName).at(0);
}
if (desktopfile.contains("Name[zh_CN]")) {
hanzi = desktopfile.value("Name[zh_CN]").toString();
} else {
hanzi = desktopfile.value("Name").toString();
if (!hanzi.contains(QRegExp("[\\x4e00-\\x9fa5]+"))) {
isHanzi = false;
}
}
if (isHanzi) {
QStringList pinyinList = FileUtils::findMultiToneWords(hanzi);
for (int i = 0; i<pinyinList.size(); ++i) {
if (i % 2) {
firstLetterOfPinyin += pinyinList.at(i);
} else {
pinyin += pinyinList.at(i);
}
}
}
int dontDisplay = 0;
if (desktopfile.value("NoDisplay").toString().contains("true") ||
desktopfile.value("NotShowIn").toString().contains("UKUI") ||
desktopFilePath.startsWith(AUTOSTART_APP_DESKTOP_PATH)) {
dontDisplay = 1;
}
sql.prepare(QString("INSERT INTO appInfo "
"(DESKTOP_FILE_PATH, MODIFYED_TIME, INSERT_TIME, "
"LOCAL_NAME, NAME_EN, NAME_ZH, PINYIN_NAME, "
"FIRST_LETTER_OF_PINYIN, FIRST_LETTER_ALL, "
"ICON, TYPE, CATEGORY, EXEC, COMMENT, MD5, "
"LAUNCH_TIMES, FAVORITES, LAUNCHED, TOP, LOCK, DONT_DISPLAY) "
"VALUES(:desktopFilePath, '%0', '%1', :localName, :enName, :zhName, :pinyinName, :firstLetterOfPinyin, :firstLetter2All, "
":icon, :type, :categories, :exec, :comment,'%2',%3,%4,%5,%6,%7,%8)")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
.arg(getAppDesktopMd5(desktopFilePath))
.arg(0)
.arg(0)
.arg(0)
.arg(0)
.arg(0)
.arg(dontDisplay));
sql.bindValue(":desktopFilePath", desktopFilePath);
sql.bindValue(":localName", localName);
sql.bindValue(":enName", desktopfile.value("Name").toString());
sql.bindValue(":zhName", hanzi);
sql.bindValue(":pinyinName", pinyin);
sql.bindValue(":firstLetterOfPinyin", firstLetterOfPinyin);
sql.bindValue(":firstLetter2All", firstLetter2All);
sql.bindValue(":icon", desktopfile.value("Icon").toString());
sql.bindValue(":type", desktopfile.value("Type").toString());
sql.bindValue(":categories", desktopfile.value("Categories").toString());
sql.bindValue(":exec", desktopfile.value("Exec").toString());
sql.bindValue(":comment", desktopfile.value("Comment").toString());
if (!sql.exec()) {
qWarning() << m_database.lastError() << sql.lastQuery();
res = false;
}
if (res) {
Q_EMIT this->appDBItemAdd(desktopFilePath);
qDebug() << "app database add " << desktopFilePath << "success!";
} else {
qDebug() << "app database add " << desktopFilePath << "failed!";
}
return res;
}
bool AppDBManager::handleDBItemDelete(const QString &desktopFilePath)
{
bool res(true);
QSqlQuery sql(m_database);
sql.setForwardOnly(true);
QString cmd = "SELECT FAVORITES, TOP FROM APPINFO WHERE DESKTOP_FILE_PATH=:desktopFilePath";
sql.prepare(cmd);
sql.bindValue(":desktopFilePath", desktopFilePath);
//查询要删除信息的应用是否被收藏或顶置过
if (!sql.exec()) {
qWarning() << m_database.lastError() << sql.lastQuery();
} else if (sql.next()) {
int favorites = sql.value("FAVORITES").toInt();
int top = sql.value("TOP").toInt();
if (favorites) {
cmd = QString("UPDATE appInfo SET FAVORITES = FAVORITES -1 WHERE FAVORITES > %1").arg(favorites);
if (!sql.exec(cmd)) {
qWarning() << "I'm going to delete item in db, fail to update the FAVORITES because:" << m_database.lastError() << cmd;
}
}
if (top) {
cmd = QString("UPDATE appInfo SET TOP = TOP -1 WHERE TOP > %1").arg(top);
if (!sql.exec(cmd)) {
qWarning() << "I'm going to delete item in db, fail to update the TOP because:" << m_database.lastError() << cmd;
}
}
} else {
qWarning() << "Fail to exec next, because" << m_database.lastError() << "while executing " << cmd;
}
//执行删除操作
cmd = "DELETE FROM APPINFO WHERE DESKTOP_FILE_PATH=:desktopFilePath";
sql.prepare(cmd);
sql.bindValue(":desktopFilePath", desktopFilePath);
if (!sql.exec()) {
qWarning() << m_database.lastError() << cmd;
res = false;
}
if (res) {
Q_EMIT this->appDBItemDelete(desktopFilePath);
qDebug() << "app database delete " << desktopFilePath << "success!";
} else {
qDebug() << "app database delete " << desktopFilePath << "failed!";
}
return res;
}
bool AppDBManager::handleDBItemUpdate(const QString &desktopFilePath)
{
bool res(true);
int dontDisplay = 0;
XdgDesktopFile desktopfile;
desktopfile.load(desktopFilePath);
if (desktopfile.value("NoDisplay").toString().contains("true") ||
desktopfile.value("NotShowIn").toString().contains("UKUI") ||
desktopFilePath.startsWith(AUTOSTART_APP_DESKTOP_PATH)) {
dontDisplay = 1;
}
QString hanzi, pinyin, firstLetterOfPinyin;
QString localName = desktopfile.localizedValue("Name", "NULL").toString();
QString firstLetter2All = localName;
bool isHanzi = true;
if (localName.contains(QRegExp("[\\x4e00-\\x9fa5]+"))) {
firstLetter2All = FileUtils::findMultiToneWords(localName).at(0);
}
if (desktopfile.contains("Name[zh_CN]")) {
hanzi = desktopfile.value("Name[zh_CN]").toString();
} else {
hanzi = desktopfile.value("Name").toString();
if (!hanzi.contains(QRegExp("[\\x4e00-\\x9fa5]+"))) {
isHanzi = false;
}
}
if (isHanzi) {
QStringList pinyinList = FileUtils::findMultiToneWords(hanzi);
firstLetter2All = pinyinList.at(0);
for (int i = 0; i<pinyinList.size(); ++i) {
if (i % 2) {
firstLetterOfPinyin += pinyinList.at(i);
} else {
pinyin += pinyinList.at(i);
}
}
}
QSqlQuery sql(m_database);
sql.prepare(QString("UPDATE appInfo SET "
"MODIFYED_TIME='%0',"
"LOCAL_NAME=:localName,"
"NAME_EN=:enName,"
"NAME_ZH=:zhName,"
"PINYIN_NAME=:pinyinName,"
"FIRST_LETTER_OF_PINYIN=:firstLetterOfPinyin,"
"FIRST_LETTER_ALL=:firstLetter2All,"
"ICON=:icon,"
"TYPE=:type,"
"CATEGORY=:categories,"
"EXEC=:exec,"
"COMMENT=:comment,"
"MD5='%1',"
"DONT_DISPLAY=%2 "
"WHERE DESKTOP_FILE_PATH=:desktopFilePath")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
.arg(getAppDesktopMd5(desktopFilePath))
.arg(dontDisplay));
sql.bindValue(":desktopFilePath", desktopFilePath);
sql.bindValue(":localName", localName);
sql.bindValue(":enName", desktopfile.value("Name").toString());
sql.bindValue(":zhName", hanzi);
sql.bindValue(":pinyinName", pinyin);
sql.bindValue(":firstLetterOfPinyin", firstLetterOfPinyin);
sql.bindValue(":firstLetter2All", firstLetter2All);
sql.bindValue(":icon", desktopfile.value("Icon").toString());
sql.bindValue(":type", desktopfile.value("Type").toString());
sql.bindValue(":categories", desktopfile.value("Categories").toString());
sql.bindValue(":exec", desktopfile.value("Exec").toString());
sql.bindValue(":comment", desktopfile.value("Comment").toString());
if (!sql.exec()) {
qWarning() << m_database.lastError() << sql.lastQuery();
res = false;
}
if (res) {
Q_EMIT this->appDBItemUpdateAll(desktopFilePath);
qDebug() << "app database update all data of" << desktopFilePath << "success!";
} else {
qDebug() << "app database update " << desktopFilePath << "failed!";
}
return res;
}
bool AppDBManager::handleLaunchTimesUpdate(const QString &desktopFilePath, int num)
{
qDebug() << "launch times will add:" << num;
bool res(true);
QSqlQuery sql(m_database);
sql.setForwardOnly(true);
sql.prepare("SELECT LAUNCH_TIMES FROM APPINFO WHERE DESKTOP_FILE_PATH=:desktopFilePath");
sql.bindValue(":desktopFilePath", desktopFilePath);
if (sql.exec()) {
if (sql.next()) {
int launchTimes = sql.value(0).toInt() + num;
sql.prepare(QString("UPDATE appInfo SET MODIFYED_TIME='%0', LAUNCH_TIMES=%1, LAUNCHED=%2 WHERE DESKTOP_FILE_PATH=:desktopFilePath")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
.arg(launchTimes)
.arg(1));
sql.bindValue(":desktopFilePath", desktopFilePath);
if (!sql.exec()) {
qWarning() << "Set app favorites state failed!" << m_database.lastError();
res = false;
} else {
ApplicationInfoMap appInfo;
appInfo[desktopFilePath].insert(ApplicationProperty::LaunchTimes, QVariant(launchTimes));
appInfo[desktopFilePath].insert(ApplicationProperty::Launched, QVariant(1));
Q_EMIT this->appDBItemUpdate(appInfo);
qDebug() << "app database update " << desktopFilePath << "launch times: " << launchTimes << "success!";
}
} else {
qWarning() << "Failed to exec next!" << sql.lastQuery();
res = false;
}
} else {
qWarning() << "Failed to exec:" << sql.lastQuery();
res = false;
}
return res;
}
bool AppDBManager::handleFavoritesStateUpdate(const QString &desktopFilePath, int num, bool isOrderChanged)
{
if (num < 0) {
qWarning() << "Invalid favorite num, I quit!!!";
return false;
}
bool res(true);
QSqlQuery sql(m_database);
sql.setForwardOnly(true);
QString cmd;
//当直接设置的时候需要查询要设置的favorites标志位是否被占用
if (!isOrderChanged) {
cmd = QString("SELECT DESKTOP_FILE_PATH, FAVORITES FROM APPINFO WHERE FAVORITES = %1").arg(num);
if (!sql.exec(cmd)) {
qWarning() << "Fail to exec:" << cmd << "because:" << m_database.lastError();
} else {
while (sql.next()) {
if (num && sql.value("FAVORITES").toInt() == num) {
res = false;
if (sql.value("DESKTOP_FILE_PATH").toString() == desktopFilePath) {
qWarning() << "favorites state has no changes, I quit!";
return res;
} else {
qWarning() << "the favorites num:" << num << "has been used, fail to update favorites state of" << desktopFilePath;
return res;
}
}
}
}
}
//更新favorites状态
cmd = QString("UPDATE APPINFO SET MODIFYED_TIME='%0', FAVORITES=%1 WHERE DESKTOP_FILE_PATH=:desktopFilePath")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
.arg(num);
sql.prepare(cmd);
sql.bindValue(":desktopFilePath", desktopFilePath);
if (!sql.exec()) {
qWarning() << "Set app favorites state failed!" << m_database.lastError();
res = false;
} else {
ApplicationInfoMap appInfo;
appInfo[desktopFilePath].insert(ApplicationProperty::Favorites, QVariant(num));
Q_EMIT this->appDBItemUpdate(appInfo);
qDebug() << "app database update " << desktopFilePath << "favorites state: " << num << "success!";
}
return res;
}
bool AppDBManager::handleTopStateUpdate(const QString &desktopFilePath, int num, bool isOrderChanged)
{
if (num < 0) {
qWarning() << "Invalid top num, I quit!!!";
return false;
}
bool res(true);
QSqlQuery sql(m_database);
sql.setForwardOnly(true);
//当直接设置的时候需要查询要设置的top标志位是否被占用
QString cmd;
if (!isOrderChanged) {
cmd = QString("SELECT DESKTOP_FILE_PATH, TOP FROM APPINFO WHERE TOP = %1").arg(num);
if (!sql.exec(cmd)) {
qWarning() << "Fail to exec:" << cmd << "because:" << m_database.lastError();
} else {
while (sql.next()) {
if (num && sql.value("TOP").toInt() == num) {
res = false;
if (sql.value("DESKTOP_FILE_PATH").toString() == desktopFilePath) {
qWarning() << "top state has no changes, I quit!";
return res;
} else {
qWarning() << "the top num:" << num << "has been used, fail to update top state of" << desktopFilePath;
return res;
}
}
}
}
}
//更新top状态
cmd = QString("UPDATE APPINFO SET MODIFYED_TIME='%0', TOP=%1 WHERE DESKTOP_FILE_PATH=:desktopFilePath")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
.arg(num);
sql.prepare(cmd);
sql.bindValue(":desktopFilePath", desktopFilePath);
if (!sql.exec()) {
qWarning() << "Set app favorites state failed!" << m_database.lastError();
res = false;
} else {
ApplicationInfoMap appInfo;
appInfo[desktopFilePath].insert(ApplicationProperty::Top, QVariant(num));
Q_EMIT this->appDBItemUpdate(appInfo);
qDebug() << "app database update " << desktopFilePath << "top state: " << num << "success!";
}
return res;
}
bool AppDBManager::handleLockStateUpdate(const QString &desktopFilePath, int num)
{
bool res(true);
QSqlQuery sql(m_database);
sql.prepare(QString("UPDATE appInfo SET MODIFYED_TIME='%0', LOCK=%1 WHERE DESKTOP_FILE_PATH=:desktopFilePath")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
.arg(num));
sql.bindValue(":desktopFilePath", desktopFilePath);
if (!sql.exec()) {
qWarning() << "Set app favorites state failed!" << m_database.lastError();
res = false;
} else {
ApplicationInfoMap appInfo;
appInfo[desktopFilePath].insert(ApplicationProperty::Lock, QVariant(num));
Q_EMIT this->appDBItemUpdate(appInfo);
qDebug() << "app database update " << desktopFilePath << "lock state: " << num << "success!";
}
return res;
}
void AppDBManager::handleDataBaseRefresh(const QStringList &appPaths)
{
QMap<QString, QString> dataMap;
QSqlQuery query(m_database);
query.setForwardOnly(true);
QString condition;
for (int i = 0; i < appPaths.size(); i++) {
condition.append("DESKTOP_FILE_PATH LIKE ? OR ");
}
condition = condition.left(condition.lastIndexOf(" OR "));
query.prepare(QString("SELECT DESKTOP_FILE_PATH,MD5 FROM APPINFO WHERE %0").arg(condition));
for (int t = 0; t < appPaths.size(); t++) {
query.bindValue(t, appPaths.at(t) + "/%");
}
if (!query.exec()) {
qWarning() << m_database.lastError() << query.lastError();
} else {
query.exec();
while (query.next()) {
dataMap.insert(query.value("DESKTOP_FILE_PATH").toString(), query.value("MD5").toString());
}
}
//遍历desktop文件
QFileInfoList infos;
for (const QString &path : appPaths) {
this->loadDesktopFilePaths(path, infos);
}
if(infos.size() < 1) {
return;
}
XdgDesktopFile desktopfile;
for (int i = 0; i < infos.length(); i++) {
QFileInfo fileInfo = infos.at(i);
QString path = fileInfo.filePath();
//对目录递归
if (fileInfo.isDir()) {
loadDesktopFilePaths(path, infos);
continue;
}
//排除非法路径非desktop文件
if (!path.endsWith(".desktop")) {
continue;
}
desktopfile.load(path);
//排除loaclized名字为空
if (desktopfile.localizedValue("Name").toString().isEmpty()) {
continue;
}
if (!dataMap.isEmpty()) {
//数据库有记录
if (dataMap.contains(path)) {
if (!QString::compare(dataMap.value(path), getAppDesktopMd5(path)) && !m_dbVersionNeedUpdate) {
//判断系统语言是否改变
if (m_localeChanged) {
this->handleLocaleDataUpdate(path);
}
dataMap.remove(path);
continue;
} else {
//数据库有记录但md5值改变或数据库版本需要更新则update
this->handleDBItemUpdate(path);
dataMap.remove(path);
continue;
}
} else {
//数据库中没有记录则insert
this->handleDBItemInsert(path);
dataMap.remove(path);
continue;
}
}
//数据库为空则全部insert
this->handleDBItemInsert(path);
dataMap.remove(path);
}
//遍历完成后重置标志位
m_localeChanged = false;
//数据库冗余项直接delete
if (!dataMap.isEmpty()) {
for (auto i = dataMap.constBegin(); i != dataMap.constEnd(); i++) {
this->handleDBItemDelete(i.key());
}
}
}
QString AppDBManager::tranPidToDesktopFp(int pid)
{
QString exePath = QFile::symLinkTarget("/proc/" + QString::number(pid) + "/exe");
QString desktopfp;
QSqlQuery sql(m_database);
sql.setForwardOnly(true);
sql.prepare("SELECT DESKTOP_FILE_PATH, EXEC FROM APPINFO WHERE EXEC LIKE :exePath AND DONT_DISPLAY = 0");
sql.bindValue(":exePath", "%" + exePath.section('/', -1) + "%");
if (sql.exec()) {
QMap<QString, QString> execInfos;
while (sql.next()) {
execInfos[sql.value("DESKTOP_FILE_PATH").toString()] = sql.value("EXEC").toString();
desktopfp = sql.value("DESKTOP_FILE_PATH").toString();
}
//筛选后有多个结果时进一步过滤
if (execInfos.size() > 1) {
desktopfp.clear();
for (const QString &path : execInfos.values()) {
QStringList execlist = path.split(" ", QString::SkipEmptyParts);
for (QString &partOfExec : execlist) {
//remove the cmd option
if (partOfExec.contains("%")) {
continue;
}
//remove the " in cmd
if (partOfExec.contains("\"")) {
partOfExec.remove("\"");
}
//compare the binary path
if ((partOfExec.contains("/") && partOfExec == exePath) ||
(partOfExec == exePath.section("/", -1))) {
desktopfp = execInfos.key(path);
break;
}
}
if (!desktopfp.isEmpty()) {
break;
}
}
}
if (!desktopfp.isEmpty()) {
qDebug() << "PID: " << pid << "Desktop file path: " << desktopfp;
} else {
qWarning() << "Can not find the desktop file of" << exePath << "by pid:" << pid;
}
} else {
qWarning() << "Fail to exec cmd" << sql.lastQuery() << m_database.lastError();
}
return desktopfp;
}
void AppDBManager::insertDBItem(const QString &desktopfd)
{
PendingAppInfo item(desktopfd, PendingAppInfo::HandleType::Insert);
PendingAppInfoQueue::getAppInfoQueue().enqueue(item);
}
void AppDBManager::updateDBItem(const QString &desktopfd)
{
PendingAppInfo item(desktopfd, PendingAppInfo::HandleType::UpdateAll);
PendingAppInfoQueue::getAppInfoQueue().enqueue(item);
}
void AppDBManager::deleteDBItem(const QString &desktopfd)
{
PendingAppInfo item(desktopfd, PendingAppInfo::HandleType::Delete);
PendingAppInfoQueue::getAppInfoQueue().enqueue(item);
}
void AppDBManager::updateLocaleData(const QString &desktopFilePath)
{
PendingAppInfo item(desktopFilePath, PendingAppInfo::HandleType::UpdateLocaleData);
PendingAppInfoQueue::getAppInfoQueue().enqueue(item);
}
void AppDBManager::updateLaunchTimes(const QString &desktopFilePath)
{
PendingAppInfo item(desktopFilePath, PendingAppInfo::HandleType::UpdateLaunchTimes);
item.setLaunchWillAdd(true);
item.setLaunchTimes(1);
PendingAppInfoQueue::getAppInfoQueue().enqueue(item);
}
void AppDBManager::updateFavoritesState(const QString &desktopFilePath, int num, bool isOrderChanged)
{
PendingAppInfo item(desktopFilePath, PendingAppInfo::HandleType::UpdateFavorites);
item.setFavorites(num);
item.setChangeFavoritePos(isOrderChanged);
PendingAppInfoQueue::getAppInfoQueue().enqueue(item);
}
void AppDBManager::updateTopState(const QString &desktopFilePath, int num, bool isOrderChanged)
{
PendingAppInfo item(desktopFilePath, PendingAppInfo::HandleType::UpdateTop);
item.setTop(num);
item.setChangeTopPos(isOrderChanged);
PendingAppInfoQueue::getAppInfoQueue().enqueue(item);
}
void AppDBManager::udpateLockState(const QString &desktopFilePath, int num)
{
PendingAppInfo item(desktopFilePath, PendingAppInfo::HandleType::UpdateTop);
item.setLock(num);
PendingAppInfoQueue::getAppInfoQueue().enqueue(item);
}
bool AppDBManager::changeFavoriteAppPos(const QString &desktopFilePath, int pos)
{
if (pos < 1) {
qWarning() << "To be moved to a invalid favorites pos , I quit!!";
return false;
}
bool res(true);
QSqlQuery sql(m_database);
sql.setForwardOnly(true);
QString cmd = "SELECT FAVORITES FROM APPINFO WHERE DESKTOP_FILE_PATH=:desktopFilePath";
sql.prepare(cmd);
sql.bindValue(":desktopFilePath", desktopFilePath);
int previousPos = 0;
//记录应用原位置
if (!sql.exec()) {
qWarning() << "Fail to change favorite-app pos, because: " << m_database.lastError() << " when exec :" << cmd;
res = false;
} else {
if (sql.next()) {
previousPos = sql.value(0).toInt();
if (previousPos < 1) {
qWarning() << QString("app: %1 is not a favorites app, I quit!!").arg(desktopFilePath);
}
if (previousPos == pos) {
qDebug() << "favorite app's pos has no changes!";
return res;
}
cmd = QString("SELECT DESKTOP_FILE_PATH, FAVORITES FROM APPINFO WHERE FAVORITES BETWEEN MIN(%1, %2) AND MAX(%1, %2)")
.arg(previousPos)
.arg(pos);
} else {
qWarning() << "Fail to change favorite-app pos when exec next, because: " << m_database.lastError();
}
}
//更新原位置和新位置之间的应用的位置
if (!sql.exec(cmd)) {
qWarning() << "Fail to change favorite-app pos, because: " << m_database.lastError() << " when exec :" << cmd;
res = false;
} else {
while (sql.next()) {
if (sql.value("FAVORITES").toInt() == previousPos) {
this->updateFavoritesState(desktopFilePath, pos, true);
continue;
}
if (previousPos > pos) {
this->updateFavoritesState(sql.value("DESKTOP_FILE_PATH").toString(), sql.value("FAVORITES").toInt() + 1, true);
} else {
this->updateFavoritesState(sql.value("DESKTOP_FILE_PATH").toString(), sql.value("FAVORITES").toInt() - 1, true);
}
}
}
return res;
}
bool AppDBManager::changeTopAppPos(const QString &desktopFilePath, int pos)
{
if (pos < 1) {
qWarning() << "To be moved to a invalid top pos, I quit!!";
return false;
}
bool res(true);
QSqlQuery sql(m_database);
QString cmd = "SELECT TOP FROM APPINFO WHERE DESKTOP_FILE_PATH=:desktopFilePath";
sql.prepare(cmd);
sql.bindValue(":desktopFilePath", desktopFilePath);
int previousPos = 0;
//记录应用原位置
if (!sql.exec()) {
qWarning() << "Fail to change top-app pos, because: " << m_database.lastError() << " when exec :" << cmd;
res = false;
} else {
if (sql.next()) {
previousPos = sql.value(0).toInt();
if (previousPos < 1) {
qWarning() << QString("app: %1 is not a favorites app, I quit!!").arg(desktopFilePath);
res = false;
return res;
}
if (previousPos == pos) {
qDebug() << "top app's pos has no changes!";
res = false;
return res;
}
cmd = QString("SELECT DESKTOP_FILE_PATH, TOP FROM APPINFO WHERE TOP BETWEEN MIN(%1, %2) AND MAX(%1, %2)")
.arg(previousPos)
.arg(pos);
} else {
qWarning() << "Fail to change top-app pos when exec next, because: " << m_database.lastError();
}
}
//更新原位置和新位置之间的应用的位置
if (!sql.exec(cmd)) {
qWarning() << "Fail to change top-app pos, because: " << m_database.lastError() << " when exec :" << cmd;
res = false;
} else {
while (sql.next()) {
if (sql.value("TOP").toInt() == previousPos) {
this->updateTopState(desktopFilePath, pos, true);
continue;
}
if (previousPos > pos) {
this->updateTopState(sql.value("DESKTOP_FILE_PATH").toString(), sql.value("TOP").toInt() + 1, true);
} else {
this->updateTopState(sql.value("DESKTOP_FILE_PATH").toString(), sql.value("TOP").toInt() - 1, true);
}
}
}
return res;
}