/*
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "recent-file-extension.h"
#define KYLIN_APP_MANAGER_NAME "com.kylin.AppManager"
#define KYLIN_APP_MANAGER_PATH "/com/kylin/AppManager"
#define KYLIN_APP_MANAGER_INTERFACE "com.kylin.AppManager"
#define FREEDESKTOP_FILEMANAGER_NAME "org.freedesktop.FileManager1"
#define FREEDESKTOP_FILEMANAGER_PATH "/org/freedesktop/FileManager1"
#define FREEDESKTOP_FILEMANAGER_INTERFACE "org.freedesktop.FileManager1"
namespace UkuiMenu {
// GVFS 最近文件获取工具
class GVFSRecentFileData
{
public:
static int s_queryFileNum;
static GFile *s_recentFileRootDir;
static GFileMonitor *s_recentFileMonitor;
static void loadRecentFileASync(RecentFileProvider *p_recentFileProvider);
static void fileMonitor(RecentFileProvider *p_recentFileProvider);
static void removeRecentFileByInfoId(const QString &infoId);
private:
static GAsyncReadyCallback enumerateFinish(GFile *file, GAsyncResult *res, RecentFileProvider *p_recentFileProvider);
static GAsyncReadyCallback parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileProvider *p_recentFileProvider);
static void fileChangedCallback(GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
RecentFileProvider *p_recentFileProvider);
};
int GVFSRecentFileData::s_queryFileNum = 100;
GFile *GVFSRecentFileData::s_recentFileRootDir = g_file_new_for_uri("recent:///");
GFileMonitor *GVFSRecentFileData::s_recentFileMonitor = nullptr;
void GVFSRecentFileData::fileMonitor(RecentFileProvider *p_recentFileProvider)
{
GError *error = nullptr;
s_recentFileMonitor = g_file_monitor_directory(GVFSRecentFileData::s_recentFileRootDir,
G_FILE_MONITOR_NONE,
nullptr,
&error);
if (error) {
qWarning() << "recentFile monitor creat error";
g_error_free(error);
return;
}
g_signal_connect(s_recentFileMonitor, "changed", G_CALLBACK(fileChangedCallback), p_recentFileProvider);
}
void GVFSRecentFileData::fileChangedCallback(GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
RecentFileProvider *p_recentFileProvider)
{
Q_UNUSED(monitor);
Q_UNUSED(file);
Q_UNUSED(other_file);
switch (event_type) {
case G_FILE_MONITOR_EVENT_DELETED:
case G_FILE_MONITOR_EVENT_CREATED: {
loadRecentFileASync(p_recentFileProvider);
break;
}
default:
break;
}
}
void GVFSRecentFileData::removeRecentFileByInfoId(const QString &infoId)
{
GFile *file = g_file_new_for_uri(infoId.toUtf8().constData());
GError *err = nullptr;
g_file_delete(file, nullptr, &err);
g_object_unref(file);
if (err) {
qWarning() << "Recentfile Delete Error";
}
}
void GVFSRecentFileData::loadRecentFileASync(RecentFileProvider *p_recentFileProvider)
{
if (!s_recentFileRootDir) {
qWarning() << "Can not find 'recent:///' dir.";
return;
}
g_file_enumerate_children_async(s_recentFileRootDir,
"*",
G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
nullptr, GAsyncReadyCallback(enumerateFinish),
p_recentFileProvider);
}
GAsyncReadyCallback
GVFSRecentFileData::enumerateFinish(GFile *file, GAsyncResult *res, RecentFileProvider *p_recentFileProvider)
{
GError *error = nullptr;
GFileEnumerator *enumerator = g_file_enumerate_children_finish(file, res, &error);
if (error) {
qWarning() << "GVFSRecentFileData::enumerateFinish Error:" << error->message;
g_error_free(error);
return nullptr;
}
g_file_enumerator_next_files_async(enumerator, s_queryFileNum, G_PRIORITY_DEFAULT,
nullptr, GAsyncReadyCallback(parseRecentFiles), p_recentFileProvider);
g_object_unref(enumerator);
return nullptr;
}
GAsyncReadyCallback
GVFSRecentFileData::parseRecentFiles(GFileEnumerator *enumerator, GAsyncResult *res, RecentFileProvider *p_recentFileProvider)
{
GError *error = nullptr;
GList *fileList = g_file_enumerator_next_files_finish(enumerator, res, &error);
if (error) {
qWarning() << "GVFSRecentFileData::parseRecentFiles Error:" << error->message;
g_error_free(error);
return nullptr;
}
QVector recentFiles;
if (!fileList) {
p_recentFileProvider->dataProcess(recentFiles);
return nullptr;
}
auto listIterator = fileList;
while (listIterator) {
RecentFile recentFile;
GFileInfo *info = static_cast(listIterator->data);
GFile *file = g_file_enumerator_get_child(enumerator, info);
recentFile.infoId = g_file_get_uri(file);
g_object_unref(file);
char *attribute = g_file_info_get_attribute_as_string(info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
if (attribute) {
recentFile.uri = attribute;
g_free(attribute);
}
const char *fileName = g_file_info_get_display_name(info);
if (fileName) {
recentFile.name = fileName;
}
// in seconds since the UNIX epoch.
recentFile.accessTime = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_ACCESS);
GIcon *icon = g_file_info_get_icon(info);
if (icon) {
const gchar* const *iconNames = g_themed_icon_get_names(G_THEMED_ICON(icon));
if (iconNames) {
auto iconNameIterator = iconNames;
while(*iconNameIterator) {
if(QIcon::hasThemeIcon(*iconNameIterator)) {
recentFile.icon = "image://appicon/" + QString(*iconNameIterator);
break;
} else {
++iconNameIterator;
}
}
}
g_object_unref(icon);
}
if (recentFile.icon.isEmpty()) {
recentFile.icon = "image://appicon/text-plain";
}
recentFiles.append(recentFile);
info = nullptr;
listIterator = listIterator->next;
}
g_list_free(fileList);
p_recentFileProvider->dataProcess(recentFiles);
return nullptr;
}
// RecentFileExtension
RecentFileExtension::RecentFileExtension(QObject *parent) : MenuExtensionIFace(parent)
{
QString translationFile(QString(RECENT_FILE_TRANSLATION_DIR) + "/recent-file-extension_" + QLocale::system().name() + ".qm");
if (QFile::exists(translationFile)) {
QTranslator *translator = new QTranslator(this);
translator->load(translationFile);
QCoreApplication::installTranslator(translator);
}
qRegisterMetaType("RecentFilesModel*");
qRegisterMetaType >("QVector");
m_recentFilesProviderThread = new QThread(this);
m_recentFilesModel = new RecentFilesModel(this);
m_recentFileProvider = new RecentFileProvider();
if ((m_recentFilesProviderThread == nullptr) || (m_recentFilesModel == nullptr) || (m_recentFileProvider == nullptr)) {
qWarning() << "recentfile construction error" ;
return;
}
m_recentFilesProviderThread->start();
m_recentFileProvider->moveToThread(m_recentFilesProviderThread);
connect(this, &RecentFileExtension::loadRecentFiles, m_recentFileProvider, &RecentFileProvider::getRecentData);
connect(m_recentFilesModel, &RecentFilesModel::updateRecentData, m_recentFileProvider, &RecentFileProvider::getRecentData);
connect(m_recentFileProvider, &RecentFileProvider::dataLoadCompleted, m_recentFilesModel, &RecentFilesModel::updateRecentFiles);
connect(this, &RecentFileExtension::openFileASync, m_recentFileProvider, &RecentFileProvider::openFileByGFile);
m_data.insert("recentFilesModel", QVariant::fromValue(m_recentFilesModel));
initFileDbus();
GVFSRecentFileData::fileMonitor(m_recentFileProvider);
Q_EMIT loadRecentFiles();
}
RecentFileExtension::~RecentFileExtension()
{
if (m_recentFilesProviderThread) {
m_recentFilesProviderThread->quit();
m_recentFilesProviderThread->wait();
}
if (m_recentFileProvider) {
delete m_recentFileProvider;
m_recentFileProvider = nullptr;
}
if (GVFSRecentFileData::s_recentFileRootDir) {
g_object_unref(GVFSRecentFileData::s_recentFileRootDir);
}
if (GVFSRecentFileData::s_recentFileMonitor) {
g_object_unref(GVFSRecentFileData::s_recentFileMonitor);
}
}
int RecentFileExtension::index()
{
return 1;
}
QString RecentFileExtension::name()
{
return tr("Recent Files");
}
QUrl RecentFileExtension::url()
{
return {"qrc:///extensions/RecentFileExtension.qml"};
}
QVariantMap RecentFileExtension::data()
{
return m_data;
}
void RecentFileExtension::receive(QVariantMap data)
{
QString path = data.value("url").toString();
if (data.value("action").toString() == "right") {
int index = data.value("index").toInt();
creatMenu(path, index);
return;
}
if (!openFile(path)) {
Q_EMIT openFileASync(path);
}
}
void RecentFileExtension::initFileDbus()
{
m_appManagerDbusInterface = new QDBusInterface(KYLIN_APP_MANAGER_NAME,
KYLIN_APP_MANAGER_PATH,
KYLIN_APP_MANAGER_INTERFACE,
QDBusConnection::sessionBus());
m_fileManagerDbusInterface = new QDBusInterface(FREEDESKTOP_FILEMANAGER_NAME,
FREEDESKTOP_FILEMANAGER_PATH,
FREEDESKTOP_FILEMANAGER_INTERFACE,
QDBusConnection::sessionBus());
if (!m_appManagerDbusInterface) {
qWarning() << "recentfile open failed: appmanager dbus does not exists.";
}
if (!m_fileManagerDbusInterface) {
qWarning() << "recentfile directory open failed: filemanager dbus does not exists.";
}
}
bool RecentFileExtension::openFile(const QString &desktopFile)
{
if (m_appManagerDbusInterface != nullptr) {
QDBusReply status = m_appManagerDbusInterface->call("LaunchApp", desktopFile);
return status;
} else {
qWarning()<<"LaunchApp is failed,return false";
return false;
}
}
void RecentFileExtension::creatMenu(const QString &path, const int &index)
{
QMenu menu;
QAction open(QIcon::fromTheme("document-open-symbolic"), tr("Open"));
QAction remove(QIcon::fromTheme("edit-clear-symbolic"), tr("Remove from list"));
QAction clear(QIcon::fromTheme("edit-delete-symbolic"), tr("Clear list"));
QAction directory(tr("Open the directory where the file is located"));
connect(&open, &QAction::triggered, this, [this, path]() {
if (!openFile(path)) {
Q_EMIT openFileASync(path);
}
});
connect(&remove, &QAction::triggered, this, [this, index]() {
GVFSRecentFileData::removeRecentFileByInfoId(m_recentFilesModel->getInfoId(index));
});
connect(&clear, &QAction::triggered, this, [this]() {
QStringList infoIdList = m_recentFilesModel->getAllInfoId();
for (const QString &infoId : infoIdList) {
GVFSRecentFileData::removeRecentFileByInfoId(infoId);
}
});
connect(&directory, &QAction::triggered, this, [this, path]() {
if (!m_fileManagerDbusInterface) { return; }
QStringList pathList;
pathList.append(path);
m_fileManagerDbusInterface->call("ShowFolders", pathList, "arg");
});
menu.addAction(&open);
menu.addSeparator();
menu.addAction(&remove);
menu.addAction(&clear);
menu.addSeparator();
menu.addAction(&directory);
menu.exec(QCursor::pos());
}
RecentFilesModel::RecentFilesModel(QObject *parent) : QAbstractListModel(parent)
{
}
int RecentFilesModel::rowCount(const QModelIndex &parent) const
{
return m_recentFileData.count();
}
QVariant RecentFilesModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
if (row < 0 || row >= m_recentFileData.count()) {
return {};
}
switch (role) {
case UriRole:
return m_recentFileData.at(row).uri;
case NameRole:
return m_recentFileData.at(row).name;
case IconRole:
return m_recentFileData.at(row).icon;
default:
break;
}
return {};
}
QHash RecentFilesModel::roleNames() const
{
QHash names;
names.insert(UriRole, "uri");
names.insert(NameRole, "name");
names.insert(IconRole, "icon");
return names;
}
QString RecentFilesModel::getInfoId(const int &index)
{
return m_recentFileData.at(index).infoId;
}
QStringList RecentFilesModel::getAllInfoId()
{
QStringList infoIdList;
for (const RecentFile &data : m_recentFileData) {
infoIdList.append(data.infoId);
}
return infoIdList;
}
void RecentFilesModel::updateData()
{
Q_EMIT updateRecentData();
}
void RecentFilesModel::updateRecentFiles(QVector recentFiles)
{
beginResetModel();
m_recentFileData.swap(recentFiles);
endResetModel();
}
RecentFileProvider::RecentFileProvider(QObject *parent) : QObject(parent)
{
}
void RecentFileProvider::dataProcess(QVector &recentFiles)
{
std::sort(recentFiles.begin(), recentFiles.end(), [](const RecentFile &a, const RecentFile &b) {
return a.accessTime > b.accessTime;
});
Q_EMIT dataLoadCompleted(recentFiles);
}
void RecentFileProvider::getRecentData()
{
GVFSRecentFileData::loadRecentFileASync(this);
}
void RecentFileProvider::openFileByGFile(const QString &path)
{
GFile *file = g_file_new_for_uri(path.toUtf8().constData());
if (!file) {
return;
}
GFileInfo *fileInfo = g_file_query_info(file,
"standard::*," "time::*," "access::*," "mountable::*," "metadata::*," "trash::*," G_FILE_ATTRIBUTE_ID_FILE,
G_FILE_QUERY_INFO_NONE,
nullptr,
nullptr);
if (!fileInfo) {
return;
}
QString mimeType(g_file_info_get_content_type(fileInfo));
if (mimeType.isEmpty()) {
if (g_file_info_has_attribute(fileInfo, "standard::fast-content-type")) {
mimeType = g_file_info_get_attribute_string(fileInfo, "standard::fast-content-type");
}
}
GError *error = NULL;
GAppInfo *info = NULL;
QString mimeAppsListPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.config/mimeapps.list";
GKeyFile *keyfile = g_key_file_new();
gboolean ret = g_key_file_load_from_file(keyfile, mimeAppsListPath.toUtf8(), G_KEY_FILE_NONE, &error);
if (!ret) {
qWarning()<< "load mimeapps list error msg" << error->message;
info = g_app_info_get_default_for_type(mimeType.toUtf8().constData(), false);
g_error_free(error);
}
else {
gchar *desktopApp = g_key_file_get_string(keyfile, "Default Applications", mimeType.toUtf8(), &error);
if (NULL != desktopApp) {
info = (GAppInfo*)g_desktop_app_info_new(desktopApp);
g_free (desktopApp);
}
else {
info = g_app_info_get_default_for_type(mimeType.toUtf8().constData(), false);
}
}
g_key_file_free (keyfile);
if(G_IS_APP_INFO(info)) {
bool isSuccess(false);
QDBusInterface * appLaunchInterface = new QDBusInterface(KYLIN_APP_MANAGER_NAME,
KYLIN_APP_MANAGER_PATH,
KYLIN_APP_MANAGER_INTERFACE,
QDBusConnection::sessionBus());
if(!appLaunchInterface->isValid()) {
qWarning() << qPrintable(QDBusConnection::sessionBus().lastError().message());
isSuccess = false;
}
else {
appLaunchInterface->setTimeout(10000);
QDBusReply reply = appLaunchInterface->call("LaunchDefaultAppWithUrl", path);
if(reply.isValid()) {
isSuccess = reply;
}
else {
qWarning() << "recentfile used appmanager dbus called failed!";
isSuccess = false;
}
}
if(appLaunchInterface) {
delete appLaunchInterface;
}
appLaunchInterface = NULL;
if (!isSuccess){
QDesktopServices::openUrl(path);
}
}
g_object_unref(info);
}
} // UkuiMenu