diff --git a/src/filemetadataprovider.cpp b/src/filemetadataprovider.cpp index acf9e00..06e75dc 100644 --- a/src/filemetadataprovider.cpp +++ b/src/filemetadataprovider.cpp @@ -1,491 +1,520 @@ /***************************************************************************** * Copyright (C) 2010 by Peter Penz * * Copyright (C) 2012 by Vishesh Handa * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 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 * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *****************************************************************************/ #include "filemetadataprovider.h" #include "indexeddataretriever.h" #include "filefetchjob.h" #include #include #include #include #include // Required includes for subDirectoriesCount(): #ifdef Q_OS_WIN #include #else #include #include #endif using namespace Baloo; namespace { QVariant intersect(const QVariant& v1, const QVariant& v2) { if (!v1.isValid() || !v2.isValid()) { return QVariant(); } // List and String if (v1.type() == QVariant::StringList && v2.type() == QVariant::String) { QStringList list = v1.toStringList(); QString str = v2.toString(); if (!list.contains(str)) { list << str; } return QVariant(list); } // String and List if (v1.type() == QVariant::String && v2.type() == QVariant::StringList) { QStringList list = v2.toStringList(); QString str = v1.toString(); if (!list.contains(str)) { list << str; } return QVariant(list); } // List and List if (v1.type() == QVariant::StringList && v2.type() == QVariant::StringList) { QSet s1 = v1.toStringList().toSet(); QSet s2 = v2.toStringList().toSet(); return QVariant(s1.intersect(s2).toList()); } if (v1 == v2) { return v1; } return QVariant(); } /** * The standard QMap::unite will contain the key multiple times if both \p v1 and \p v2 * contain the same key. * * This will only take the key from \p v2 into account */ QVariantMap unite(const QVariantMap& v1, const QVariantMap& v2) { QVariantMap v(v1); QMapIterator it(v2); while (it.hasNext()) { it.next(); v[it.key()] = it.value(); } return v; } + } void FileMetaDataProvider::totalPropertyAndInsert(const QString& prop, const QList& resources, QSet& allProperties) { if (allProperties.contains(prop)) { int total = 0; foreach (const QVariantMap& map, resources) { QVariantMap::const_iterator it = map.constFind(prop); if (it == map.constEnd()) { total = 0; break; } else { total += it.value().toInt(); } } if (total) { m_data.insert (prop, QVariant(total)); } allProperties.remove(prop); } } void FileMetaDataProvider::slotFileFetchFinished(KJob* job) { FileFetchJob* fetchJob = static_cast(job); QList files = fetchJob->data(); - - if (files.size() == 1) { + + Q_ASSERT(!files.isEmpty()); + + if (files.size() > 1) { + insertCommonData(files); + } else { m_data = files.first(); - insertBasicData(); - } - else { - // - // Only report the stuff that is common to all the files - // - QSet allProperties; - QList propertyList; - foreach (const QVariantMap& fileData, files) { - propertyList << fileData; - allProperties.unite(fileData.uniqueKeys().toSet()); - } - - // Special handling for certain properties - totalPropertyAndInsert("duration", propertyList, allProperties); - totalPropertyAndInsert("characterCount", propertyList, allProperties); - totalPropertyAndInsert("wordCount", propertyList, allProperties); - totalPropertyAndInsert("lineCount", propertyList, allProperties); - - foreach (const QString& propUri, allProperties) { - foreach (const QVariantMap& map, propertyList) { - QVariantMap::const_iterator it = map.find( propUri ); - if( it == map.constEnd() ) { - m_data.remove( propUri ); - break; - } - else { - QVariantMap::iterator dit = m_data.find( it.key() ); - if( dit == m_data.end() ) { - m_data.insert( propUri, it.value() ); - } - else { - QVariant finalValue = intersect( it.value(), dit.value() ); - if( finalValue.isValid() ) - m_data[propUri] = finalValue; - else { - m_data.remove( propUri ); - break; - } - } - } - } - } + insertSingleFileBasicData(); } insertEditableData(); emit loadingFinished(); } void FileMetaDataProvider::slotLoadingFinished(KJob* job) { IndexedDataRetriever* ret = dynamic_cast(job); m_data = unite(m_data, ret->data()); emit loadingFinished(); } -void FileMetaDataProvider::insertBasicData() +void FileMetaDataProvider::insertSingleFileBasicData() { - KFormat format; + // TODO: Handle case if remote URLs are used properly. isDir() does + // not work, the modification date needs also to be adjusted... + Q_ASSERT(m_fileItems.count() <= 1); if (m_fileItems.count() == 1) { - // TODO: Handle case if remote URLs are used properly. isDir() does - // not work, the modification date needs also to be adjusted... - const KFileItem& item = m_fileItems.first(); - - if (item.isDir()) { - const int count = subDirectoriesCount(item.url().path()); - if (count == -1) { - m_data.insert("kfileitem#size", i18nc("unknown file size", "Unknown")); - } else { - const QString itemCountString = i18ncp("@item:intable", "%1 item", "%1 items", count); - m_data.insert("kfileitem#size", itemCountString); - } - } else { - m_data.insert("kfileitem#size", format.formatByteSize(item.size())); + KFormat format; + const KFileItem& item = m_fileItems.first(); + + if (item.isDir()) { + const int count = subDirectoriesCount(item.url().path()); + if (count == -1) { + m_data.insert("kfileitem#size", i18nc("unknown file size", "Unknown")); + } else { + const QString itemCountString = i18ncp("@item:intable", "%1 item", "%1 items", count); + m_data.insert("kfileitem#size", itemCountString); + } + } else { + m_data.insert("kfileitem#size", format.formatByteSize(item.size())); + } + + m_data.insert("kfileitem#type", item.mimeComment()); + m_data.insert("kfileitem#modified", + format.formatRelativeDateTime(item.time(KFileItem::ModificationTime), QLocale::LongFormat)); + m_data.insert("kfileitem#owner", item.user()); + m_data.insert("kfileitem#permissions", item.permissionsString()); + } + +} + +void FileMetaDataProvider::insertFilesListBasicData() +{ + + KFormat format; + // If all directories + Q_ASSERT(m_fileItems.count() > 1); + bool allDirectories = true; + for (const KFileItem& item : m_fileItems) { + allDirectories &= item.isDir(); + if (!allDirectories) { + break; } + } - m_data.insert("kfileitem#type", item.mimeComment()); - m_data.insert("kfileitem#modified", format.formatRelativeDateTime(item.time(KFileItem::ModificationTime), QLocale::LongFormat) ); - m_data.insert("kfileitem#owner", item.user()); - m_data.insert("kfileitem#permissions", item.permissionsString()); - - - } else if (m_fileItems.count() > 1) { - // If all directories - bool allDirectories = true; + if (allDirectories) { + int count = 0; for (const KFileItem& item : m_fileItems) { - allDirectories &= item.isDir(); + count += subDirectoriesCount(item.url().path()); } - if (allDirectories) { - int count = 0; - for (const KFileItem& item : m_fileItems) { - count += subDirectoriesCount(item.url().path()); - } - - if (count) { - const QString itemCountString = i18ncp("@item:intable", "%1 item", "%1 items", count); - m_data.insert("kfileitem#totalSize", itemCountString); - } - } else { - // Calculate the size of all items - quint64 totalSize = 0; - for (const KFileItem& item : m_fileItems) { - if (!item.isDir() && !item.isLink()) { - totalSize += item.size(); - } + if (count) { + const QString itemCountString = i18ncp("@item:intable", "%1 item", "%1 items", count); + m_data.insert("kfileitem#totalSize", itemCountString); + } + } else { + // Calculate the size of all items + quint64 totalSize = 0; + for (const KFileItem& item : m_fileItems) { + if (!item.isDir() && !item.isLink()) { + totalSize += item.size(); } - m_data.insert("kfileitem#totalSize", format.formatByteSize(totalSize)); } + m_data.insert("kfileitem#totalSize", format.formatByteSize(totalSize)); + } +} + +void FileMetaDataProvider::insertBasicData() +{ + if (m_fileItems.count() > 1) { + insertFilesListBasicData(); + } else { + insertSingleFileBasicData(); } } void FileMetaDataProvider::insertEditableData() { if (!m_readOnly) { if (!m_data.contains("tags")) { m_data.insert("tags", QVariant()); } if (!m_data.contains("rating")) { m_data.insert("rating", 0); } if (!m_data.contains("userComment")) { m_data.insert("userComment", QVariant()); } } } +void FileMetaDataProvider::insertCommonData(const QList& files) +{ + // + // Only report the stuff that is common to all the files + // + QSet allProperties; + QList propertyList; + foreach (const QVariantMap& fileData, files) { + propertyList << fileData; + allProperties.unite(fileData.uniqueKeys().toSet()); + } + + // Special handling for certain properties + totalPropertyAndInsert("duration", propertyList, allProperties); + totalPropertyAndInsert("characterCount", propertyList, allProperties); + totalPropertyAndInsert("wordCount", propertyList, allProperties); + totalPropertyAndInsert("lineCount", propertyList, allProperties); + + foreach (const QString& propUri, allProperties) { + foreach (const QVariantMap& map, propertyList) { + QVariantMap::const_iterator it = map.find(propUri); + if (it == map.constEnd()) { + m_data.remove(propUri); + break; + } + + QVariantMap::iterator dit = m_data.find(it.key()); + if (dit == m_data.end()) { + m_data.insert(propUri, it.value()); + } else { + QVariant finalValue = intersect(it.value(), dit.value()); + if (finalValue.isValid()) { + m_data[propUri] = finalValue; + } else { + m_data.remove(propUri); + break; + } + } + } + } +} + FileMetaDataProvider::FileMetaDataProvider(QObject* parent) : QObject(parent) , m_readOnly(false) , m_realTimeIndexing(false) { } FileMetaDataProvider::~FileMetaDataProvider() { } -void FileMetaDataProvider::setItems(const KFileItemList& items) +void FileMetaDataProvider::setFileItem() { - m_fileItems = items; - m_data.clear(); - m_realTimeIndexing = false; - - if (items.isEmpty()) { + // There are 3 code paths - + // Remote file + // Single local file - + // * Not Indexed + // * Indexed + // + const QUrl url = m_fileItems.first().targetUrl(); + if (!url.isLocalFile()) { + insertBasicData(); emit loadingFinished(); return; } - // There are 4 code paths - - // Single File - - // * Not Indexed - // * Indexed - // - if (items.size() == 1) { - const QUrl url = items.first().targetUrl(); - if (!url.isLocalFile()) { - insertBasicData(); - emit loadingFinished(); - return; - } + // Not Indexed + const QString filePath = url.toLocalFile(); + if (!m_config.fileIndexingEnabled() || !m_config.shouldBeIndexed(filePath)) { + m_realTimeIndexing = true; - // Not Indexed - const QString filePath = url.toLocalFile(); - if (!m_config.fileIndexingEnabled() || !m_config.shouldBeIndexed(filePath)) { - m_realTimeIndexing = true; - - IndexedDataRetriever *ret = new IndexedDataRetriever(filePath, this); - connect(ret, SIGNAL(finished(KJob*)), this, SLOT(slotLoadingFinished(KJob*))); - ret->start(); - - insertBasicData(); - insertEditableData(); - emit loadingFinished(); - - } else { - FileFetchJob* job = new FileFetchJob(QStringList() << filePath, this); - connect(job, SIGNAL(finished(KJob*)), this, SLOT(slotFileFetchFinished(KJob*))); - job->start(); - } + IndexedDataRetriever *ret = new IndexedDataRetriever(filePath, this); + connect(ret, SIGNAL(finished(KJob*)), this, SLOT(slotLoadingFinished(KJob*))); + ret->start(); + + insertBasicData(); + insertEditableData(); + emit loadingFinished(); + } else { - // Multiple Files - - // * Not Indexed - // * Indexed - QStringList urls; - Q_FOREACH (const KFileItem& item, items) { - const QUrl url = item.targetUrl(); - // Only extract data from indexed files, - // it would be too expensive otherwise. - if (url.isLocalFile()) { - urls << url.toLocalFile(); - } - } + FileFetchJob* job = new FileFetchJob(QStringList() << filePath, this); + connect(job, SIGNAL(finished(KJob*)), this, SLOT(slotFileFetchFinished(KJob*))); + job->start(); + } +} - if (urls.isEmpty()) { - insertBasicData(); - emit loadingFinished(); - return; +void FileMetaDataProvider::setFileItems() +{ + // Multiple Files - + // * Not Indexed + // * Indexed + + QStringList urls; + // Only extract data from indexed files, + // it would be too expensive otherwise. + Q_FOREACH (const KFileItem& item, m_fileItems) { + const QUrl url = item.targetUrl(); + if (url.isLocalFile()) { + urls << url.toLocalFile(); } + } + if (!urls.isEmpty()) { FileFetchJob* job = new FileFetchJob(urls, this); connect(job, SIGNAL(finished(KJob*)), this, SLOT(slotFileFetchFinished(KJob*))); job->start(); + } - insertBasicData(); + insertBasicData(); + emit loadingFinished(); +} + +void FileMetaDataProvider::setItems(const KFileItemList& items) +{ + m_fileItems = items; + m_data.clear(); + m_realTimeIndexing = false; + + if (items.isEmpty()) { emit loadingFinished(); + } else if (items.size() == 1) { + setFileItem(); + } else { + setFileItems(); } } QString FileMetaDataProvider::label(const QString& metaDataLabel) const { struct TranslationItem { const char* const key; const char* const context; const char* const value; }; static const TranslationItem translations[] = { { "kfileitem#comment", I18N_NOOP2_NOSTRIP("@label", "Comment") }, { "kfileitem#modified", I18N_NOOP2_NOSTRIP("@label", "Modified") }, { "kfileitem#owner", I18N_NOOP2_NOSTRIP("@label", "Owner") }, { "kfileitem#permissions", I18N_NOOP2_NOSTRIP("@label", "Permissions") }, { "kfileitem#rating", I18N_NOOP2_NOSTRIP("@label", "Rating") }, { "kfileitem#size", I18N_NOOP2_NOSTRIP("@label", "Size") }, { "kfileitem#tags", I18N_NOOP2_NOSTRIP("@label", "Tags") }, { "kfileitem#totalSize", I18N_NOOP2_NOSTRIP("@label", "Total Size") }, { "kfileitem#type", I18N_NOOP2_NOSTRIP("@label", "Type") }, { "tags", I18N_NOOP2_NOSTRIP("@label", "Tags") }, { "rating", I18N_NOOP2_NOSTRIP("@label", "Rating") }, { "userComment", I18N_NOOP2_NOSTRIP("@label", "Comment") }, { "originUrl", I18N_NOOP2_NOSTRIP("@label", "Downloaded From") }, { 0, 0, 0} // Mandatory last entry }; static QHash hash; if (hash.isEmpty()) { const TranslationItem* item = &translations[0]; while (item->key != 0) { hash.insert(item->key, i18nc(item->context, item->value)); ++item; } } QString value = hash.value(metaDataLabel); if (value.isEmpty()) { value = KFileMetaData::PropertyInfo::fromName(metaDataLabel).displayName(); } return value; } QString FileMetaDataProvider::group(const QString& label) const { static QHash uriGrouper; if (uriGrouper.isEmpty()) { // KFileItem Data uriGrouper.insert(QLatin1String("kfileitem#type"), QLatin1String("0FileItemA")); uriGrouper.insert(QLatin1String("kfileitem#size"), QLatin1String("0FileItemB")); uriGrouper.insert(QLatin1String("kfileitem#totalSize"), QLatin1String("0FileItemB")); uriGrouper.insert(QLatin1String("kfileitem#modified"), QLatin1String("0FileItemC")); uriGrouper.insert(QLatin1String("kfileitem#owner"), QLatin1String("0FileItemD")); uriGrouper.insert(QLatin1String("kfileitem#permissions"), QLatin1String("0FileItemE")); // Editable Data uriGrouper.insert(QLatin1String("tags"), QLatin1String("1EditableDataA")); uriGrouper.insert(QLatin1String("rating"), QLatin1String("1EditableDataB")); uriGrouper.insert(QLatin1String("userComment"), QLatin1String("1EditableDataC")); // Image Data uriGrouper.insert(QLatin1String("width"), QLatin1String("2SizA")); uriGrouper.insert(QLatin1String("height"), QLatin1String("2SizeB")); // Music Data uriGrouper.insert("title", QLatin1String("3MusicA")); uriGrouper.insert("artist", QLatin1String("3MusicB")); uriGrouper.insert("album", QLatin1String("3MusicC")); uriGrouper.insert("genre", QLatin1String("3MusicD")); uriGrouper.insert("trackNumber", QLatin1String("3MusicE")); // Audio Data uriGrouper.insert("duration", QLatin1String("4AudioA")); uriGrouper.insert("sampleRate", QLatin1String("4AudioB")); uriGrouper.insert("sampleCount", QLatin1String("4AudioC")); // Miscellaneous Data uriGrouper.insert("originUrl", QLatin1String("5MiscA")); } const QString val = uriGrouper.value(label); if (val.isEmpty()) { return "lastGroup"; } return val; } KFileItemList FileMetaDataProvider::items() const { return m_fileItems; } void FileMetaDataProvider::setReadOnly(bool readOnly) { m_readOnly = readOnly; } bool FileMetaDataProvider::isReadOnly() const { return m_readOnly; } QVariantMap FileMetaDataProvider::data() const { return m_data; } int FileMetaDataProvider::subDirectoriesCount(const QString& path) { #ifdef Q_OS_WIN QDir dir(path); return dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count(); #else // Taken from kdelibs/kio/kio/kdirmodel.cpp // Copyright (C) 2006 David Faure int count = -1; DIR* dir = ::opendir(QFile::encodeName(path)); if (dir) { count = 0; struct dirent *dirEntry = 0; while ((dirEntry = ::readdir(dir))) { // krazy:exclude=syscalls if (dirEntry->d_name[0] == '.') { if (dirEntry->d_name[1] == '\0') { // Skip "." continue; } if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { // Skip ".." continue; } } ++count; } ::closedir(dir); } return count; #endif } bool FileMetaDataProvider::realTimeIndexing() { return m_realTimeIndexing; } diff --git a/src/filemetadataprovider.h b/src/filemetadataprovider.h index 60216f6..0aa3d03 100644 --- a/src/filemetadataprovider.h +++ b/src/filemetadataprovider.h @@ -1,147 +1,166 @@ /***************************************************************************** * Copyright (C) 2010 by Peter Penz * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 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 * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *****************************************************************************/ #ifndef _BALOO_FILEMETADATAMODEL_H #define _BALOO_FILEMETADATAMODEL_H #include #include #include #include #include namespace Baloo { /** * @brief Provides the data for the MetaDataWidget. * * The default implementation provides all meta data * that are available due to Nepomuk. If custom * meta data should be added, the method KFileMetaDataProvider::loadData() * must be overwritten. * * @see FileMetaDataWidget */ class FileMetaDataProvider : public QObject { Q_OBJECT public: explicit FileMetaDataProvider(QObject* parent = 0); virtual ~FileMetaDataProvider(); /** * Sets the items, where the meta data should be * requested. The loading of the meta data is done * asynchronously. The signal loadingFinished() is * emitted, as soon as the loading has been finished. * The meta data can be retrieved by * KFileMetaDataProvider::data() afterwards. The label for * each item can be retrieved by KFileMetaDataProvider::label(). */ void setItems(const KFileItemList& items); KFileItemList items() const; /** * If set to true, data such as the comment, tag or rating cannot be changed by the user. * Per default read-only is disabled. The method readOnlyChanged() can be overwritten * to react on the change. */ void setReadOnly(bool readOnly); bool isReadOnly() const; /** * @return Translated string for the label of the meta data represented * by \p metaDataUri. If no custom translation is provided, the * base implementation must be invoked. */ virtual QString label(const QString& metaDataLabel) const; /** * Meta data items are sorted alphabetically by their translated * label per default. However it is possible to provide an internal * prefix to the label, so that specific items are grouped together. * For example it makes sense that the meta data for 'width' and 'height' * of an image are shown below each other. By adding a common prefix, * a grouping is done. * @return Returns the name of the group the meta data indicated * by \p label belongs to. Per default an empty string * is returned. */ virtual QString group(const QString& label) const; /** * @return Meta data for the items that have been set by * KFileMetaDataProvider::setItems(). The method should * be invoked after the signal loadingFinished() has * been received (otherwise no data will be returned). */ QVariantMap data() const; /** * Returns true if the items do not exist in the database and * have just been indexed. This means, that we should not allow * others to search through these items cause they do not exist * in the database. */ bool realTimeIndexing(); Q_SIGNALS: /** * Is emitted after the loading triggered by KFileMetaDataProvider::setItems() * has been finished. * * Can be emitted multiple times to indicate data changes */ void loadingFinished(); private Q_SLOTS: void slotLoadingFinished(KJob* job); void slotFileFetchFinished(KJob* job); + +private: void insertBasicData(); void insertEditableData(); + + void setFileItem(); + void setFileItems(); + + /** + * Insert intersection of common data of \p files + */ + void insertCommonData(const QList& files); -private: + /** + * Insert basic data of a single file + */ + void insertSingleFileBasicData(); + + /** + * Insert basic data of a list of files + */ + void insertFilesListBasicData(); + /** * Checks for the existance of \p uri in \p allProperties, and accordingly * inserts the total integer value of that property in m_data. On completion * it removes \p uri from \p allProperties */ void totalPropertyAndInsert(const QString& prop, const QList& resources, QSet& allProperties); - + /* * @return The number of subdirectories for the directory \a path. */ static int subDirectoriesCount(const QString &path); bool m_readOnly; /// Set to true when the file has been specially indexed and does not exist in the db bool m_realTimeIndexing; QList m_fileItems; QVariantMap m_data; Baloo::IndexerConfig m_config; }; } #endif