File indexing completed on 2024-05-12 05:10:10

0001 /***************************************************************************
0002     Copyright (C) 2024 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "filereader.h"
0026 #include "../fieldformat.h"
0027 #include "../images/imagefactory.h"
0028 #include "../core/netaccess.h"
0029 
0030 #include <Solid/Device>
0031 #include <Solid/StorageVolume>
0032 #include <Solid/StorageAccess>
0033 
0034 #include <KFileItem>
0035 #ifdef HAVE_KFILEMETADATA
0036 #include <KFileMetaData/Extractor>
0037 #include <KFileMetaData/SimpleExtractionResult>
0038 // kfilemetadata_version.h was added in 5.94, so first use kcoreaddons_version to check
0039 // with the expectation that the two versions should match or be no less than
0040 #include <kcoreaddons_version.h>
0041 #if KCOREADDONS_VERSION >= QT_VERSION_CHECK(5,94,0)
0042 #include <kfilemetadata_version.h>
0043 #endif
0044 #endif
0045 
0046 #include <QDir>
0047 #include <QIcon>
0048 
0049 namespace {
0050   static const int FILE_PREVIEW_SIZE = 128;
0051 }
0052 
0053 using Tellico::FileReaderMetaData;
0054 using Tellico::FileReaderFile;
0055 
0056 #ifdef HAVE_KFILEMETADATA
0057 KFileMetaData::PropertyMap FileReaderMetaData::properties(const KFileItem& item_) {
0058   KFileMetaData::SimpleExtractionResult result(item_.url().toLocalFile(),
0059                                                item_.mimetype(),
0060                                                KFileMetaData::ExtractionResult::ExtractMetaData);
0061   QList<KFileMetaData::Extractor*> exList = m_extractors.fetchExtractors(item_.mimetype());
0062   foreach(KFileMetaData::Extractor* ex, exList) {
0063 // initializing exempi can cause a crash in Exiv for files with XMP data
0064 // crude workaround is to avoid using the exivextractor and the only apparent way is to
0065 // match against the mimetypes. Here, we use image/x-exv as the canary in the coal mine
0066 // see https://bugs.kde.org/show_bug.cgi?id=390744
0067 #ifdef HAVE_EXEMPI
0068     if(!ex->mimetypes().contains(QStringLiteral("image/x-exv"))) {
0069 #else
0070     if(true) {
0071 #endif
0072       ex->extract(&result);
0073     }
0074   }
0075 #if KFILEMETADATA_VERSION >= QT_VERSION_CHECK(5,89,0)
0076   auto props = result.properties(KFileMetaData::PropertiesMapType::MultiMap);
0077 #else
0078   auto props = result.properties();
0079 #endif
0080   return props;
0081 }
0082 #endif
0083 
0084 class FileReaderFile::Private {
0085 public:
0086   Private() {}
0087 
0088   QString volume;
0089   QStringList metaIgnore;
0090   // cache the icon image ids to avoid repeated creation of Data::Image objects
0091   QHash<QString, QString> iconImageId;
0092 #ifdef HAVE_KFILEMETADATA
0093   QHash<KFileMetaData::Property::Property, QString> propertyNameHash;
0094 #endif
0095 };
0096 
0097 FileReaderFile::FileReaderFile(const QUrl& url_) : FileReaderMetaData(url_), d(new Private) {
0098   // going to assume only one volume will ever be imported
0099   d->volume = volumeName();
0100   d->metaIgnore << QStringLiteral("mimeType")
0101                 << QStringLiteral("url")
0102                 << QStringLiteral("fileName")
0103                 << QStringLiteral("lastModified")
0104                 << QStringLiteral("contentSize")
0105                 << QStringLiteral("type");
0106 }
0107 
0108 FileReaderFile::~FileReaderFile() = default;
0109 
0110 bool FileReaderFile::populate(Data::EntryPtr entry, const KFileItem& item) {
0111   const QString title    = QStringLiteral("title");
0112   const QString url      = QStringLiteral("url");
0113   const QString desc     = QStringLiteral("description");
0114   const QString vol      = QStringLiteral("volume");
0115   const QString folder   = QStringLiteral("folder");
0116   const QString type     = QStringLiteral("mimetype");
0117   const QString size     = QStringLiteral("size");
0118   const QString perm     = QStringLiteral("permissions");
0119   const QString owner    = QStringLiteral("owner");
0120   const QString group    = QStringLiteral("group");
0121   const QString created  = QStringLiteral("created");
0122   const QString modified = QStringLiteral("modified");
0123   const QString metainfo = QStringLiteral("metainfo");
0124   const QString icon     = QStringLiteral("icon");
0125 
0126   const QUrl u = item.url();
0127   entry->setField(title,  u.fileName());
0128   entry->setField(url,    u.url());
0129   entry->setField(desc,   item.mimeComment());
0130   entry->setField(vol,    d->volume);
0131   const QString folderPath = QDir(this->url().toLocalFile()).relativeFilePath(u.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path());
0132   // use empty string for root folder instead of "."
0133   entry->setField(folder, folderPath == QLatin1String(".") ? QString() : folderPath);
0134   entry->setField(type,   item.mimetype());
0135   entry->setField(size,   KIO::convertSize(item.size()));
0136   entry->setField(perm,   item.permissionsString());
0137   entry->setField(owner,  item.user());
0138   entry->setField(group,  item.group());
0139 
0140   QDateTime dt(item.time(KFileItem::CreationTime));
0141   if(!dt.isNull()) {
0142     entry->setField(created, dt.date().toString(Qt::ISODate));
0143   }
0144   dt = QDateTime(item.time(KFileItem::ModificationTime));
0145   if(!dt.isNull()) {
0146     entry->setField(modified, dt.date().toString(Qt::ISODate));
0147   }
0148 
0149 #ifdef HAVE_KFILEMETADATA
0150   QStringList strings;
0151   const auto props = properties(item);
0152   for(auto it = props.constBegin(); it != props.constEnd(); ++it) {
0153     const QString value = it.value().toString();
0154     if(!value.isEmpty()) {
0155       QString label;
0156       if(d->propertyNameHash.contains(it.key())) {
0157         label = d->propertyNameHash.value(it.key());
0158       } else {
0159         label = KFileMetaData::PropertyInfo(it.key()).displayName();
0160         d->propertyNameHash.insert(it.key(), label);
0161       }
0162       if(!d->metaIgnore.contains(label)) {
0163         strings << label + FieldFormat::columnDelimiterString() + value;
0164       }
0165     }
0166   }
0167   entry->setField(metainfo, strings.join(FieldFormat::rowDelimiterString()));
0168 #endif
0169 
0170   QPixmap pixmap;
0171   if(useFilePreview()) {
0172     pixmap = Tellico::NetAccess::filePreview(item, FILE_PREVIEW_SIZE);
0173   }
0174   if(pixmap.isNull()) {
0175     if(d->iconImageId.contains(item.iconName())) {
0176       entry->setField(icon, d->iconImageId.value(item.iconName()));
0177     } else {
0178       pixmap = QIcon::fromTheme(item.iconName()).pixmap(QSize(FILE_PREVIEW_SIZE, FILE_PREVIEW_SIZE));
0179       const QString id = ImageFactory::addImage(pixmap, QStringLiteral("PNG"));
0180       if(!id.isEmpty()) {
0181         entry->setField(icon, id);
0182         d->iconImageId.insert(item.iconName(), id);
0183       }
0184     }
0185   } else {
0186     const QString id = ImageFactory::addImage(pixmap, QStringLiteral("PNG"));
0187     if(!id.isEmpty()) {
0188       entry->setField(icon, id);
0189     }
0190   }
0191 
0192   return true;
0193 }
0194 
0195 QString FileReaderFile::volumeName() const {
0196   const QString filePath = url().toLocalFile();
0197   QString matchingPath, volume;
0198   QList<Solid::Device> devices = Solid::Device::listFromType(Solid::DeviceInterface::StorageVolume, QString());
0199   foreach(const Solid::Device& device, devices) {
0200     const Solid::StorageAccess* acc = device.as<const Solid::StorageAccess>();
0201     if(acc && !acc->filePath().isEmpty() && filePath.startsWith(acc->filePath())) {
0202       // it might be possible for one volume to be mounted at /dir1 and another to be mounted at /dir1/dir2
0203       // so we need to find the longest natching filePath
0204       if(acc->filePath().length() > matchingPath.length()) {
0205         matchingPath = acc->filePath();
0206         const Solid::StorageVolume* vol = device.as<const Solid::StorageVolume>();
0207         if(vol) {
0208           volume = vol->label();
0209         }
0210       }
0211     }
0212   }
0213   return volume;
0214 }