UI: Show app names & icons.

This commit is contained in:
Nodir Temirkhodjaev 2019-04-21 15:21:15 +05:00
parent 2aa2f70d1b
commit a5d97fddd2
40 changed files with 662 additions and 427 deletions

View File

@ -1,8 +1,10 @@
#include "sqlitedb.h"
#include <QDataStream>
#include <QBuffer>
#include <QDebug>
#include <QDir>
#include <QImage>
#include <sqlite3.h>
@ -59,7 +61,10 @@ QVariant SqliteDb::executeEx(const char *sql,
bool *ok)
{
QVariantList list;
QStringList bindTexts;
QList<QByteArray> bindDatas;
sqlite3_stmt *stmt = nullptr;
int res;
@ -72,22 +77,29 @@ QVariant SqliteDb::executeEx(const char *sql,
int index = 0;
for (const QVariant &v : vars) {
++index;
switch (v.type()) {
const int vType = v.type();
switch (vType) {
case QVariant::Invalid:
res = sqlite3_bind_null(stmt, index);
break;
case QVariant::Bool:
case QVariant::Int:
case QVariant::UInt:
res = sqlite3_bind_int(stmt, index, v.toInt());
break;
case QVariant::LongLong:
case QVariant::ULongLong:
res = sqlite3_bind_int64(stmt, index, v.toLongLong());
break;
case QVariant::Double:
res = sqlite3_bind_double(stmt, index, v.toDouble());
break;
case QVariant::LongLong:
res = sqlite3_bind_int64(stmt, index, v.toLongLong());
break;
case QVariant::String: {
const QString text = v.toString();
const int bytesCount = text.size() * int(sizeof(wchar_t));
bindTexts.append(text);
res = sqlite3_bind_text16(stmt, index, text.utf16(),
@ -96,12 +108,37 @@ QVariant SqliteDb::executeEx(const char *sql,
}
default: {
QByteArray data;
QDataStream stream(data);
stream << v;
// Write type
QDataStream stream(&data, QIODevice::WriteOnly);
stream << vType;
// Write content
{
QByteArray bufData;
QBuffer buf(&bufData);
buf.open(QIODevice::WriteOnly);
switch (vType) {
case QVariant::Image: {
const QImage image = v.value<QImage>();
image.save(&buf, "PNG");
break;
}
default:
Q_UNREACHABLE();
}
buf.close();
stream << bufData;
}
const char *bits = data.constData();
const int bytesCount = data.size();
bindDatas.append(data);
res = sqlite3_bind_blob(stmt, index, bits,
bytesCount, SQLITE_STATIC);
}
@ -135,7 +172,27 @@ QVariant SqliteDb::executeEx(const char *sql,
QByteArray data(bits, bytesCount);
QDataStream stream(data);
stream >> v;
// Load type
int vType;
stream >> vType;
// Load content
{
QByteArray bufData;
stream >> bufData;
switch (vType) {
case QVariant::Image: {
QImage image;
image.loadFromData(bufData, "PNG");
v = image;
break;
}
default:
Q_UNREACHABLE();
}
}
break;
}
case SQLITE_NULL:
@ -156,7 +213,9 @@ QVariant SqliteDb::executeEx(const char *sql,
*ok = (res == SQLITE_OK || res == SQLITE_ROW || res == SQLITE_DONE);
}
return (list.size() == 1) ? list.at(0) : list;
const int listSize = list.size();
return (listSize == 0) ? QVariant()
: (list.size() == 1) ? list.at(0) : list;
}
qint64 SqliteDb::lastInsertRowid() const
@ -248,7 +307,8 @@ bool SqliteDb::migrate(const QString &sqlDir, int version,
QFile file(filePath);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
qWarning() << "SQLite: Cannot open migration file" << filePath;
qWarning() << "SQLite: Cannot open migration file" << filePath
<< file.errorString();
res = false;
break;
}

View File

@ -1,4 +1,4 @@
QT += core gui qml widgets
QT += core gui qml quick widgets
CONFIG += c++11
@ -46,8 +46,10 @@ SOURCES += \
task/taskupdatechecker.cpp \
task/taskworker.cpp \
translationmanager.cpp \
util/app/appiconprovider.cpp \
util/app/appinfo.cpp \
util/app/appinfocache.cpp \
util/app/appinfojob.cpp \
util/app/appinfomanager.cpp \
util/app/appinfoworker.cpp \
util/app/apputil.cpp \
@ -62,8 +64,8 @@ SOURCES += \
util/nativeeventfilter.cpp \
util/net/hostinfo.cpp \
util/net/hostinfocache.cpp \
util/net/hostinfojob.cpp \
util/net/hostinfomanager.cpp \
util/net/hostinfoworker.cpp \
util/net/ip4range.cpp \
util/net/netdownloader.cpp \
util/net/netutil.cpp \
@ -74,6 +76,7 @@ SOURCES += \
util/window/widgetwindow.cpp \
util/window/widgetwindowstatewatcher.cpp \
util/window/windowstatewatcher.cpp \
util/worker/workerjob.cpp \
util/worker/workermanager.cpp \
util/worker/workerobject.cpp
@ -113,8 +116,10 @@ HEADERS += \
task/taskupdatechecker.h \
task/taskworker.h \
translationmanager.h \
util/app/appiconprovider.h \
util/app/appinfo.h \
util/app/appinfocache.h \
util/app/appinfojob.h \
util/app/appinfomanager.h \
util/app/appinfoworker.h \
util/app/apputil.h \
@ -129,8 +134,8 @@ HEADERS += \
util/nativeeventfilter.h \
util/net/hostinfo.h \
util/net/hostinfocache.h \
util/net/hostinfojob.h \
util/net/hostinfomanager.h \
util/net/hostinfoworker.h \
util/net/ip4range.h \
util/net/netdownloader.h \
util/net/netutil.h \
@ -141,6 +146,7 @@ HEADERS += \
util/window/widgetwindow.h \
util/window/widgetwindowstatewatcher.h \
util/window/windowstatewatcher.h \
util/worker/workerjob.h \
util/worker/workermanager.h \
util/worker/workerobject.h
@ -168,8 +174,8 @@ OTHER_FILES += \
util/app/migrations/*.sql
RESOURCES += \
db/migrations.qrc \
util/app/migrations.qrc
db/db-migrations.qrc \
util/app/app-migrations.qrc
# Shadow Build: Copy i18n/ to build path
!equals(PWD, $${OUT_PWD}) {

View File

@ -26,6 +26,7 @@
#include "task/taskinfo.h"
#include "task/taskmanager.h"
#include "translationmanager.h"
#include "util/app/appiconprovider.h"
#include "util/app/appinfocache.h"
#include "util/fileutil.h"
#include "util/guiutil.h"
@ -59,7 +60,8 @@ FortManager::FortManager(FortSettings *fortSettings,
m_driverManager->driverWorker(), this)),
m_nativeEventFilter(new NativeEventFilter(this)),
m_hotKeyManager(new HotKeyManager(m_nativeEventFilter, this)),
m_taskManager(new TaskManager(this, this))
m_taskManager(new TaskManager(this, this)),
m_appInfoCache(new AppInfoCache(this))
{
setupLogger();
setupDatabaseManager();
@ -219,6 +221,10 @@ bool FortManager::setupEngine()
context->setContextProperty("fortManager", this);
context->setContextProperty("driverManager", m_driverManager);
context->setContextProperty("translationManager", TranslationManager::instance());
context->setContextProperty("appInfoCache", m_appInfoCache);
m_engine->addImageProvider(AppIconProvider::id(),
new AppIconProvider(m_appInfoCache->manager()));
m_engine->load(QUrl("qrc:/qml/main.qml"));

View File

@ -8,6 +8,7 @@
QT_FORWARD_DECLARE_CLASS(QQmlApplicationEngine)
QT_FORWARD_DECLARE_CLASS(QSystemTrayIcon)
QT_FORWARD_DECLARE_CLASS(AppInfoCache)
QT_FORWARD_DECLARE_CLASS(DatabaseManager)
QT_FORWARD_DECLARE_CLASS(DriverManager)
QT_FORWARD_DECLARE_CLASS(FirewallConf)
@ -167,6 +168,7 @@ private:
NativeEventFilter *m_nativeEventFilter;
HotKeyManager *m_hotKeyManager;
TaskManager *m_taskManager;
AppInfoCache *m_appInfoCache;
};
#endif // FORTMANAGER_H

View File

@ -9,10 +9,6 @@ ListView {
readonly property bool hasCurrentItem:
currentIndex >= 0 && currentIndex < count && !!currentItem
readonly property string currentItemText:
(currentIndex >= 0 && currentIndex < count && currentItem)
? currentItem.displayText : ""
Keys.onUpPressed: decrementCurrentIndex()
Keys.onDownPressed: incrementCurrentIndex()

View File

@ -15,12 +15,12 @@ BasePage {
readonly property Item currentAppItem:
appListView.hasCurrentItem ? appListView.currentItem : null
readonly property string currentAppPath:
(currentAppItem && currentAppItem.displayText) || ""
(currentAppItem && currentAppItem.appPath) || ""
readonly property Item currentIpItem:
ipListView.hasCurrentItem ? ipListView.currentItem : null
readonly property string currentIpText:
(currentIpItem && currentIpItem.displayText) || ""
(currentIpItem && currentIpItem.ipAddress) || ""
readonly property string currentHostName:
(currentIpItem && currentIpItem.hostName) || ""

View File

@ -18,7 +18,7 @@ BasePage {
readonly property Item currentAppItem:
appListView.hasCurrentItem ? appListView.currentItem : null
readonly property string currentAppPath:
(currentAppItem && currentAppItem.displayText) || ""
(currentAppItem && currentAppItem.appPath) || ""
readonly property var trafCellWidths: [
trafsContainer.width * 0.34,

View File

@ -1,5 +1,6 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import "../../controls"
import com.fortfirewall 1.0
@ -13,25 +14,31 @@ ListViewControl {
onClicked: forceActiveFocus()
delegate: Row {
delegate: RowLayout {
id: appItem
width: appListView.width
spacing: 6
readonly property string displayText: display
readonly property string appPath: display
readonly property var appInfo:
appInfoCache.infoTrigger && appInfoCache.appInfo(appPath)
// TODO: Use SHGetFileInfo() to get app's display name and icon
Image {
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 1
source: (!appItem.displayText && emptyIcon)
Layout.topMargin: 1
Layout.preferredWidth: 22
Layout.preferredHeight: 22
source: (!appItem.appPath && emptyIcon)
|| appInfo.iconPath
|| "qrc:/images/application.png"
}
Label {
font.pixelSize: 20
Layout.fillWidth: true
font.pixelSize: 18
elide: Text.ElideRight
text: (!appItem.displayText && emptyText)
|| fileUtil.fileName(appItem.displayText)
text: (!appItem.appPath && emptyText)
|| appInfo.fileDescription
|| fileUtil.fileName(appItem.appPath)
}
}
}

View File

@ -11,14 +11,14 @@ ListViewControl {
delegate: Label {
width: ipListView.width
elide: Text.ElideRight
text: hostName || displayText
text: hostName || ipAddress
font.italic: !!hostName
readonly property string hostName:
(firewallConf.resolveAddress
&& hostInfoCache.hostTrigger
&& hostInfoCache.hostName(displayText)) || ""
&& hostInfoCache.hostName(ipAddress)) || ""
readonly property string displayText: display
readonly property string ipAddress: display
}
}

View File

@ -0,0 +1,38 @@
#include "appiconprovider.h"
#include "appinfomanager.h"
AppIconProvider::AppIconProvider(AppInfoManager *manager) :
QQuickImageProvider(QQuickImageProvider::Image),
m_manager(manager)
{
}
QImage AppIconProvider::requestImage(const QString &id, QSize *size,
const QSize &requestedSize)
{
Q_UNUSED(size)
Q_UNUSED(requestedSize)
QImage icon;
bool ok;
const qint64 iconId = id.toLongLong(&ok, 16);
if (ok && iconId != 0) {
icon = m_manager->loadIconFromDb(iconId);
}
return icon.isNull() ? QImage(":/images/application.png")
: icon;
}
QString AppIconProvider::id()
{
return QLatin1String("app-icon");
}
QString AppIconProvider::iconPath(qint64 iconId)
{
return QLatin1String("image://") + id()
+ QString("/%1").arg(iconId, 0, 16);
}

View File

@ -0,0 +1,23 @@
#ifndef APPICONPROVIDER_H
#define APPICONPROVIDER_H
#include <QQuickImageProvider>
QT_FORWARD_DECLARE_CLASS(AppInfoManager)
class AppIconProvider: public QQuickImageProvider
{
public:
explicit AppIconProvider(AppInfoManager *manager);
QImage requestImage(const QString &id, QSize *size,
const QSize &requestedSize) override;
static QString id();
static QString iconPath(qint64 iconId);
private:
AppInfoManager *m_manager;
};
#endif // APPICONPROVIDER_H

View File

@ -1 +1,8 @@
#include "appinfo.h"
#include "appiconprovider.h"
QString AppInfo::iconPath() const
{
return AppIconProvider::iconPath(iconId);
}

View File

@ -1,24 +1,27 @@
#ifndef APPINFO_H
#define APPINFO_H
#include <QPixmap>
#include <QObject>
class AppInfo
{
Q_GADGET
Q_PROPERTY(QString iconPath READ iconPath CONSTANT)
Q_PROPERTY(QString fileDescription MEMBER fileDescription CONSTANT)
Q_PROPERTY(QString companyName MEMBER companyName CONSTANT)
Q_PROPERTY(QString productName MEMBER productName CONSTANT)
Q_PROPERTY(QString productVersion MEMBER productVersion CONSTANT)
Q_PROPERTY(QPixmap icon MEMBER icon CONSTANT)
public:
QString iconPath() const;
public:
qint64 iconId = 0;
QString fileDescription;
QString companyName;
QString productName;
QString productVersion;
QPixmap icon;
};
Q_DECLARE_METATYPE(AppInfo)

View File

@ -17,7 +17,7 @@ AppInfoCache::AppInfoCache(QObject *parent) :
this, &AppInfoCache::cacheChanged);
}
AppInfo *AppInfoCache::appInfo(const QString &appPath)
AppInfo AppInfoCache::appInfo(const QString &appPath)
{
AppInfo *appInfo = m_cache.object(appPath);
@ -25,10 +25,10 @@ AppInfo *AppInfoCache::appInfo(const QString &appPath)
appInfo = new AppInfo();
m_cache.insert(appPath, appInfo, 1);
m_manager->lookupApp(appPath);
m_manager->lookupAppInfo(appPath);
}
return appInfo;
return *appInfo;
}
void AppInfoCache::handleFinishedLookup(const QString &appPath,

View File

@ -7,6 +7,7 @@
#include "appinfo.h"
QT_FORWARD_DECLARE_CLASS(AppIconProvider)
QT_FORWARD_DECLARE_CLASS(AppInfoManager)
class AppInfoCache : public QObject
@ -19,11 +20,13 @@ public:
bool infoTrigger() const { return true; }
AppInfoManager *manager() const { return m_manager; }
signals:
void cacheChanged();
public slots:
AppInfo *appInfo(const QString &appPath);
AppInfo appInfo(const QString &appPath);
private slots:
void handleFinishedLookup(const QString &appPath,

View File

@ -0,0 +1,6 @@
#include "appinfojob.h"
AppInfoJob::AppInfoJob(const QString &appPath) :
WorkerJob(appPath)
{
}

View File

@ -0,0 +1,18 @@
#ifndef APPINFOJOB_H
#define APPINFOJOB_H
#include "../worker/workerjob.h"
#include "appinfo.h"
class AppInfoJob : public WorkerJob
{
public:
explicit AppInfoJob(const QString &appPath);
QString appPath() const { return text; }
public:
AppInfo appInfo;
};
#endif // APPINFOJOB_H

View File

@ -1,13 +1,116 @@
#include "appinfomanager.h"
#include <QImage>
#include <QLoggingCategory>
#include "../fileutil.h"
#include "appinfojob.h"
#include "appinfoworker.h"
#include "apputil.h"
#include <sqlite/sqlitedb.h>
#include <sqlite/sqlitestmt.h>
Q_DECLARE_LOGGING_CATEGORY(CLOG_APPINFOCACHE)
Q_LOGGING_CATEGORY(CLOG_APPINFOCACHE, "fort.appInfoWorker")
#define DATABASE_USER_VERSION 1
#define APP_CACHE_MAX_COUNT 2000
namespace {
const char * const sqlSelectAppInfo =
"SELECT file_descr, company_name,"
" product_name, product_ver, icon_id"
" FROM app WHERE path = ?1;"
;
const char * const sqlUpdateAppAccessTime =
"UPDATE app"
" SET access_time = datetime('now')"
" WHERE path = ?1;"
;
const char * const sqlSelectIconImage =
"SELECT image FROM icon WHERE icon_id = ?1;"
;
const char * const sqlSelectIconIdByHash =
"SELECT icon_id FROM icon WHERE hash = ?1;"
;
const char * const sqlInsertIcon =
"INSERT INTO icon(ref_count, hash, image)"
" VALUES(1, ?1, ?2);"
;
const char * const sqlUpdateIconRefCount =
"UPDATE icon"
" SET ref_count = ref_count + ?2"
" WHERE icon_id = ?1;"
;
const char * const sqlInsertAppInfo =
"INSERT INTO app(path, file_descr, company_name,"
" product_name, product_ver, icon_id, access_time)"
" VALUES(?1, ?2, ?3, ?4, ?5, ?6, datetime('now'));"
;
const char * const sqlSelectAppCount =
"SELECT count(*) FROM app;"
;
const char * const sqlSelectAppOlds =
"SELECT path, icon_id"
" FROM app"
" ORDER BY access_time DESC"
" LIMIT ?1;"
;
const char * const sqlDeleteIconIfNotUsed =
"DELETE FROM icon"
" WHERE icon_id = ?1 AND ref_count = 0;"
;
const char * const sqlDeleteApp =
"DELETE FROM app WHERE path = ?1;"
;
}
AppInfoManager::AppInfoManager(QObject *parent) :
WorkerManager(parent)
WorkerManager(parent),
m_sqliteDb(new SqliteDb())
{
setMaxWorkersCount(1);
setupDb();
}
AppInfoManager::~AppInfoManager()
{
delete m_sqliteDb;
}
void AppInfoManager::setupDb()
{
const QString cachePath = FileUtil::appCacheLocation();
FileUtil::makePath(cachePath);
const QString filePath = cachePath + "/appinfocache.db";
if (!m_sqliteDb->open(filePath)) {
qCritical(CLOG_APPINFOCACHE()) << "File open error:"
<< filePath
<< m_sqliteDb->errorMessage();
return;
}
if (!m_sqliteDb->migrate(":/appinfocache/migrations", DATABASE_USER_VERSION)) {
qCritical(CLOG_APPINFOCACHE()) << "Migration error" << filePath;
return;
}
}
WorkerObject *AppInfoManager::createWorker()
@ -15,15 +118,183 @@ WorkerObject *AppInfoManager::createWorker()
return new AppInfoWorker(this);
}
void AppInfoManager::lookupApp(const QString &appPath)
void AppInfoManager::lookupAppInfo(const QString &appPath)
{
enqueueJob(appPath);
enqueueJob(new AppInfoJob(appPath));
}
void AppInfoManager::handleWorkerResult(const QString &appPath,
const QVariant &result)
void AppInfoManager::handleWorkerResult(WorkerJob *workerJob)
{
auto appInfo = result.value<AppInfo>();
if (!aborted()) {
auto job = static_cast<AppInfoJob *>(workerJob);
emit lookupFinished(appPath, appInfo);
emit lookupFinished(job->appPath(), job->appInfo);
}
delete workerJob;
}
bool AppInfoManager::loadInfoFromFs(const QString &appPath, AppInfo &appInfo)
{
return AppUtil::getInfo(appPath, appInfo);
}
QImage AppInfoManager::loadIconFromFs(const QString &appPath)
{
return AppUtil::getIcon(appPath);
}
bool AppInfoManager::loadInfoFromDb(const QString &appPath, AppInfo &appInfo)
{
QMutexLocker locker(&m_mutex);
const QVariantList vars = QVariantList() << appPath;
// Load version info
const int resultCount = 5;
const QVariantList list = m_sqliteDb->executeEx(
sqlSelectAppInfo, vars, resultCount)
.toList();
if (list.size() != resultCount)
return false;
appInfo.fileDescription = list.at(0).toString();
appInfo.companyName = list.at(1).toString();
appInfo.productName = list.at(2).toString();
appInfo.productVersion = list.at(3).toString();
appInfo.iconId = list.at(4).toLongLong();
// Update last access time
m_sqliteDb->executeEx(sqlUpdateAppAccessTime, vars);
return true;
}
QImage AppInfoManager::loadIconFromDb(qint64 iconId)
{
QMutexLocker locker(&m_mutex);
const QVariant icon = m_sqliteDb->executeEx(
sqlSelectIconImage, QVariantList() << iconId);
return icon.value<QImage>();
}
bool AppInfoManager::saveToDb(const QString &appPath, AppInfo &appInfo,
const QImage &appIcon)
{
QMutexLocker locker(&m_mutex);
bool ok = true;
m_sqliteDb->beginTransaction();
// Save icon image
QVariant iconId;
{
const uint iconHash = qHashBits(appIcon.constBits(),
size_t(appIcon.sizeInBytes()));
iconId = m_sqliteDb->executeEx(sqlSelectIconIdByHash,
QVariantList() << iconHash);
if (iconId.isNull()) {
m_sqliteDb->executeEx(sqlInsertIcon,
QVariantList() << iconHash << appIcon,
0, &ok);
if (ok) {
iconId = m_sqliteDb->lastInsertRowid();
}
} else {
m_sqliteDb->executeEx(sqlUpdateIconRefCount,
QVariantList() << iconId << +1,
0, &ok);
}
}
// Save version info
if (ok) {
const QVariantList vars = QVariantList()
<< appPath
<< appInfo.fileDescription
<< appInfo.companyName
<< appInfo.productName
<< appInfo.productVersion
<< iconId
;
m_sqliteDb->executeEx(sqlInsertAppInfo, vars, 0, &ok);
}
m_sqliteDb->endTransaction(ok);
if (ok) {
appInfo.iconId = iconId.toLongLong();
// Delete excess info
const int appCount = m_sqliteDb->executeEx(sqlSelectAppCount).toInt();
const int excessCount = appCount - APP_CACHE_MAX_COUNT;
if (excessCount > 0) {
shrinkDb(excessCount);
}
}
return ok;
}
void AppInfoManager::shrinkDb(int excessCount)
{
QStringList appPaths;
QHash<qint64, int> iconIds;
bool ok = false;
m_sqliteDb->beginTransaction();
// Get old app info list
{
SqliteStmt stmt;
if (stmt.prepare(m_sqliteDb->db(), sqlSelectAppOlds,
SqliteStmt::PreparePersistent)
&& stmt.bindInt(1, excessCount)) {
while (stmt.step() == SqliteStmt::StepRow) {
const QString appPath = stmt.columnText(0);
appPaths.append(appPath);
const qint64 iconId = stmt.columnInt64(1);
const int iconCount = iconIds.value(iconId);
iconIds.insert(iconId, iconCount + 1);
}
ok = true;
}
}
// Delete old icons
auto iconIt = iconIds.constBegin();
while (iconIt != iconIds.constEnd()) {
const qint64 iconId = iconIt.key();
const int count = iconIt.value();
m_sqliteDb->executeEx(sqlUpdateIconRefCount,
QVariantList() << iconId << -count,
0, &ok);
if (!ok) goto end;
m_sqliteDb->executeEx(sqlDeleteIconIfNotUsed,
QVariantList() << iconId,
0, &ok);
if (!ok) goto end;
}
// Delete old app infos
for (const QString &path : appPaths) {
m_sqliteDb->executeEx(sqlDeleteApp, QVariantList() << path,
0, &ok);
if (!ok) goto end;
}
end:
m_sqliteDb->endTransaction(ok);
}

View File

@ -1,27 +1,49 @@
#ifndef APPINFOMANAGER_H
#define APPINFOMANAGER_H
#include <QMutex>
#include "../worker/workermanager.h"
#include "appinfo.h"
QT_FORWARD_DECLARE_CLASS(SqliteDb)
class AppInfoManager : public WorkerManager
{
Q_OBJECT
public:
explicit AppInfoManager(QObject *parent = nullptr);
~AppInfoManager() override;
bool loadInfoFromFs(const QString &appPath, AppInfo &appInfo);
QImage loadIconFromFs(const QString &appPath);
bool loadInfoFromDb(const QString &appPath, AppInfo &appInfo);
QImage loadIconFromDb(qint64 iconId);
bool saveToDb(const QString &appPath, AppInfo &appInfo,
const QImage &appIcon);
signals:
void lookupFinished(const QString &appPath, const AppInfo appInfo);
public slots:
void lookupApp(const QString &appPath);
void lookupAppInfo(const QString &appPath);
void handleWorkerResult(const QString &appPath,
const QVariant &result) override;
void handleWorkerResult(WorkerJob *workerJob) override;
protected:
WorkerObject *createWorker() override;
private:
void setupDb();
void shrinkDb(int excessCount);
private:
SqliteDb *m_sqliteDb;
QMutex m_mutex;
};
#endif // APPINFOMANAGER_H

View File

@ -1,277 +1,32 @@
#include "appinfoworker.h"
#include <QImage>
#include <QLoggingCategory>
#include "../fileutil.h"
#include "appinfo.h"
#include "appinfomanager.h"
#include "apputil.h"
#include <sqlite/sqlitedb.h>
#include <sqlite/sqlitestmt.h>
Q_DECLARE_LOGGING_CATEGORY(CLOG_APPINFOCACHE)
Q_LOGGING_CATEGORY(CLOG_APPINFOCACHE, "fort.appInfoWorker")
#define DATABASE_USER_VERSION 1
namespace {
const char * const sqlSelectAppInfo =
"SELECT file_descr, company_name,"
" product_name, product_ver, icon_id"
" FROM app WHERE path = ?1;"
;
const char * const sqlUpdateAppAccessTime =
"UPDATE app"
" SET access_time = datetime('now')"
" WHERE path = ?1;"
;
const char * const sqlSelectIconImage =
"SELECT image FROM icon WHERE icon_id = ?1;"
;
const char * const sqlSelectIconIdByHash =
"SELECT icon_id FROM icon WHERE hash = ?1;"
;
const char * const sqlInsertIcon =
"INSERT INTO icon(hash, image)"
" VALUES(?1, ?2);"
;
const char * const sqlUpdateIconRefCount =
"UPDATE icon"
" SET ref_count = ref_count + ?2"
" WHERE icon_id = ?1;"
;
const char * const sqlInsertAppInfo =
"INSERT INTO app(path, file_descr, company_name,"
" product_name, product_ver, icon_id)"
" VALUES(?1, ?2, ?3, ?4, ?5, ?6);"
;
const char * const sqlSelectAppCount =
"SELECT count(*) FROM app;"
;
const char * const sqlSelectAppOlds =
"SELECT path, icon_id"
" FROM app"
" ORDER BY access_time DESC"
" LIMIT ?1;"
;
const char * const sqlDeleteIconIfNotUsed =
"DELETE FROM icon"
" WHERE icon_id = ?1 AND ref_count = 0;"
;
const char * const sqlDeleteApp =
"DELETE FROM app WHERE path = ?1;"
;
}
#include "appinfojob.h"
AppInfoWorker::AppInfoWorker(AppInfoManager *manager) :
WorkerObject(manager),
m_sqliteDb(new SqliteDb())
WorkerObject(manager)
{
setupDb();
}
AppInfoWorker::~AppInfoWorker()
AppInfoManager *AppInfoWorker::manager() const
{
delete m_sqliteDb;
return static_cast<AppInfoManager *>(WorkerObject::manager());
}
void AppInfoWorker::setupDb()
void AppInfoWorker::doJob(WorkerJob *workerJob)
{
const QString cachePath = FileUtil::appCacheLocation() + "/cache";
auto job = static_cast<AppInfoJob *>(workerJob);
const QString &appPath = job->appPath();
FileUtil::makePath(cachePath);
if (!manager()->loadInfoFromDb(appPath, job->appInfo)
&& manager()->loadInfoFromFs(appPath, job->appInfo)) {
const QImage appIcon = manager()->loadIconFromFs(appPath);
const QString filePath = cachePath + "/appinfocache.db";
if (!m_sqliteDb->open(filePath)) {
qCritical(CLOG_APPINFOCACHE()) << "File open error:"
<< filePath
<< m_sqliteDb->errorMessage();
return;
manager()->saveToDb(appPath, job->appInfo, appIcon);
}
if (!m_sqliteDb->migrate(":/appinfocache/migrations", DATABASE_USER_VERSION)) {
qCritical(CLOG_APPINFOCACHE()) << "Migration error" << filePath;
return;
}
}
void AppInfoWorker::doJob(const QString &appPath)
{
AppInfo appInfo;
if (!loadFromDb(appPath, appInfo)
&& loadFromFs(appPath, appInfo)) {
saveToDb(appPath, appInfo);
}
if (aborted()) return;
manager()->handleWorkerResult(appPath, QVariant::fromValue(appInfo));
}
bool AppInfoWorker::loadFromFs(const QString &appPath, AppInfo &appInfo)
{
return AppUtil::getInfo(appPath, appInfo);
}
bool AppInfoWorker::loadFromDb(const QString &appPath, AppInfo &appInfo)
{
const QVariantList vars = QVariantList() << appPath;
// Load version info
const int resultCount = 5;
const QVariantList list = m_sqliteDb->executeEx(
sqlSelectAppInfo, vars, resultCount)
.toList();
if (list.size() != resultCount)
return false;
appInfo.fileDescription = list.at(0).toString();
appInfo.companyName = list.at(1).toString();
appInfo.productName = list.at(2).toString();
appInfo.productVersion = list.at(3).toString();
// Update last access time
m_sqliteDb->executeEx(sqlUpdateAppAccessTime, vars);
// Load icon image
const QVariant iconId = list.at(4);
const QVariant icon = m_sqliteDb->executeEx(
sqlSelectIconImage, QVariantList() << iconId);
appInfo.icon = icon.value<QPixmap>();
return true;
}
bool AppInfoWorker::saveToDb(const QString &appPath, const AppInfo &appInfo)
{
bool ok = true;
m_sqliteDb->beginTransaction();
// Save icon image
QVariant iconId;
{
const QPixmap pixmap = appInfo.icon;
const QImage image = pixmap.toImage();
const uint iconHash = qHashBits(image.constBits(),
size_t(image.sizeInBytes()));
iconId = m_sqliteDb->executeEx(sqlSelectIconIdByHash,
QVariantList() << iconHash);
if (iconId.isNull()) {
m_sqliteDb->executeEx(sqlInsertIcon,
QVariantList() << iconHash << pixmap,
0, &ok);
if (ok) {
iconId = m_sqliteDb->lastInsertRowid();
}
} else {
m_sqliteDb->executeEx(sqlUpdateIconRefCount,
QVariantList() << iconId << +1,
0, &ok);
}
}
// Save version info
if (ok) {
const QVariantList vars = QVariantList()
<< appPath
<< appInfo.fileDescription
<< appInfo.companyName
<< appInfo.productName
<< appInfo.productVersion
<< iconId
;
m_sqliteDb->executeEx(sqlInsertAppInfo, vars, 0, &ok);
}
m_sqliteDb->endTransaction(ok);
// Delete excess info
if (ok) {
const int appMaxCount = 2000;
const int appCount = m_sqliteDb->executeEx(sqlSelectAppCount).toInt();
const int excessCount = appCount - appMaxCount;
if (excessCount > 0) {
shrinkDb(excessCount);
}
}
return ok;
}
void AppInfoWorker::shrinkDb(int excessCount)
{
QStringList appPaths;
QHash<qint64, int> iconIds;
bool ok = false;
m_sqliteDb->beginTransaction();
// Get old app info list
{
SqliteStmt stmt;
if (stmt.prepare(m_sqliteDb->db(), sqlSelectAppOlds,
SqliteStmt::PreparePersistent)
&& stmt.bindInt(1, excessCount)) {
while (stmt.step() == SqliteStmt::StepRow) {
const QString appPath = stmt.columnText(0);
appPaths.append(appPath);
const qint64 iconId = stmt.columnInt64(1);
const int iconCount = iconIds.value(iconId);
iconIds.insert(iconId, iconCount + 1);
}
ok = true;
}
}
// Delete old icons
auto iconIt = iconIds.constBegin();
while (iconIt != iconIds.constEnd()) {
const qint64 iconId = iconIt.key();
const int count = iconIt.value();
m_sqliteDb->executeEx(sqlUpdateIconRefCount,
QVariantList() << iconId << -count,
0, &ok);
if (!ok) goto end;
m_sqliteDb->executeEx(sqlDeleteIconIfNotUsed,
QVariantList() << iconId,
0, &ok);
if (!ok) goto end;
}
// Delete old app infos
for (const QString &path : appPaths) {
m_sqliteDb->executeEx(sqlDeleteApp, QVariantList() << path,
0, &ok);
if (!ok) goto end;
}
end:
m_sqliteDb->endTransaction(ok);
WorkerObject::doJob(workerJob);
}

View File

@ -5,28 +5,16 @@
QT_FORWARD_DECLARE_CLASS(AppInfo)
QT_FORWARD_DECLARE_CLASS(AppInfoManager)
QT_FORWARD_DECLARE_CLASS(SqliteDb)
class AppInfoWorker : public WorkerObject
{
public:
explicit AppInfoWorker(AppInfoManager *manager);
~AppInfoWorker() override;
AppInfoManager *manager() const;
protected:
void doJob(const QString &appPath) override;
private:
void setupDb();
bool loadFromFs(const QString &appPath, AppInfo &appInfo);
bool loadFromDb(const QString &appPath, AppInfo &appInfo);
bool saveToDb(const QString &appPath, const AppInfo &appInfo);
void shrinkDb(int excessCount);
private:
SqliteDb *m_sqliteDb;
void doJob(WorkerJob *workerJob) override;
};
#endif // APPINFOWORKER_H

View File

@ -1,6 +1,7 @@
#include "apputil.h"
#include <QDir>
#include <QPixmap>
#include <comdef.h>
#include <commctrl.h>
@ -45,7 +46,7 @@ QPixmap extractShellIcon(const QString &appPath)
const HRESULT hr = SHGetFileInfoW(appPathW, 0, &info,
sizeof(SHFILEINFOW), flags);
if (SUCCEEDED(hr)) {
pixmap = pixmapFromImageList(SHIL_JUMBO, info);
pixmap = pixmapFromImageList(SHIL_EXTRALARGE, info);
}
return pixmap;
@ -124,9 +125,11 @@ bool extractVersionInfo(const QString &appPath, AppInfo &appInfo)
bool AppUtil::getInfo(const QString &appPath, AppInfo &appInfo)
{
if (extractVersionInfo(appPath, appInfo)) {
appInfo.icon = extractShellIcon(appPath);
return true;
return extractVersionInfo(appPath, appInfo);
}
return false;
QImage AppUtil::getIcon(const QString &appPath)
{
return extractShellIcon(appPath)
.toImage();
}

View File

@ -2,6 +2,7 @@
#define APPUTIL_H
#include <QObject>
#include <QImage>
QT_FORWARD_DECLARE_CLASS(AppInfo)
@ -9,6 +10,7 @@ class AppUtil
{
public:
static bool getInfo(const QString &appPath, AppInfo &appInfo);
static QImage getIcon(const QString &appPath);
};
#endif // APPUTIL_H

View File

@ -2,21 +2,21 @@ PRAGMA user_version = 1;
PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS app(
CREATE TABLE app(
path TEXT PRIMARY KEY,
file_descr TEXT,
company_name TEXT,
product_name TEXT,
product_ver TEXT,
icon_id INTEGER,
access_time DATETIME DEFAULT datetime('now')
access_time DATETIME
) WITHOUT ROWID;
CREATE INDEX idx_app_access_time ON app(access_time);
CREATE TABLE IF NOT EXISTS icon(
CREATE TABLE icon(
icon_id INTEGER PRIMARY KEY,
ref_count INTEGER NOT NULL DEFAULT 1,
ref_count INTEGER NOT NULL,
hash INTEGER NOT NULL,
image BLOB NOT NULL
);

View File

@ -70,15 +70,16 @@ void Logger::setPath(const QString &path)
bool Logger::openLogFile()
{
const QString filename = QLatin1String(LOGGER_FILE_PREFIX)
const QString fileName = QLatin1String(LOGGER_FILE_PREFIX)
+ DateUtil::now().toString("yyyy-MM-dd_HH-mm-ss_zzz")
+ QLatin1String(LOGGER_FILE_SUFFIX);
m_file.setFileName(m_dir.filePath(filename));
m_file.setFileName(m_dir.filePath(fileName));
if (!m_file.open(QIODevice::WriteOnly | QIODevice::Text
| QIODevice::Truncate)) {
qWarning() << "Cannot open log file: " << m_file.errorString();
qWarning() << "Cannot open log file: " << m_file.fileName()
<< m_file.errorString();
return false;
}

View File

@ -0,0 +1,13 @@
#include "hostinfojob.h"
#include "netutil.h"
HostInfoJob::HostInfoJob(const QString &address) :
WorkerJob(address)
{
}
void HostInfoJob::doJob()
{
hostName = NetUtil::getHostName(address());
}

View File

@ -0,0 +1,19 @@
#ifndef HOSTINFOJOB_H
#define HOSTINFOJOB_H
#include "../worker/workerjob.h"
class HostInfoJob : public WorkerJob
{
public:
explicit HostInfoJob(const QString &address);
QString address() const { return text; }
void doJob() override;
public:
QString hostName;
};
#endif // HOSTINFOJOB_H

View File

@ -1,6 +1,6 @@
#include "hostinfomanager.h"
#include "hostinfoworker.h"
#include "hostinfojob.h"
HostInfoManager::HostInfoManager(QObject *parent) :
WorkerManager(parent)
@ -10,18 +10,18 @@ HostInfoManager::HostInfoManager(QObject *parent) :
QSysInfo::machineHostName(); // Initialize ws2_32.dll
}
WorkerObject *HostInfoManager::createWorker()
{
return new HostInfoWorker(this);
}
void HostInfoManager::lookupHost(const QString &address)
{
enqueueJob(address);
enqueueJob(new HostInfoJob(address));
}
void HostInfoManager::handleWorkerResult(const QString &address,
const QVariant &hostName)
void HostInfoManager::handleWorkerResult(WorkerJob *workerJob)
{
emit lookupFinished(address, hostName.toString());
if (!aborted()) {
auto job = static_cast<HostInfoJob *>(workerJob);
emit lookupFinished(job->address(), job->hostName);
}
delete workerJob;
}

View File

@ -16,11 +16,7 @@ signals:
public slots:
void lookupHost(const QString &address);
void handleWorkerResult(const QString &address,
const QVariant &hostName) override;
protected:
WorkerObject *createWorker() override;
void handleWorkerResult(WorkerJob *workerJob) override;
};
#endif // HOSTINFOMANAGER_H

View File

@ -1,18 +0,0 @@
#include "hostinfoworker.h"
#include "hostinfomanager.h"
#include "netutil.h"
HostInfoWorker::HostInfoWorker(HostInfoManager *manager) :
WorkerObject(manager)
{
}
void HostInfoWorker::doJob(const QString &address)
{
const QString hostName = NetUtil::getHostName(address);
if (aborted()) return;
manager()->handleWorkerResult(address, hostName);
}

View File

@ -1,17 +0,0 @@
#ifndef HOSTINFOWORKER_H
#define HOSTINFOWORKER_H
#include "../worker/workerobject.h"
QT_FORWARD_DECLARE_CLASS(HostInfoManager)
class HostInfoWorker : public WorkerObject
{
public:
explicit HostInfoWorker(HostInfoManager *manager);
protected:
void doJob(const QString &address) override;
};
#endif // HOSTINFOWORKER_H

View File

@ -0,0 +1,6 @@
#include "workerjob.h"
WorkerJob::WorkerJob(const QString &_text) :
text(_text)
{
}

View File

@ -0,0 +1,18 @@
#ifndef WORKERJOB_H
#define WORKERJOB_H
#include <QObject>
class WorkerJob
{
public:
explicit WorkerJob(const QString &_text);
virtual ~WorkerJob() {}
virtual void doJob() {}
public:
QString text;
};
#endif // WORKERJOB_H

View File

@ -26,7 +26,7 @@ void WorkerManager::setupWorker()
if (workersCount != 0
&& (workersCount >= maxWorkersCount()
|| m_queue.isEmpty()))
|| m_jobQueue.isEmpty()))
return;
WorkerObject *worker = createWorker(); // autoDelete = true
@ -46,11 +46,16 @@ void WorkerManager::workerFinished(WorkerObject *worker)
}
}
WorkerObject *WorkerManager::createWorker()
{
return new WorkerObject(this);
}
void WorkerManager::clear()
{
QMutexLocker locker(&m_mutex);
m_queue.clear();
m_jobQueue.clear();
}
void WorkerManager::abort()
@ -59,42 +64,35 @@ void WorkerManager::abort()
m_aborted = true;
if (!m_workers.isEmpty()) {
for (WorkerObject *worker : m_workers) {
worker->abort();
}
m_waitCondition.wakeAll();
do {
while (!m_workers.isEmpty()) {
m_waitCondition.wait(&m_mutex);
} while (!m_workers.isEmpty());
}
}
void WorkerManager::enqueueJob(const QString &job)
void WorkerManager::enqueueJob(WorkerJob *job)
{
QMutexLocker locker(&m_mutex);
setupWorker();
m_queue.enqueue(job);
m_jobQueue.enqueue(job);
m_waitCondition.wakeOne();
}
bool WorkerManager::dequeueJob(QString &job)
WorkerJob *WorkerManager::dequeueJob()
{
QMutexLocker locker(&m_mutex);
while (!m_aborted && m_queue.isEmpty()) {
while (!m_aborted && m_jobQueue.isEmpty()) {
if (!m_waitCondition.wait(&m_mutex, WORKER_TIMEOUT_MSEC))
break; // timed out
}
if (m_aborted || m_queue.isEmpty())
return false;
if (m_aborted || m_jobQueue.isEmpty())
return nullptr;
job = m_queue.dequeue();
return true;
return m_jobQueue.dequeue();
}

View File

@ -8,6 +8,7 @@
#include <QVariant>
#include <QWaitCondition>
QT_FORWARD_DECLARE_CLASS(WorkerJob)
QT_FORWARD_DECLARE_CLASS(WorkerObject)
class WorkerManager : public QObject
@ -26,22 +27,22 @@ signals:
public slots:
void clear();
void enqueueJob(const QString &job);
bool dequeueJob(QString &job);
void enqueueJob(WorkerJob *job);
WorkerJob *dequeueJob();
void workerFinished(WorkerObject *worker);
virtual void handleWorkerResult(const QString &job,
const QVariant &result) = 0;
virtual void handleWorkerResult(WorkerJob *job) = 0;
protected:
virtual WorkerObject *createWorker() = 0;
virtual WorkerObject *createWorker();
bool aborted() const { return m_aborted; }
void abort();
private:
void setupWorker();
void abort();
private:
volatile bool m_aborted;
@ -49,7 +50,7 @@ private:
QList<WorkerObject *> m_workers;
QQueue<QString> m_queue;
QQueue<WorkerJob *> m_jobQueue;
QMutex m_mutex;
QWaitCondition m_waitCondition;

View File

@ -1,19 +1,18 @@
#include "workerobject.h"
#include "workerjob.h"
#include "workermanager.h"
WorkerObject::WorkerObject(WorkerManager *manager) :
m_aborted(false),
m_manager(manager)
{
}
void WorkerObject::run()
{
while (!aborted()) {
QString job;
if (!manager()->dequeueJob(job))
for (; ; ) {
WorkerJob *job = manager()->dequeueJob();
if (job == nullptr)
break;
doJob(job);
@ -21,3 +20,10 @@ void WorkerObject::run()
manager()->workerFinished(this);
}
void WorkerObject::doJob(WorkerJob *job)
{
job->doJob();
manager()->handleWorkerResult(job);
}

View File

@ -4,6 +4,7 @@
#include <QObject>
#include <QRunnable>
QT_FORWARD_DECLARE_CLASS(WorkerJob)
QT_FORWARD_DECLARE_CLASS(WorkerManager)
class WorkerObject : public QRunnable
@ -13,17 +14,12 @@ public:
WorkerManager *manager() const { return m_manager; }
bool aborted() const { return m_aborted; }
virtual void abort() { m_aborted = true; }
void run() override;
protected:
virtual void doJob(const QString &job) = 0;
virtual void doJob(WorkerJob *job);
private:
volatile bool m_aborted;
WorkerManager *m_manager;
};