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 }