peony/libpeony-qt/thumbnail-manager.cpp

489 lines
14 KiB
C++

/*
* Peony-Qt's Library
*
* Copyright (C) 2019-2020, Tianjin KYLIN Information Technology Co., Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library. If not, see <https://www.gnu.org/licenses/>.
*
* Authors: Yue Lan <lanyue@kylinos.cn>
* Burgess Chang <brs@sdf.org>
*
*/
#include "thumbnail-manager.h"
#include "file-info-manager.h"
#include "file-watcher.h"
#include "file-utils.h"
#include "thumbnail/pdf-thumbnail.h"
#include "thumbnail/video-thumbnail.h"
#include "thumbnail/office-thumbnail.h"
#include "thumbnail/image-pdf-thumbnail.h"
#include "generic-thumbnailer.h"
#include "thumbnail-job.h"
#include "global-settings.h"
#include <QtConcurrent>
#include <QIcon>
#include <QUrl>
#include <QThreadPool>
#include <QSemaphore>
#include <QGuiApplication>
#include <gio/gdesktopappinfo.h>
using namespace Peony;
static ThumbnailManager *global_instance = nullptr;
static bool m_tril_exist = false;
/*!
* \brief ThumbnailManager::ThumbnailManager
* \param parent
* \bug
* thumbnail will do i/o on the file. if we write on a pictrue and save
* save it, the image editor might report a modified error due to we hold
* the file in peony-qt.
*
* this bug is not critical, but i have to consider current thumbnailer
* might be a bad desgin.
*/
ThumbnailManager::ThumbnailManager(QObject *parent) : QObject(parent)
{
GlobalSettings::getInstance();
m_thumbnail_thread_pool = new QThreadPool(this);
m_thumbnail_thread_pool->setMaxThreadCount(1);
m_semaphore = new QSemaphore(1);
findAtril();
connect(qApp, &QGuiApplication::lastWindowClosed, this, [=]{
m_thumbnail_thread_pool->clear();
m_thumbnail_thread_pool->waitForDone(500);
});
m_thumbnail = new QGSettings("org.ukui.peony.settings", QByteArray(), this);
connect(m_thumbnail, &QGSettings::changed, this, [=](const QString &key) {
if (FORBID_THUMBNAIL_IN_VIEW == key) {
auto settings = Peony::GlobalSettings::getInstance();
if (m_do_not_thumbnail != settings->getValue(FORBID_THUMBNAIL_IN_VIEW).toBool()) {
m_do_not_thumbnail = settings->getValue(FORBID_THUMBNAIL_IN_VIEW).toBool();
if (true == m_do_not_thumbnail) {
Peony::ThumbnailManager::getInstance()->clearThumbnail();
}
Q_EMIT updateFileThumbnail();
}
}
});
connect(this, &ThumbnailManager::updateFileThemedIconFromThread, this, [=](const QString &uri, const QString &themedIcon){
auto icon = QIcon::fromTheme(themedIcon);
if (icon.isNull()) {
return false;
}
this->insertOrUpdateThumbnail(uri, icon);
return true;
}, Qt::BlockingQueuedConnection);
}
ThumbnailManager::~ThumbnailManager()
{
delete m_semaphore;
}
ThumbnailManager *ThumbnailManager::getInstance()
{
if (!global_instance)
global_instance = new ThumbnailManager;
return global_instance;
}
void ThumbnailManager::syncThumbnailPreferences()
{
GlobalSettings::getInstance()->forceSync(FORBID_THUMBNAIL_IN_VIEW);
}
void ThumbnailManager::insertOrUpdateThumbnail(const QString &uri, const QIcon &icon)
{
m_semaphore->acquire();
m_hash.remove(uri);
m_hash.insert(uri, icon);
m_semaphore->release();
}
void ThumbnailManager::setForbidThumbnailInView(bool forbid)
{
GlobalSettings::getInstance()->setValue(FORBID_THUMBNAIL_IN_VIEW, forbid);
}
void ThumbnailManager::createVideFileThumbnail(const QString &uri, std::shared_ptr<FileWatcher> watcher)
{
QIcon thumbnail;
VideoThumbnail videoThumbnail(uri);
thumbnail = videoThumbnail.generateThumbnail();
if (!thumbnail.isNull()) {
insertOrUpdateThumbnail(uri, thumbnail);
if (watcher) {
watcher->fileChanged(uri);
}
}
return;
}
void ThumbnailManager::createImagePdfFileThumbnail(const QString &uri, std::shared_ptr<FileWatcher> watcher)
{
QIcon thumbnail;
ImagePdfThumbnail officeThumbnail(uri);
ThumbnailManager::getInstance()->updateFileThemedIconFromThread(uri, "atril");
if (watcher) {
watcher->fileChanged(uri);
}
// thumbnail = officeThumbnail.generateThumbnail();;
// if (!thumbnail.isNull()) {
// insertOrUpdateThumbnail(uri, thumbnail);
// if (watcher) {
// watcher->fileChanged(uri);
// }
// }
return;
}
void ThumbnailManager::createPdfFileThumbnail(const QString &uri, std::shared_ptr<FileWatcher> watcher)
{
QIcon thumbnail;
QUrl url = uri;
if (!uri.startsWith("file:///")) {
url = FileUtils::getTargetUri(uri);
//qDebug()<<url;
}
PdfThumbnail pdfThumbnail(url.path());
QPixmap pix = pdfThumbnail.generateThumbnail();
thumbnail = GenericThumbnailer::generateThumbnail(pix, true);
if (!thumbnail.isNull()) {
insertOrUpdateThumbnail(uri, thumbnail);
if (watcher) {
watcher->fileChanged(uri);
}
}
return;
}
void ThumbnailManager::createImageFileThumbnail(const QString &uri, std::shared_ptr<FileWatcher> watcher)
{
QUrl url = uri;
if (!uri.startsWith("file:///")) {
url = FileUtils::getTargetUri(uri);
//qDebug()<<url;
}
QIcon thumbnail = GenericThumbnailer::generateThumbnail(url.path(), true);
if (!thumbnail.isNull()) {
insertOrUpdateThumbnail(uri, thumbnail);
if (watcher) {
watcher->fileChanged(uri);
}
}
//qApp->processEvents();
return;
}
void ThumbnailManager::createOfficeFileThumbnail(const QString &uri, std::shared_ptr<FileWatcher> watcher)
{
QIcon thumbnail;
OfficeThumbnail officeThumbnail(uri);
thumbnail = officeThumbnail.generateThumbnail();;
if (!thumbnail.isNull()) {
insertOrUpdateThumbnail(uri, thumbnail);
if (watcher) {
watcher->fileChanged(uri);
}
}
return;
}
void ThumbnailManager::createDesktopFileThumbnail(const QString &uri, std::shared_ptr<FileWatcher> watcher)
{
QIcon thumbnail;
QUrl url = uri;
QString path = url.path();
if (!uri.startsWith("file:///")) {
g_autoptr (GFile) gfile = g_file_new_for_uri(uri.toUtf8().constData());
g_autoptr (GFileInfo) gfileinfo = g_file_query_info(gfile, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, G_FILE_QUERY_INFO_NONE, 0, 0);
g_autofree gchar *target_uri = g_file_info_get_attribute_as_string(gfileinfo, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
if (target_uri) {
url = QString(target_uri);
}
}
QString string;
g_autoptr (GDesktopAppInfo) desktop_app_info = g_desktop_app_info_new_from_filename(path.toUtf8().constData());
if (desktop_app_info) {
auto app_info = G_APP_INFO(desktop_app_info);
GIcon *icon = g_app_info_get_icon(app_info);
string = FileUtils::getIconStringFromGIcon(icon);
}
if (string.isEmpty()) {
string = url.fileName().remove(".desktop");
auto key_file = g_key_file_new();
if (g_key_file_load_from_file(key_file, path.toUtf8().constData(), G_KEY_FILE_NONE, 0)) {
g_autofree gchar* icon_name = g_key_file_get_value(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, 0);
if (icon_name) {
string = icon_name;
}
g_key_file_free(key_file);
} else {
qWarning()<<"failed to load desktop file";
return;
}
}
if (string.startsWith("/")) {
thumbnail = GenericThumbnailer::generateThumbnail(string, true);
insertOrUpdateThumbnail(uri, thumbnail);
if (watcher) {
watcher->fileChanged(uri);
}
return;
} else {
if (string.endsWith(".jpg") || string.endsWith(".jpeg") || string.endsWith(".png") || string.endsWith(".svg")) {
string.chop(string.count() - string.lastIndexOf("."));
}
}
bool successed = ThumbnailManager::getInstance()->updateFileThemedIconFromThread(uri, string);
successed = !thumbnail.isNull() || successed;
//fix desktop file set customer icon issue, link to bug#77638
// auto info = FileInfo::fromUri(uri);
// if (! info->customIcon().isEmpty()){
// thumbnail = GenericThumbnailer::generateThumbnail(info->customIcon(), true);
// }
//add special path search /use/share/pixmaps
if (!successed)
{
QString path = QString("/usr/share/pixmaps/%1.%2").arg(string).arg("png");
QString path_svg = QString("/usr/share/pixmaps/%1.%2").arg(string).arg("svg");
//qDebug() << "createDesktopFileThumbnail path:" <<path;
if(QFile::exists(path)){
thumbnail=QIcon(path);
}
else if(QFile::exists(path_svg)){
thumbnail=QIcon(path_svg);
}
else{
//search /usr/share/icons/hicolor/scalable/apps
//fix installed app desktop icon not loaded in time issue
path_svg = QString("/usr/share/icons/hicolor/scalable/apps/%1.%2").arg(string).arg("svg");
if(QFile::exists(path_svg))
{
thumbnail=QIcon(path_svg);
}
}
//still not find icon, default find 64*64 png
//link to bug#69429, after install app not show icon issue
if (thumbnail.isNull())
{
path = QString("/usr/share/icons/hicolor/64x64/apps/%1.%2").arg(string).arg("png");
if(QFile::exists(path)){
thumbnail=QIcon(path);
}
}
if (thumbnail.isNull()) {
path = QString("/usr/share/kylin-software-center/data/icons/%1.%2").arg(string).arg("png");
if(QFile::exists(path)){
thumbnail=QIcon(path);
}
}
} else {
if (watcher) {
watcher->fileChanged(uri);
}
return;
}
if (!thumbnail.isNull()) {
insertOrUpdateThumbnail(uri, thumbnail);
if (watcher) {
watcher->fileChanged(uri);
}
} else {
qWarning()<<"can not pharse desktop file"<<uri;
}
return;
}
//is system has atril software
void ThumbnailManager::findAtril()
{
QtConcurrent::run([](){
GList *infos = g_app_info_get_all();
GList *l = infos;
while (l && ! m_tril_exist) {
const char *cmd = g_app_info_get_executable(static_cast<GAppInfo*>(l->data));
QString tmp = cmd;
if (tmp.contains("atril")) {
m_tril_exist = true;
}
l = l->next;
}
g_list_free_full(infos, g_object_unref);
});
}
void ThumbnailManager::createThumbnailInternal(const QString &uri, std::shared_ptr<FileWatcher> watcher, bool force)
{
// deprecated
}
void ThumbnailManager::createThumbnail(const QString &uri, std::shared_ptr<FileWatcher> watcher, bool force)
{
//qDebug() <<"createThumbnail:" <<force<<uri;
auto thumbnail = tryGetThumbnail(uri);
if (!thumbnail.isNull()) {
if (!force) {
watcher->thumbnailUpdated(uri);
watcher->fileChanged(uri);
qDebug() <<"createThumbnail return:" <<uri;
return;
}
}
// check if need thumbnail
bool needThumbnail = false;
auto info = FileInfo::fromUri(uri);
bool hasCustomIcon = false;
if (!info->customIcon().isEmpty() /*&& info->customIcon().startsWith("/")*/) {
needThumbnail = true;
hasCustomIcon = true;
}
if (!info->mimeType().isEmpty()) {
if (info->isImageFile()) {
needThumbnail = true;
}
else if (info->mimeType().contains("pdf")) {
needThumbnail = true;
}
else if(info->isVideoFile()) {
needThumbnail = true;
}
else if (info->isOfficeFile()) {
needThumbnail = true;
}
else if (info->uri().endsWith(".desktop")) {
if (thumbnail.isNull())
{
needThumbnail = false;
updateDesktopFileThumbnail(uri, watcher);
}
else
needThumbnail = true;
}
}
if (!needThumbnail)
return;
auto thumbnailJob = new ThumbnailJob(uri, watcher, this);
thumbnailJob->setForceUpdate(force);
m_thumbnail_thread_pool->start(thumbnailJob, hasCustomIcon? QThread::HighestPriority: 0);
qDebug() <<"createThumbnail thumbnailJob start:" <<uri;
}
void ThumbnailManager::updateDesktopFileThumbnail(const QString &uri, std::shared_ptr<FileWatcher> watcher)
{
auto info = FileInfo::fromUri(uri);
if (info->uri().endsWith(".desktop")) {
//qDebug()<<"is desktop file"<<uri;
//get desktop file icon.
//async
//qDebug()<<"desktop file"<<uri;
auto thumbnailJob = new ThumbnailJob(uri, watcher, this);
m_thumbnail_thread_pool->start(thumbnailJob, QThread::Priority::HighestPriority);
} else {
releaseThumbnail(uri);
if (watcher) {
watcher->thumbnailUpdated(uri);
}
}
}
void ThumbnailManager::clearThumbnail()
{
m_semaphore->acquire();
if (!m_hash.isEmpty()) {
m_hash.clear();
}
m_semaphore->release();
}
void ThumbnailManager::releaseThumbnail(const QString &uri)
{
m_semaphore->acquire();
m_hash.remove(uri);
m_semaphore->release();
}
void ThumbnailManager::releaseThumbnail(const QStringList &uris)
{
m_semaphore->acquire();
for (auto uri : uris) {
m_hash.remove(uri);
}
m_semaphore->release();
}
const QIcon ThumbnailManager::tryGetThumbnail(const QString &uri)
{
m_semaphore->acquire();
auto icon = m_hash.value(uri);
m_semaphore->release();
return icon;
}
bool ThumbnailManager::hasThumbnailThreadSafety(const QString &uri)
{
m_semaphore->acquire();
bool res = hasThumbnail(uri);
m_semaphore->release();
return res;
}