peony/libpeony-qt/thumbnail-manager.cpp

465 lines
13 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);
});
}
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);
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;
if (!uri.startsWith("file:///")) {
url = FileUtils::getTargetUri(uri);
}
auto _desktop_file = g_desktop_app_info_new_from_filename(url.path().toUtf8().constData());
if (!_desktop_file) {
return;
}
auto _icon_string = g_desktop_app_info_get_string(_desktop_file, "Icon");
thumbnail = QIcon::fromTheme(_icon_string);
QString string = _icon_string;
//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);
}
if (thumbnail.isNull()) {
if (string.startsWith("/")) {
thumbnail = GenericThumbnailer::generateThumbnail(_icon_string, true);
} else if (string.contains(".")) {
// try getting themed icon with image suffix.
string.chop(string.count() - string.lastIndexOf("."));
thumbnail = QIcon::fromTheme(string);
}
}
//add special path search /use/share/pixmaps
if (thumbnail.isNull())
{
QString path = QString("/usr/share/pixmaps/%1.%2").arg(_icon_string).arg("png");
QString path_svg = QString("/usr/share/pixmaps/%1.%2").arg(_icon_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(_icon_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(_icon_string).arg("png");
if(QFile::exists(path)){
thumbnail=QIcon(path);
}
}
}
g_free(_icon_string);
g_object_unref(_desktop_file);
if (!thumbnail.isNull()) {
insertOrUpdateThumbnail(uri, thumbnail);
if (watcher) {
watcher->fileChanged(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)
{
auto settings = GlobalSettings::getInstance();
if (settings->isExist(FORBID_THUMBNAIL_IN_VIEW)) {
bool do_not_thumbnail = settings->getValue(FORBID_THUMBNAIL_IN_VIEW).toBool();
if (do_not_thumbnail && !force) {
qDebug()<<"setting is not thumbnail";
return;
}
}
//NOTE: we should do createThumbnail() after we have queried the file's info.
auto info = FileInfo::fromUri(uri);
//qDebug()<<"file uri:"<< uri << " mime type:" << info->mimeType();
//qDebug()<<"file path:" << info->filePath();
//qDebug()<<"file modify time:" << info->modifiedTime();
if (!info->mimeType().isEmpty()) {
if (!info->customIcon().isEmpty()) {
auto icon = GenericThumbnailer::generateThumbnail(info->customIcon());
if (!icon.isNull()) {
insertOrUpdateThumbnail(uri, icon);
if (watcher) {
watcher->fileChanged(uri);
}
}
}
else if (info->isImagePdfFile())
{
qDebug() <<"isImagePdfFile m_tril_exist:" <<m_tril_exist;
if (m_tril_exist)
{
createImagePdfFileThumbnail(uri, watcher);
}
}
else if (info->isImageFile()) {
createImageFileThumbnail(uri, watcher);
}
else if (info->mimeType().contains("pdf")) {
createPdfFileThumbnail(uri, watcher);
}
else if(info->isVideoFile()) {
createVideFileThumbnail(uri, watcher);
}
else if (info->isOfficeFile()) {
createOfficeFileThumbnail(uri, watcher);
}
else if (info->isDesktopFile()) {
createDesktopFileThumbnail(uri, watcher);
}
else {
//qDebug()<<"the file type: " << info->mimeType();
//qDebug()<<"the mime type can not generate thumbnail.";
}
}
}
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);
if (!info->customIcon().isEmpty() && info->customIcon().startsWith("/"))
needThumbnail = 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);
m_thumbnail_thread_pool->start(thumbnailJob);
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;
QtConcurrent::run([=]() {
createDesktopFileThumbnail(uri, watcher);
});
} 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;
}