File indexing completed on 2024-05-12 16:46:30
0001 /*************************************************************************** 0002 Copyright (C) 2005-2009 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 <config.h> 0026 0027 #include "filelistingimporter.h" 0028 #include "../collections/filecatalog.h" 0029 #include "../entry.h" 0030 #include "../field.h" 0031 #include "../fieldformat.h" 0032 #include "../images/imagefactory.h" 0033 #include "../utils/tellico_utils.h" 0034 #include "../utils/guiproxy.h" 0035 #include "../progressmanager.h" 0036 #include "../core/netaccess.h" 0037 #include "../tellico_debug.h" 0038 0039 #include <KLocalizedString> 0040 #include <KJobWidgets/KJobWidgets> 0041 #include <KIO/Job> 0042 #include <Solid/Device> 0043 #include <Solid/StorageVolume> 0044 #include <Solid/StorageAccess> 0045 0046 #ifdef HAVE_KFILEMETADATA 0047 #include <KFileMetaData/Extractor> 0048 #include <KFileMetaData/ExtractorCollection> 0049 #include <KFileMetaData/SimpleExtractionResult> 0050 #include <KFileMetaData/PropertyInfo> 0051 #endif 0052 0053 #include <QDate> 0054 #include <QDir> 0055 #include <QCheckBox> 0056 #include <QGroupBox> 0057 #include <QFile> 0058 #include <QFileInfo> 0059 #include <QVBoxLayout> 0060 #include <QApplication> 0061 0062 namespace { 0063 static const int FILE_PREVIEW_SIZE = 128; 0064 } 0065 0066 using Tellico::Import::FileListingImporter; 0067 0068 FileListingImporter::FileListingImporter(const QUrl& url_) : Importer(url_), m_coll(nullptr), m_widget(nullptr), 0069 m_recursive(nullptr), m_filePreview(nullptr), m_job(nullptr), m_cancelled(false) { 0070 } 0071 0072 bool FileListingImporter::canImport(int type) const { 0073 return type == Data::Collection::File; 0074 } 0075 0076 Tellico::Data::CollPtr FileListingImporter::collection() { 0077 if(m_coll) { 0078 return m_coll; 0079 } 0080 0081 ProgressItem& item = ProgressManager::self()->newProgressItem(this, i18n("Scanning files..."), true); 0082 item.setTotalSteps(100); 0083 connect(&item, &Tellico::ProgressItem::signalCancelled, this, &FileListingImporter::slotCancel); 0084 ProgressItem::Done done(this); 0085 0086 // going to assume only one volume will ever be imported 0087 const QString volume = volumeName(); 0088 0089 // the importer might be running without a gui/widget 0090 m_job = (m_widget && m_recursive->isChecked()) 0091 ? KIO::listRecursive(url(), KIO::DefaultFlags, false /* include hidden */) 0092 : KIO::listDir(url(), KIO::DefaultFlags, false /* include hidden */); 0093 KJobWidgets::setWindow(m_job, GUI::Proxy::widget()); 0094 void (KIO::ListJob::* jobEntries)(KIO::Job*, const KIO::UDSEntryList&) = &KIO::ListJob::entries; 0095 connect(static_cast<KIO::ListJob*>(m_job.data()), jobEntries, this, &FileListingImporter::slotEntries); 0096 0097 if(!m_job->exec() || m_cancelled) { 0098 myDebug() << "did not run job:" << m_job->errorString(); 0099 return Data::CollPtr(); 0100 } 0101 0102 #ifdef HAVE_KFILEMETADATA 0103 KFileMetaData::ExtractorCollection extractors; 0104 QHash<KFileMetaData::Property::Property, QString> propertyNameHash; 0105 #endif 0106 0107 QStringList metaIgnore = QStringList() 0108 << QStringLiteral("mimeType") 0109 << QStringLiteral("url") 0110 << QStringLiteral("fileName") 0111 << QStringLiteral("lastModified") 0112 << QStringLiteral("contentSize") 0113 << QStringLiteral("type"); 0114 0115 const bool usePreview = m_widget && m_filePreview->isChecked(); 0116 0117 const QString title = QStringLiteral("title"); 0118 const QString url = QStringLiteral("url"); 0119 const QString desc = QStringLiteral("description"); 0120 const QString vol = QStringLiteral("volume"); 0121 const QString folder = QStringLiteral("folder"); 0122 const QString type = QStringLiteral("mimetype"); 0123 const QString size = QStringLiteral("size"); 0124 const QString perm = QStringLiteral("permissions"); 0125 const QString owner = QStringLiteral("owner"); 0126 const QString group = QStringLiteral("group"); 0127 const QString created = QStringLiteral("created"); 0128 const QString modified = QStringLiteral("modified"); 0129 const QString metainfo = QStringLiteral("metainfo"); 0130 const QString icon = QStringLiteral("icon"); 0131 0132 // cache the icon image ids to avoid repeated creation of Data::Image objects 0133 QHash<QString, QString> iconImageId; 0134 0135 m_coll = new Data::FileCatalog(true); 0136 QString tmp; 0137 const uint stepSize = qMax(1, m_files.count()/100); 0138 const bool showProgress = options() & ImportProgress; 0139 0140 item.setTotalSteps(m_files.count()); 0141 uint j = 0; 0142 foreach(const KFileItem& item, m_files) { 0143 if(m_cancelled) { 0144 break; 0145 } 0146 0147 Data::EntryPtr entry(new Data::Entry(m_coll)); 0148 0149 const QUrl u = item.url(); 0150 entry->setField(title, u.fileName()); 0151 entry->setField(url, u.url()); 0152 entry->setField(desc, item.mimeComment()); 0153 entry->setField(vol, volume); 0154 tmp = QDir(this->url().toLocalFile()).relativeFilePath(u.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path()); 0155 // use empty string for root folder instead of "." 0156 entry->setField(folder, tmp == QLatin1String(".") ? QString() : tmp); 0157 entry->setField(type, item.mimetype()); 0158 entry->setField(size, KIO::convertSize(item.size())); 0159 entry->setField(perm, item.permissionsString()); 0160 entry->setField(owner, item.user()); 0161 entry->setField(group, item.group()); 0162 0163 QDateTime dt(item.time(KFileItem::CreationTime)); 0164 if(!dt.isNull()) { 0165 entry->setField(created, dt.date().toString(Qt::ISODate)); 0166 } 0167 dt = QDateTime(item.time(KFileItem::ModificationTime)); 0168 if(!dt.isNull()) { 0169 entry->setField(modified, dt.date().toString(Qt::ISODate)); 0170 } 0171 0172 #ifdef HAVE_KFILEMETADATA 0173 KFileMetaData::SimpleExtractionResult result(u.toLocalFile(), 0174 item.mimetype(), 0175 KFileMetaData::ExtractionResult::ExtractMetaData); 0176 QList<KFileMetaData::Extractor*> exList = extractors.fetchExtractors(item.mimetype()); 0177 foreach(KFileMetaData::Extractor* ex, exList) { 0178 // initializing exempi can cause a crash in Exiv for files with XMP data 0179 // crude workaround is to avoid using the exivextractor and the only apparent way is to 0180 // match against the mimetypes. Here, we use image/x-exv as the canary in the coal mine 0181 // see https://bugs.kde.org/show_bug.cgi?id=390744 0182 #ifdef HAVE_EXEMPI 0183 if(!ex->mimetypes().contains(QStringLiteral("image/x-exv"))) { 0184 #else 0185 if(true) { 0186 #endif 0187 ex->extract(&result); 0188 } 0189 } 0190 QStringList strings; 0191 KFileMetaData::PropertyMap properties = result.properties(); 0192 KFileMetaData::PropertyMap::const_iterator it = properties.constBegin(); 0193 for( ; it != properties.constEnd(); ++it) { 0194 const QString value = it.value().toString(); 0195 if(!value.isEmpty()) { 0196 QString label; 0197 if(propertyNameHash.contains(it.key())) { 0198 label = propertyNameHash.value(it.key()); 0199 } else { 0200 label = KFileMetaData::PropertyInfo(it.key()).displayName(); 0201 propertyNameHash.insert(it.key(), label); 0202 } 0203 // myDebug() << label << value; 0204 if(!metaIgnore.contains(label)) { 0205 strings << label + FieldFormat::columnDelimiterString() + value; 0206 } 0207 } 0208 } 0209 entry->setField(metainfo, strings.join(FieldFormat::rowDelimiterString())); 0210 #endif 0211 0212 QPixmap pixmap; 0213 if(!m_cancelled && usePreview) { 0214 pixmap = Tellico::NetAccess::filePreview(item, FILE_PREVIEW_SIZE); 0215 } 0216 if(pixmap.isNull()) { 0217 if(iconImageId.contains(item.iconName())) { 0218 entry->setField(icon, iconImageId.value(item.iconName())); 0219 } else { 0220 pixmap = QIcon::fromTheme(item.iconName()).pixmap(QSize(FILE_PREVIEW_SIZE, FILE_PREVIEW_SIZE)); 0221 const QString id = ImageFactory::addImage(pixmap, QStringLiteral("PNG")); 0222 if(!id.isEmpty()) { 0223 entry->setField(icon, id); 0224 iconImageId.insert(item.iconName(), id); 0225 } 0226 } 0227 } else { 0228 const QString id = ImageFactory::addImage(pixmap, QStringLiteral("PNG")); 0229 if(!id.isEmpty()) { 0230 entry->setField(icon, id); 0231 } 0232 } 0233 0234 m_coll->addEntries(entry); 0235 0236 if(showProgress && j%stepSize == 0) { 0237 ProgressManager::self()->setProgress(this, j); 0238 qApp->processEvents(); 0239 } 0240 ++j; 0241 } 0242 0243 if(m_cancelled) { 0244 m_coll = Data::CollPtr(); 0245 return m_coll; 0246 } 0247 0248 return m_coll; 0249 } 0250 0251 QWidget* FileListingImporter::widget(QWidget* parent_) { 0252 if(m_widget) { 0253 return m_widget; 0254 } 0255 0256 m_widget = new QWidget(parent_); 0257 QVBoxLayout* l = new QVBoxLayout(m_widget); 0258 0259 QGroupBox* gbox = new QGroupBox(i18n("File Listing Options"), m_widget); 0260 QVBoxLayout* vlay = new QVBoxLayout(gbox); 0261 0262 m_recursive = new QCheckBox(i18n("Recursive folder search"), gbox); 0263 m_recursive->setWhatsThis(i18n("If checked, folders are recursively searched for all files.")); 0264 // by default, make it checked 0265 m_recursive->setChecked(true); 0266 0267 m_filePreview = new QCheckBox(i18n("Generate file previews"), gbox); 0268 m_filePreview->setWhatsThis(i18n("If checked, previews of the file contents are generated, which can slow down " 0269 "the folder listing.")); 0270 // by default, make it no previews 0271 m_filePreview->setChecked(false); 0272 0273 vlay->addWidget(m_recursive); 0274 vlay->addWidget(m_filePreview); 0275 0276 l->addWidget(gbox); 0277 l->addStretch(1); 0278 return m_widget; 0279 } 0280 0281 void FileListingImporter::slotEntries(KIO::Job* job_, const KIO::UDSEntryList& list_) { 0282 if(m_cancelled) { 0283 job_->kill(); 0284 m_job = nullptr; 0285 return; 0286 } 0287 0288 for(KIO::UDSEntryList::ConstIterator it = list_.begin(); it != list_.end(); ++it) { 0289 KFileItem item(*it, url(), false, true); 0290 if(item.isFile()) { 0291 m_files.append(item); 0292 } 0293 } 0294 } 0295 0296 QString FileListingImporter::volumeName() const { 0297 const QString filePath = url().toLocalFile(); 0298 QString matchingPath, volume; 0299 QList<Solid::Device> devices = Solid::Device::listFromType(Solid::DeviceInterface::StorageVolume, QString()); 0300 foreach(const Solid::Device& device, devices) { 0301 const Solid::StorageAccess* acc = device.as<const Solid::StorageAccess>(); 0302 if(acc && !acc->filePath().isEmpty() && filePath.startsWith(acc->filePath())) { 0303 // it might be possible for one volume to be mounted at /dir1 and another to be mounted at /dir1/dir2 0304 // so we need to find the longest natching filePath 0305 if(acc->filePath().length() > matchingPath.length()) { 0306 matchingPath = acc->filePath(); 0307 const Solid::StorageVolume* vol = device.as<const Solid::StorageVolume>(); 0308 if(vol) { 0309 volume = vol->label(); 0310 } 0311 } 0312 } 0313 } 0314 return volume; 0315 } 0316 0317 void FileListingImporter::slotCancel() { 0318 m_cancelled = true; 0319 if(m_job) { 0320 m_job->kill(); 0321 } 0322 }