summaryrefslogtreecommitdiffstats
path: root/src/corelib/mimetypes/qmimeprovider.cpp
diff options
context:
space:
mode:
authorDavid Faure <faure@kde.org>2012-02-18 16:00:09 +0100
committerQt by Nokia <qt-info@nokia.com>2012-02-18 22:19:43 +0100
commitb164911b7f0efd81ec33325405b88bff8b2334d0 (patch)
treeea4c2eb39f3ae8edb90b8255ffe3ee1b14806ab7 /src/corelib/mimetypes/qmimeprovider.cpp
parent87fcbd82fc679715853e5261f8f0194a80c10b76 (diff)
Import QMimeType / QMimeDatabase into QtCore.
History of the development before the import: ssh://codereview.qt-project.org/playground/mimetypes.git Mimetype definitions come from shared-mime-info where available (UNIX systems), loaded using a mmap'ed binary cache generated by update-mime-database. As a fallback if no cache is found, we parse the raw XML files otherwise. This makes the MIME type support fast and with very low memory usage on UNIX, and it makes it easy to use on Windows (no dependency on shared-mime-info, Qt even includes a freedesktop.xml file to use if none are found on the system). Change-Id: I27b05008216ff936dc463bd80d3893422bfb940e Reviewed-by: Richard J. Moore <rich@kde.org>
Diffstat (limited to 'src/corelib/mimetypes/qmimeprovider.cpp')
-rw-r--r--src/corelib/mimetypes/qmimeprovider.cpp830
1 files changed, 830 insertions, 0 deletions
diff --git a/src/corelib/mimetypes/qmimeprovider.cpp b/src/corelib/mimetypes/qmimeprovider.cpp
new file mode 100644
index 0000000000..8ef0ee8881
--- /dev/null
+++ b/src/corelib/mimetypes/qmimeprovider.cpp
@@ -0,0 +1,830 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmimeprovider_p.h"
+
+#include "qmimetypeparser_p.h"
+#include <qstandardpaths.h>
+#include "qmimemagicrulematcher_p.h"
+
+#include <QXmlStreamReader>
+#include <QDir>
+#include <QFile>
+#include <QByteArrayMatcher>
+#include <QDebug>
+#include <QDateTime>
+#include <QtEndian>
+
+QT_BEGIN_NAMESPACE
+
+static QString fallbackParent(const QString &mimeTypeName)
+{
+ const QString myGroup = mimeTypeName.left(mimeTypeName.indexOf(QLatin1Char('/')));
+ // All text/* types are subclasses of text/plain.
+ if (myGroup == QLatin1String("text") && mimeTypeName != QLatin1String("text/plain"))
+ return QLatin1String("text/plain");
+ // All real-file mimetypes implicitly derive from application/octet-stream
+ if (myGroup != QLatin1String("inode") &&
+ // ignore non-file extensions
+ myGroup != QLatin1String("all") && myGroup != QLatin1String("fonts") && myGroup != QLatin1String("print") && myGroup != QLatin1String("uri")
+ && mimeTypeName != QLatin1String("application/octet-stream")) {
+ return QLatin1String("application/octet-stream");
+ }
+ return QString();
+}
+
+QMimeProviderBase::QMimeProviderBase(QMimeDatabasePrivate *db)
+ : m_db(db)
+{
+}
+
+Q_CORE_EXPORT int qmime_secondsBetweenChecks = 5; // exported for the unit test
+
+bool QMimeProviderBase::shouldCheck()
+{
+ const QDateTime now = QDateTime::currentDateTime();
+ if (m_lastCheck.isValid() && m_lastCheck.secsTo(now) < qmime_secondsBetweenChecks)
+ return false;
+ m_lastCheck = now;
+ return true;
+}
+
+QMimeBinaryProvider::QMimeBinaryProvider(QMimeDatabasePrivate *db)
+ : QMimeProviderBase(db), m_mimetypeListLoaded(false)
+{
+}
+
+#if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY)
+#define QT_USE_MMAP
+#endif
+
+struct QMimeBinaryProvider::CacheFile
+{
+ CacheFile(const QString &fileName);
+ ~CacheFile();
+
+ bool isValid() const { return m_valid; }
+ inline quint16 getUint16(int offset) const
+ {
+ return qFromBigEndian(*reinterpret_cast<quint16 *>(data + offset));
+ }
+ inline quint32 getUint32(int offset) const
+ {
+ return qFromBigEndian(*reinterpret_cast<quint32 *>(data + offset));
+ }
+ inline const char *getCharStar(int offset) const
+ {
+ return reinterpret_cast<const char *>(data + offset);
+ }
+ bool load();
+ bool reload();
+
+ QFile file;
+ uchar *data;
+ QDateTime m_mtime;
+ bool m_valid;
+};
+
+QMimeBinaryProvider::CacheFile::CacheFile(const QString &fileName)
+ : file(fileName), m_valid(false)
+{
+ load();
+}
+
+QMimeBinaryProvider::CacheFile::~CacheFile()
+{
+}
+
+bool QMimeBinaryProvider::CacheFile::load()
+{
+ if (!file.open(QIODevice::ReadOnly))
+ return false;
+ data = file.map(0, file.size());
+ if (data) {
+ const int major = getUint16(0);
+ const int minor = getUint16(2);
+ m_valid = (major == 1 && minor >= 1 && minor <= 2);
+ }
+ m_mtime = QFileInfo(file).lastModified();
+ return m_valid;
+}
+
+bool QMimeBinaryProvider::CacheFile::reload()
+{
+ //qDebug() << "reload!" << file->fileName();
+ m_valid = false;
+ if (file.isOpen()) {
+ file.close();
+ }
+ data = 0;
+ return load();
+}
+
+QMimeBinaryProvider::CacheFile *QMimeBinaryProvider::CacheFileList::findCacheFile(const QString &fileName) const
+{
+ for (const_iterator it = begin(); it != end(); ++it) {
+ if ((*it)->file.fileName() == fileName)
+ return *it;
+ }
+ return 0;
+}
+
+QMimeBinaryProvider::~QMimeBinaryProvider()
+{
+ qDeleteAll(m_cacheFiles);
+}
+
+// Position of the "list offsets" values, at the beginning of the mime.cache file
+enum {
+ PosAliasListOffset = 4,
+ PosParentListOffset = 8,
+ PosLiteralListOffset = 12,
+ PosReverseSuffixTreeOffset = 16,
+ PosGlobListOffset = 20,
+ PosMagicListOffset = 24,
+ // PosNamespaceListOffset = 28,
+ PosIconsListOffset = 32,
+ PosGenericIconsListOffset = 36
+};
+
+bool QMimeBinaryProvider::isValid()
+{
+#if defined(QT_USE_MMAP)
+ if (!qgetenv("QT_NO_MIME_CACHE").isEmpty())
+ return false;
+
+ Q_ASSERT(m_cacheFiles.isEmpty()); // this method is only ever called once
+ checkCache();
+
+ if (m_cacheFiles.count() > 1)
+ return true;
+ if (m_cacheFiles.isEmpty())
+ return false;
+
+ // We found exactly one file; is it the user-modified mimes, or a system file?
+ const QString foundFile = m_cacheFiles.first()->file.fileName();
+ const QString localCacheFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/mime.cache");
+
+ return foundFile != localCacheFile;
+#else
+ return false;
+#endif
+}
+
+bool QMimeBinaryProvider::CacheFileList::checkCacheChanged()
+{
+ bool somethingChanged = false;
+ QMutableListIterator<CacheFile *> it(*this);
+ while (it.hasNext()) {
+ CacheFile *cacheFile = it.next();
+ QFileInfo fileInfo(cacheFile->file);
+ if (!fileInfo.exists()) { // This can't happen by just running update-mime-database. But the user could use rm -rf :-)
+ delete cacheFile;
+ it.remove();
+ somethingChanged = true;
+ } else if (fileInfo.lastModified() > cacheFile->m_mtime) {
+ if (!cacheFile->reload()) {
+ delete cacheFile;
+ it.remove();
+ }
+ somethingChanged = true;
+ }
+ }
+ return somethingChanged;
+}
+
+void QMimeBinaryProvider::checkCache()
+{
+ if (!shouldCheck())
+ return;
+
+ // First iterate over existing known cache files and check for uptodate
+ if (m_cacheFiles.checkCacheChanged())
+ m_mimetypeListLoaded = false;
+
+ // Then check if new cache files appeared
+ const QStringList cacheFileNames = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime/mime.cache"));
+ if (cacheFileNames != m_cacheFileNames) {
+ foreach (const QString &cacheFileName, cacheFileNames) {
+ CacheFile *cacheFile = m_cacheFiles.findCacheFile(cacheFileName);
+ if (!cacheFile) {
+ //qDebug() << "new file:" << cacheFileName;
+ cacheFile = new CacheFile(cacheFileName);
+ if (cacheFile->isValid()) // verify version
+ m_cacheFiles.append(cacheFile);
+ else
+ delete cacheFile;
+ }
+ }
+ m_cacheFileNames = cacheFileNames;
+ m_mimetypeListLoaded = false;
+ }
+}
+
+static QMimeType mimeTypeForNameUnchecked(const QString &name)
+{
+ QMimeTypePrivate data;
+ data.name = name;
+ // The rest is retrieved on demand.
+ // comment and globPatterns: in loadMimeTypePrivate
+ // iconName: in loadIcon
+ // genericIconName: in loadGenericIcon
+ return QMimeType(data);
+}
+
+QMimeType QMimeBinaryProvider::mimeTypeForName(const QString &name)
+{
+ checkCache();
+ if (!m_mimetypeListLoaded)
+ loadMimeTypeList();
+ if (!m_mimetypeNames.contains(name))
+ return QMimeType(); // unknown mimetype
+ return mimeTypeForNameUnchecked(name);
+}
+
+QStringList QMimeBinaryProvider::findByFileName(const QString &fileName, QString *foundSuffix)
+{
+ checkCache();
+ const QString lowerFileName = fileName.toLower();
+ QMimeGlobMatchResult result;
+ // TODO this parses in the order (local, global). Check that it handles "NOGLOBS" correctly.
+ foreach (CacheFile *cacheFile, m_cacheFiles) {
+ matchGlobList(result, cacheFile, cacheFile->getUint32(PosLiteralListOffset), fileName);
+ matchGlobList(result, cacheFile, cacheFile->getUint32(PosGlobListOffset), fileName);
+ const int reverseSuffixTreeOffset = cacheFile->getUint32(PosReverseSuffixTreeOffset);
+ const int numRoots = cacheFile->getUint32(reverseSuffixTreeOffset);
+ const int firstRootOffset = cacheFile->getUint32(reverseSuffixTreeOffset + 4);
+ matchSuffixTree(result, cacheFile, numRoots, firstRootOffset, lowerFileName, fileName.length() - 1, false);
+ if (result.m_matchingMimeTypes.isEmpty())
+ matchSuffixTree(result, cacheFile, numRoots, firstRootOffset, fileName, fileName.length() - 1, true);
+ }
+ if (foundSuffix)
+ *foundSuffix = result.m_foundSuffix;
+ return result.m_matchingMimeTypes;
+}
+
+void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off, const QString &fileName)
+{
+ const int numGlobs = cacheFile->getUint32(off);
+ //qDebug() << "Loading" << numGlobs << "globs from" << cacheFile->file.fileName() << "at offset" << cacheFile->globListOffset;
+ for (int i = 0; i < numGlobs; ++i) {
+ const int globOffset = cacheFile->getUint32(off + 4 + 12 * i);
+ const int mimeTypeOffset = cacheFile->getUint32(off + 4 + 12 * i + 4);
+ const int flagsAndWeight = cacheFile->getUint32(off + 4 + 12 * i + 8);
+ const int weight = flagsAndWeight & 0xff;
+ const bool caseSensitive = flagsAndWeight & 0x100;
+ const Qt::CaseSensitivity qtCaseSensitive = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
+ const QString pattern = QLatin1String(cacheFile->getCharStar(globOffset));
+
+ const char *mimeType = cacheFile->getCharStar(mimeTypeOffset);
+ //qDebug() << pattern << mimeType << weight << caseSensitive;
+ QMimeGlobPattern glob(pattern, QString() /*unused*/, weight, qtCaseSensitive);
+
+ // TODO: this could be done faster for literals where a simple == would do.
+ if (glob.matchFileName(fileName))
+ result.addMatch(QLatin1String(mimeType), weight, pattern);
+ }
+}
+
+bool QMimeBinaryProvider::matchSuffixTree(QMimeGlobMatchResult &result, QMimeBinaryProvider::CacheFile *cacheFile, int numEntries, int firstOffset, const QString &fileName, int charPos, bool caseSensitiveCheck)
+{
+ QChar fileChar = fileName[charPos];
+ int min = 0;
+ int max = numEntries - 1;
+ while (min <= max) {
+ const int mid = (min + max) / 2;
+ const int off = firstOffset + 12 * mid;
+ const QChar ch = cacheFile->getUint32(off);
+ if (ch < fileChar)
+ min = mid + 1;
+ else if (ch > fileChar)
+ max = mid - 1;
+ else {
+ --charPos;
+ int numChildren = cacheFile->getUint32(off + 4);
+ int childrenOffset = cacheFile->getUint32(off + 8);
+ bool success = false;
+ if (charPos > 0)
+ success = matchSuffixTree(result, cacheFile, numChildren, childrenOffset, fileName, charPos, caseSensitiveCheck);
+ if (!success) {
+ for (int i = 0; i < numChildren; ++i) {
+ const int childOff = childrenOffset + 12 * i;
+ const int mch = cacheFile->getUint32(childOff);
+ if (mch != 0)
+ break;
+ const int mimeTypeOffset = cacheFile->getUint32(childOff + 4);
+ const char *mimeType = cacheFile->getCharStar(mimeTypeOffset);
+ const int flagsAndWeight = cacheFile->getUint32(childOff + 8);
+ const int weight = flagsAndWeight & 0xff;
+ const bool caseSensitive = flagsAndWeight & 0x100;
+ if (caseSensitiveCheck || !caseSensitive) {
+ result.addMatch(QLatin1String(mimeType), weight, QLatin1Char('*') + fileName.mid(charPos+1));
+ success = true;
+ }
+ }
+ }
+ return success;
+ }
+ }
+ return false;
+}
+
+bool QMimeBinaryProvider::matchMagicRule(QMimeBinaryProvider::CacheFile *cacheFile, int numMatchlets, int firstOffset, const QByteArray &data)
+{
+ const char *dataPtr = data.constData();
+ const int dataSize = data.size();
+ for (int matchlet = 0; matchlet < numMatchlets; ++matchlet) {
+ const int off = firstOffset + matchlet * 32;
+ const int rangeStart = cacheFile->getUint32(off);
+ const int rangeLength = cacheFile->getUint32(off + 4);
+ //const int wordSize = cacheFile->getUint32(off + 8);
+ const int valueLength = cacheFile->getUint32(off + 12);
+ const int valueOffset = cacheFile->getUint32(off + 16);
+ const int maskOffset = cacheFile->getUint32(off + 20);
+ const char *mask = maskOffset ? cacheFile->getCharStar(maskOffset) : NULL;
+
+ if (!QMimeMagicRule::matchSubstring(dataPtr, dataSize, rangeStart, rangeLength, valueLength, cacheFile->getCharStar(valueOffset), mask))
+ continue;
+
+ const int numChildren = cacheFile->getUint32(off + 24);
+ const int firstChildOffset = cacheFile->getUint32(off + 28);
+ if (numChildren == 0) // No submatch? Then we are done.
+ return true;
+ // Check that one of the submatches matches too
+ if (matchMagicRule(cacheFile, numChildren, firstChildOffset, data))
+ return true;
+ }
+ return false;
+}
+
+QMimeType QMimeBinaryProvider::findByMagic(const QByteArray &data, int *accuracyPtr)
+{
+ checkCache();
+ foreach (CacheFile *cacheFile, m_cacheFiles) {
+ const int magicListOffset = cacheFile->getUint32(PosMagicListOffset);
+ const int numMatches = cacheFile->getUint32(magicListOffset);
+ //const int maxExtent = cacheFile->getUint32(magicListOffset + 4);
+ const int firstMatchOffset = cacheFile->getUint32(magicListOffset + 8);
+
+ for (int i = 0; i < numMatches; ++i) {
+ const int off = firstMatchOffset + i * 16;
+ const int numMatchlets = cacheFile->getUint32(off + 8);
+ const int firstMatchletOffset = cacheFile->getUint32(off + 12);
+ if (matchMagicRule(cacheFile, numMatchlets, firstMatchletOffset, data)) {
+ const int mimeTypeOffset = cacheFile->getUint32(off + 4);
+ const char *mimeType = cacheFile->getCharStar(mimeTypeOffset);
+ *accuracyPtr = cacheFile->getUint32(off);
+ // Return the first match. We have no rules for conflicting magic data...
+ // (mime.cache itself is sorted, but what about local overrides with a lower prio?)
+ return mimeTypeForNameUnchecked(QLatin1String(mimeType));
+ }
+ }
+ }
+ return QMimeType();
+}
+
+QStringList QMimeBinaryProvider::parents(const QString &mime)
+{
+ checkCache();
+ const QByteArray mimeStr = mime.toLatin1();
+ QStringList result;
+ foreach (CacheFile *cacheFile, m_cacheFiles) {
+ const int parentListOffset = cacheFile->getUint32(PosParentListOffset);
+ const int numEntries = cacheFile->getUint32(parentListOffset);
+
+ int begin = 0;
+ int end = numEntries - 1;
+ while (begin <= end) {
+ const int medium = (begin + end) / 2;
+ const int off = parentListOffset + 4 + 8 * medium;
+ const int mimeOffset = cacheFile->getUint32(off);
+ const char *aMime = cacheFile->getCharStar(mimeOffset);
+ const int cmp = qstrcmp(aMime, mimeStr);
+ if (cmp < 0) {
+ begin = medium + 1;
+ } else if (cmp > 0) {
+ end = medium - 1;
+ } else {
+ const int parentsOffset = cacheFile->getUint32(off + 4);
+ const int numParents = cacheFile->getUint32(parentsOffset);
+ for (int i = 0; i < numParents; ++i) {
+ const int parentOffset = cacheFile->getUint32(parentsOffset + 4 + 4 * i);
+ const char *aParent = cacheFile->getCharStar(parentOffset);
+ result.append(QString::fromLatin1(aParent));
+ }
+ break;
+ }
+ }
+ }
+ if (result.isEmpty()) {
+ const QString parent = fallbackParent(mime);
+ if (!parent.isEmpty())
+ result.append(parent);
+ }
+ return result;
+}
+
+QString QMimeBinaryProvider::resolveAlias(const QString &name)
+{
+ checkCache();
+ const QByteArray input = name.toLatin1();
+ foreach (CacheFile *cacheFile, m_cacheFiles) {
+ const int aliasListOffset = cacheFile->getUint32(PosAliasListOffset);
+ const int numEntries = cacheFile->getUint32(aliasListOffset);
+ int begin = 0;
+ int end = numEntries - 1;
+ while (begin <= end) {
+ const int medium = (begin + end) / 2;
+ const int off = aliasListOffset + 4 + 8 * medium;
+ const int aliasOffset = cacheFile->getUint32(off);
+ const char *alias = cacheFile->getCharStar(aliasOffset);
+ const int cmp = qstrcmp(alias, input);
+ if (cmp < 0) {
+ begin = medium + 1;
+ } else if (cmp > 0) {
+ end = medium - 1;
+ } else {
+ const int mimeOffset = cacheFile->getUint32(off + 4);
+ const char *mimeType = cacheFile->getCharStar(mimeOffset);
+ return QLatin1String(mimeType);
+ }
+ }
+ }
+
+ return name;
+}
+
+void QMimeBinaryProvider::loadMimeTypeList()
+{
+ if (!m_mimetypeListLoaded) {
+ m_mimetypeListLoaded = true;
+ m_mimetypeNames.clear();
+ // Unfortunately mime.cache doesn't have a full list of all mimetypes.
+ // So we have to parse the plain-text files called "types".
+ const QStringList typesFilenames = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime/types"));
+ foreach (const QString &typeFilename, typesFilenames) {
+ QFile file(typeFilename);
+ if (file.open(QIODevice::ReadOnly)) {
+ while (!file.atEnd()) {
+ QByteArray line = file.readLine();
+ line.chop(1);
+ m_mimetypeNames.insert(QString::fromLatin1(line.constData(), line.size()));
+ }
+ }
+ }
+ }
+}
+
+QList<QMimeType> QMimeBinaryProvider::allMimeTypes()
+{
+ QList<QMimeType> result;
+ loadMimeTypeList();
+
+ for (QSet<QString>::const_iterator it = m_mimetypeNames.constBegin();
+ it != m_mimetypeNames.constEnd(); ++it)
+ result.append(mimeTypeForNameUnchecked(*it));
+
+ return result;
+}
+
+void QMimeBinaryProvider::loadMimeTypePrivate(QMimeTypePrivate &data)
+{
+ // load comment and globPatterns
+
+ const QString file = data.name + QLatin1String(".xml");
+ const QStringList mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QString::fromLatin1("mime/") + file);
+ if (mimeFiles.isEmpty()) {
+ // TODO: ask Thiago about this
+ qWarning() << "No file found for" << file << ", even though the file appeared in a directory listing.";
+ qWarning() << "Either it was just removed, or the directory doesn't have executable permission...";
+ qWarning() << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime"), QStandardPaths::LocateDirectory);
+ return;
+ }
+
+ QString comment;
+ QString mainPattern;
+ const QString preferredLanguage = QLocale::system().name();
+
+ QListIterator<QString> mimeFilesIter(mimeFiles);
+ mimeFilesIter.toBack();
+ while (mimeFilesIter.hasPrevious()) { // global first, then local.
+ const QString fullPath = mimeFilesIter.previous();
+ QFile qfile(fullPath);
+ if (!qfile.open(QFile::ReadOnly))
+ continue;
+
+ QXmlStreamReader xml(&qfile);
+ if (xml.readNextStartElement()) {
+ if (xml.name() != QLatin1String("mime-type")) {
+ continue;
+ }
+ const QString name = xml.attributes().value(QLatin1String("type")).toString();
+ if (name.isEmpty())
+ continue;
+ if (name != data.name) {
+ qWarning() << "Got name" << name << "in file" << file << "expected" << data.name;
+ }
+
+ while (xml.readNextStartElement()) {
+ const QStringRef tag = xml.name();
+ if (tag == QLatin1String("comment")) {
+ QString lang = xml.attributes().value(QLatin1String("xml:lang")).toString();
+ const QString text = xml.readElementText();
+ if (lang.isEmpty()) {
+ lang = QLatin1String("en_US");
+ }
+ data.localeComments.insert(lang, text);
+ continue; // we called readElementText, so we're at the EndElement already.
+ } else if (tag == QLatin1String("icon")) { // as written out by shared-mime-info >= 0.40
+ data.iconName = xml.attributes().value(QLatin1String("name")).toString();
+ } else if (tag == QLatin1String("glob-deleteall")) { // as written out by shared-mime-info >= 0.70
+ data.globPatterns.clear();
+ } else if (tag == QLatin1String("glob")) { // as written out by shared-mime-info >= 0.70
+ const QString pattern = xml.attributes().value(QLatin1String("pattern")).toString();
+ if (mainPattern.isEmpty() && pattern.startsWith(QLatin1Char('*'))) {
+ mainPattern = pattern;
+ }
+ if (!data.globPatterns.contains(pattern))
+ data.globPatterns.append(pattern);
+ }
+ xml.skipCurrentElement();
+ }
+ Q_ASSERT(xml.name() == QLatin1String("mime-type"));
+ }
+ }
+
+ // Let's assume that shared-mime-info is at least version 0.70
+ // Otherwise we would need 1) a version check, and 2) code for parsing patterns from the globs file.
+#if 1
+ if (!mainPattern.isEmpty() && data.globPatterns.first() != mainPattern) {
+ // ensure it's first in the list of patterns
+ data.globPatterns.removeAll(mainPattern);
+ data.globPatterns.prepend(mainPattern);
+ }
+#else
+ const bool globsInXml = sharedMimeInfoVersion() >= QT_VERSION_CHECK(0, 70, 0);
+ if (globsInXml) {
+ if (!mainPattern.isEmpty() && data.globPatterns.first() != mainPattern) {
+ // ensure it's first in the list of patterns
+ data.globPatterns.removeAll(mainPattern);
+ data.globPatterns.prepend(mainPattern);
+ }
+ } else {
+ // Fallback: get the patterns from the globs file
+ // TODO: This would be the only way to support shared-mime-info < 0.70
+ // But is this really worth the effort?
+ }
+#endif
+}
+
+// Binary search in the icons or generic-icons list
+QString QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int posListOffset, const QByteArray &inputMime)
+{
+ const int iconsListOffset = cacheFile->getUint32(posListOffset);
+ const int numIcons = cacheFile->getUint32(iconsListOffset);
+ int begin = 0;
+ int end = numIcons - 1;
+ while (begin <= end) {
+ const int medium = (begin + end) / 2;
+ const int off = iconsListOffset + 4 + 8 * medium;
+ const int mimeOffset = cacheFile->getUint32(off);
+ const char *mime = cacheFile->getCharStar(mimeOffset);
+ const int cmp = qstrcmp(mime, inputMime);
+ if (cmp < 0)
+ begin = medium + 1;
+ else if (cmp > 0)
+ end = medium - 1;
+ else {
+ const int iconOffset = cacheFile->getUint32(off + 4);
+ return QLatin1String(cacheFile->getCharStar(iconOffset));
+ }
+ }
+ return QString();
+}
+
+void QMimeBinaryProvider::loadIcon(QMimeTypePrivate &data)
+{
+ checkCache();
+ const QByteArray inputMime = data.name.toLatin1();
+ foreach (CacheFile *cacheFile, m_cacheFiles) {
+ const QString icon = iconForMime(cacheFile, PosIconsListOffset, inputMime);
+ if (!icon.isEmpty()) {
+ data.iconName = icon;
+ return;
+ }
+ }
+}
+
+void QMimeBinaryProvider::loadGenericIcon(QMimeTypePrivate &data)
+{
+ checkCache();
+ const QByteArray inputMime = data.name.toLatin1();
+ foreach (CacheFile *cacheFile, m_cacheFiles) {
+ const QString icon = iconForMime(cacheFile, PosGenericIconsListOffset, inputMime);
+ if (!icon.isEmpty()) {
+ data.genericIconName = icon;
+ return;
+ }
+ }
+}
+
+////
+
+QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db)
+ : QMimeProviderBase(db), m_loaded(false)
+{
+}
+
+bool QMimeXMLProvider::isValid()
+{
+ return true;
+}
+
+QMimeType QMimeXMLProvider::mimeTypeForName(const QString &name)
+{
+ ensureLoaded();
+
+ return m_nameMimeTypeMap.value(name);
+}
+
+QStringList QMimeXMLProvider::findByFileName(const QString &fileName, QString *foundSuffix)
+{
+ ensureLoaded();
+
+ const QStringList matchingMimeTypes = m_mimeTypeGlobs.matchingGlobs(fileName, foundSuffix);
+ return matchingMimeTypes;
+}
+
+QMimeType QMimeXMLProvider::findByMagic(const QByteArray &data, int *accuracyPtr)
+{
+ ensureLoaded();
+
+ QString candidate;
+
+ foreach (const QMimeMagicRuleMatcher &matcher, m_magicMatchers) {
+ if (matcher.matches(data)) {
+ const int priority = matcher.priority();
+ if (priority > *accuracyPtr) {
+ *accuracyPtr = priority;
+ candidate = matcher.mimetype();
+ }
+ }
+ }
+ return mimeTypeForName(candidate);
+}
+
+void QMimeXMLProvider::ensureLoaded()
+{
+ if (!m_loaded || shouldCheck()) {
+ bool fdoXmlFound = false;
+ QStringList allFiles;
+
+ const QStringList packageDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime/packages"), QStandardPaths::LocateDirectory);
+ //qDebug() << "packageDirs=" << packageDirs;
+ foreach (const QString &packageDir, packageDirs) {
+ QDir dir(packageDir);
+ const QStringList files = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
+ //qDebug() << static_cast<const void *>(this) << Q_FUNC_INFO << packageDir << files;
+ if (!fdoXmlFound)
+ fdoXmlFound = files.contains(QLatin1String("freedesktop.org.xml"));
+ QStringList::const_iterator endIt(files.constEnd());
+ for (QStringList::const_iterator it(files.constBegin()); it != endIt; ++it) {
+ allFiles.append(packageDir + QLatin1Char('/') + *it);
+ }
+ }
+
+ if (!fdoXmlFound) {
+ // We could instead install the file as part of installing Qt?
+ allFiles.prepend(QLatin1String(":/qt-project.org/qmime/freedesktop.org.xml"));
+ }
+
+ if (m_allFiles == allFiles)
+ return;
+ m_allFiles = allFiles;
+
+ m_nameMimeTypeMap.clear();
+ m_aliases.clear();
+ m_parents.clear();
+ m_mimeTypeGlobs.clear();
+ m_magicMatchers.clear();
+
+ //qDebug() << "Loading" << m_allFiles;
+
+ foreach (const QString &file, allFiles)
+ load(file);
+ }
+}
+
+void QMimeXMLProvider::load(const QString &fileName)
+{
+ QString errorMessage;
+ if (!load(fileName, &errorMessage))
+ qWarning("QMimeDatabase: Error loading %s\n%s", qPrintable(fileName), qPrintable(errorMessage));
+}
+
+bool QMimeXMLProvider::load(const QString &fileName, QString *errorMessage)
+{
+ m_loaded = true;
+
+ QFile file(fileName);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ if (errorMessage)
+ *errorMessage = QString::fromLatin1("Cannot open %1: %2").arg(fileName, file.errorString());
+ return false;
+ }
+
+ if (errorMessage)
+ errorMessage->clear();
+
+ QMimeTypeParser parser(*this);
+ return parser.parse(&file, fileName, errorMessage);
+}
+
+void QMimeXMLProvider::addGlobPattern(const QMimeGlobPattern &glob)
+{
+ m_mimeTypeGlobs.addGlob(glob);
+}
+
+void QMimeXMLProvider::addMimeType(const QMimeType &mt)
+{
+ m_nameMimeTypeMap.insert(mt.name(), mt);
+}
+
+QStringList QMimeXMLProvider::parents(const QString &mime)
+{
+ ensureLoaded();
+ QStringList result = m_parents.value(mime);
+ if (result.isEmpty()) {
+ const QString parent = fallbackParent(mime);
+ if (!parent.isEmpty())
+ result.append(parent);
+ }
+ return result;
+}
+
+void QMimeXMLProvider::addParent(const QString &child, const QString &parent)
+{
+ m_parents[child].append(parent);
+}
+
+QString QMimeXMLProvider::resolveAlias(const QString &name)
+{
+ ensureLoaded();
+ return m_aliases.value(name, name);
+}
+
+void QMimeXMLProvider::addAlias(const QString &alias, const QString &name)
+{
+ m_aliases.insert(alias, name);
+}
+
+QList<QMimeType> QMimeXMLProvider::allMimeTypes()
+{
+ ensureLoaded();
+ return m_nameMimeTypeMap.values();
+}
+
+void QMimeXMLProvider::addMagicMatcher(const QMimeMagicRuleMatcher &matcher)
+{
+ m_magicMatchers.append(matcher);
+}
+
+QT_END_NAMESPACE