| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Copyright (C) 2015 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com> |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtCore module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $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> |
| |
| static void initResources() |
| { |
| #if QT_CONFIG(mimetype_database) |
| Q_INIT_RESOURCE(mimetypes); |
| #endif |
| } |
| |
| QT_BEGIN_NAMESPACE |
| |
| QMimeProviderBase::QMimeProviderBase(QMimeDatabasePrivate *db, const QString &directory) |
| : m_db(db), m_directory(directory) |
| { |
| } |
| |
| |
| QMimeBinaryProvider::QMimeBinaryProvider(QMimeDatabasePrivate *db, const QString &directory) |
| : QMimeProviderBase(db, directory), m_mimetypeListLoaded(false) |
| { |
| ensureLoaded(); |
| } |
| |
| 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() |
| { |
| m_valid = false; |
| if (file.isOpen()) { |
| file.close(); |
| } |
| data = 0; |
| return load(); |
| } |
| |
| QMimeBinaryProvider::~QMimeBinaryProvider() |
| { |
| delete m_cacheFile; |
| } |
| |
| bool QMimeBinaryProvider::isValid() |
| { |
| return m_cacheFile != nullptr; |
| } |
| |
| // 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::checkCacheChanged() |
| { |
| QFileInfo fileInfo(m_cacheFile->file); |
| if (fileInfo.lastModified() > m_cacheFile->m_mtime) { |
| // Deletion can't happen by just running update-mime-database. |
| // But the user could use rm -rf :-) |
| m_cacheFile->reload(); // will mark itself as invalid on failure |
| return true; |
| } |
| return false; |
| } |
| |
| void QMimeBinaryProvider::ensureLoaded() |
| { |
| if (!m_cacheFile) { |
| const QString cacheFileName = m_directory + QLatin1String("/mime.cache"); |
| m_cacheFile = new CacheFile(cacheFileName); |
| m_mimetypeListLoaded = false; |
| } else { |
| if (checkCacheChanged()) |
| m_mimetypeListLoaded = false; |
| else |
| return; // nothing to do |
| } |
| if (!m_cacheFile->isValid()) { // verify existence and version |
| delete m_cacheFile; |
| m_cacheFile = nullptr; |
| } |
| } |
| |
| static QMimeType mimeTypeForNameUnchecked(const QString &name) |
| { |
| QMimeTypePrivate data; |
| data.name = name; |
| data.fromCache = true; |
| // 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) |
| { |
| if (!m_mimetypeListLoaded) |
| loadMimeTypeList(); |
| if (!m_mimetypeNames.contains(name)) |
| return QMimeType(); // unknown mimetype |
| return mimeTypeForNameUnchecked(name); |
| } |
| |
| void QMimeBinaryProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result) |
| { |
| if (fileName.isEmpty()) |
| return; |
| Q_ASSERT(m_cacheFile); |
| const QString lowerFileName = fileName.toLower(); |
| // Check literals (e.g. "Makefile") |
| matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosLiteralListOffset), fileName); |
| // Check complex globs (e.g. "callgrind.out[0-9]*") |
| matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosGlobListOffset), fileName); |
| // Check the very common *.txt cases with the suffix tree |
| const int reverseSuffixTreeOffset = m_cacheFile->getUint32(PosReverseSuffixTreeOffset); |
| const int numRoots = m_cacheFile->getUint32(reverseSuffixTreeOffset); |
| const int firstRootOffset = m_cacheFile->getUint32(reverseSuffixTreeOffset + 4); |
| matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, lowerFileName, lowerFileName.length() - 1, false); |
| if (result.m_matchingMimeTypes.isEmpty()) |
| matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, fileName, fileName.length() - 1, true); |
| } |
| |
| 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.midRef(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; |
| } |
| |
| void QMimeBinaryProvider::findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate) |
| { |
| const int magicListOffset = m_cacheFile->getUint32(PosMagicListOffset); |
| const int numMatches = m_cacheFile->getUint32(magicListOffset); |
| //const int maxExtent = cacheFile->getUint32(magicListOffset + 4); |
| const int firstMatchOffset = m_cacheFile->getUint32(magicListOffset + 8); |
| |
| for (int i = 0; i < numMatches; ++i) { |
| const int off = firstMatchOffset + i * 16; |
| const int numMatchlets = m_cacheFile->getUint32(off + 8); |
| const int firstMatchletOffset = m_cacheFile->getUint32(off + 12); |
| if (matchMagicRule(m_cacheFile, numMatchlets, firstMatchletOffset, data)) { |
| const int mimeTypeOffset = m_cacheFile->getUint32(off + 4); |
| const char *mimeType = m_cacheFile->getCharStar(mimeTypeOffset); |
| *accuracyPtr = m_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?) |
| candidate = mimeTypeForNameUnchecked(QLatin1String(mimeType)); |
| return; |
| } |
| } |
| } |
| |
| void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result) |
| { |
| const QByteArray mimeStr = mime.toLatin1(); |
| const int parentListOffset = m_cacheFile->getUint32(PosParentListOffset); |
| const int numEntries = m_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 = m_cacheFile->getUint32(off); |
| const char *aMime = m_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 = m_cacheFile->getUint32(off + 4); |
| const int numParents = m_cacheFile->getUint32(parentsOffset); |
| for (int i = 0; i < numParents; ++i) { |
| const int parentOffset = m_cacheFile->getUint32(parentsOffset + 4 + 4 * i); |
| const char *aParent = m_cacheFile->getCharStar(parentOffset); |
| const QString strParent = QString::fromLatin1(aParent); |
| if (!result.contains(strParent)) |
| result.append(strParent); |
| } |
| break; |
| } |
| } |
| } |
| |
| QString QMimeBinaryProvider::resolveAlias(const QString &name) |
| { |
| const QByteArray input = name.toLatin1(); |
| const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset); |
| const int numEntries = m_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 = m_cacheFile->getUint32(off); |
| const char *alias = m_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 = m_cacheFile->getUint32(off + 4); |
| const char *mimeType = m_cacheFile->getCharStar(mimeOffset); |
| return QLatin1String(mimeType); |
| } |
| } |
| return QString(); |
| } |
| |
| void QMimeBinaryProvider::addAliases(const QString &name, QStringList &result) |
| { |
| const QByteArray input = name.toLatin1(); |
| const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset); |
| const int numEntries = m_cacheFile->getUint32(aliasListOffset); |
| for (int pos = 0; pos < numEntries; ++pos) { |
| const int off = aliasListOffset + 4 + 8 * pos; |
| const int mimeOffset = m_cacheFile->getUint32(off + 4); |
| const char *mimeType = m_cacheFile->getCharStar(mimeOffset); |
| |
| if (input == mimeType) { |
| const int aliasOffset = m_cacheFile->getUint32(off); |
| const char *alias = m_cacheFile->getCharStar(aliasOffset); |
| const QString strAlias = QString::fromLatin1(alias); |
| if (!result.contains(strAlias)) |
| result.append(strAlias); |
| } |
| } |
| } |
| |
| 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". |
| QFile file(m_directory + QStringLiteral("/types")); |
| if (file.open(QIODevice::ReadOnly)) { |
| QTextStream stream(&file); |
| stream.setCodec("ISO 8859-1"); |
| QString line; |
| while (stream.readLineInto(&line)) |
| m_mimetypeNames.insert(line); |
| } |
| } |
| } |
| |
| void QMimeBinaryProvider::addAllMimeTypes(QList<QMimeType> &result) |
| { |
| loadMimeTypeList(); |
| if (result.isEmpty()) { |
| result.reserve(m_mimetypeNames.count()); |
| for (const QString &name : qAsConst(m_mimetypeNames)) |
| result.append(mimeTypeForNameUnchecked(name)); |
| } else { |
| for (const QString &name : qAsConst(m_mimetypeNames)) |
| if (std::find_if(result.constBegin(), result.constEnd(), [name](const QMimeType &mime) -> bool { return mime.name() == name; }) |
| == result.constEnd()) |
| result.append(mimeTypeForNameUnchecked(name)); |
| } |
| } |
| |
| void QMimeBinaryProvider::loadMimeTypePrivate(QMimeTypePrivate &data) |
| { |
| #ifdef QT_NO_XMLSTREAMREADER |
| Q_UNUSED(data); |
| qWarning("Cannot load mime type since QXmlStreamReader is not available."); |
| return; |
| #else |
| if (data.loaded) |
| return; |
| data.loaded = true; |
| // load comment and globPatterns |
| |
| const QString file = data.name + QLatin1String(".xml"); |
| // shared-mime-info since 1.3 lowercases the xml files |
| QStringList mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime/") + file.toLower()); |
| if (mimeFiles.isEmpty()) |
| mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime/") + file); // pre-1.3 |
| if (mimeFiles.isEmpty()) { |
| qWarning() << "No file found for" << file << ", even though update-mime-info said it would exist.\n" |
| "Either it was just removed, or the directory doesn't have executable permission..." |
| << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime"), QStandardPaths::LocateDirectory); |
| return; |
| } |
| |
| QString mainPattern; |
| |
| for (QStringList::const_reverse_iterator it = mimeFiles.crbegin(), end = mimeFiles.crend(); it != end; ++it) { // global first, then local. |
| QFile qfile(*it); |
| if (!qfile.open(QFile::ReadOnly)) |
| continue; |
| |
| QXmlStreamReader xml(&qfile); |
| if (xml.readNextStartElement()) { |
| if (xml.name() != QLatin1String("mime-type")) { |
| continue; |
| } |
| const QStringRef name = xml.attributes().value(QLatin1String("type")); |
| if (name.isEmpty()) |
| continue; |
| if (name.compare(data.name, Qt::CaseInsensitive)) |
| 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("default"); // no locale attribute provided, treat it as default. |
| } |
| 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.isEmpty() || data.globPatterns.constFirst() != 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.constFirst() != 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 |
| #endif //QT_NO_XMLSTREAMREADER |
| } |
| |
| // Binary search in the icons or generic-icons list |
| QLatin1String 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 QLatin1String(); |
| } |
| |
| void QMimeBinaryProvider::loadIcon(QMimeTypePrivate &data) |
| { |
| const QByteArray inputMime = data.name.toLatin1(); |
| const QLatin1String icon = iconForMime(m_cacheFile, PosIconsListOffset, inputMime); |
| if (!icon.isEmpty()) { |
| data.iconName = icon; |
| } |
| } |
| |
| void QMimeBinaryProvider::loadGenericIcon(QMimeTypePrivate &data) |
| { |
| const QByteArray inputMime = data.name.toLatin1(); |
| const QLatin1String icon = iconForMime(m_cacheFile, PosGenericIconsListOffset, inputMime); |
| if (!icon.isEmpty()) { |
| data.genericIconName = icon; |
| } |
| } |
| |
| //// |
| |
| QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, const QString &directory) |
| : QMimeProviderBase(db, directory) |
| { |
| initResources(); |
| ensureLoaded(); |
| } |
| |
| QMimeXMLProvider::~QMimeXMLProvider() |
| { |
| } |
| |
| bool QMimeXMLProvider::isValid() |
| { |
| // If you change this method, adjust the logic in QMimeDatabasePrivate::loadProviders, |
| // which assumes isValid==false is only possible in QMimeBinaryProvider. |
| return true; |
| } |
| |
| QMimeType QMimeXMLProvider::mimeTypeForName(const QString &name) |
| { |
| return m_nameMimeTypeMap.value(name); |
| } |
| |
| void QMimeXMLProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result) |
| { |
| m_mimeTypeGlobs.matchingGlobs(fileName, result); |
| } |
| |
| void QMimeXMLProvider::findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate) |
| { |
| QString candidateName; |
| bool foundOne = false; |
| for (const QMimeMagicRuleMatcher &matcher : qAsConst(m_magicMatchers)) { |
| if (matcher.matches(data)) { |
| const int priority = matcher.priority(); |
| if (priority > *accuracyPtr) { |
| *accuracyPtr = priority; |
| candidateName = matcher.mimetype(); |
| foundOne = true; |
| } |
| } |
| } |
| if (foundOne) |
| candidate = mimeTypeForName(candidateName); |
| } |
| |
| void QMimeXMLProvider::ensureLoaded() |
| { |
| QStringList allFiles; |
| const QString packageDir = m_directory + QStringLiteral("/packages"); |
| QDir dir(packageDir); |
| const QStringList files = dir.entryList(QDir::Files | QDir::NoDotAndDotDot); |
| allFiles.reserve(files.count()); |
| for (const QString &xmlFile : files) |
| allFiles.append(packageDir + QLatin1Char('/') + xmlFile); |
| |
| 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; |
| |
| for (const QString &file : qAsConst(allFiles)) |
| load(file); |
| } |
| |
| void QMimeXMLProvider::load(const QString &fileName) |
| { |
| QString errorMessage; |
| if (!load(fileName, &errorMessage)) |
| qWarning("QMimeDatabase: Error loading %ls\n%ls", qUtf16Printable(fileName), qUtf16Printable(errorMessage)); |
| } |
| |
| bool QMimeXMLProvider::load(const QString &fileName, QString *errorMessage) |
| { |
| QFile file(fileName); |
| if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { |
| if (errorMessage) |
| *errorMessage = QLatin1String("Cannot open ") + fileName + QLatin1String(": ") + 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) |
| { |
| Q_ASSERT(!mt.d.data()->fromCache); |
| m_nameMimeTypeMap.insert(mt.name(), mt); |
| } |
| |
| void QMimeXMLProvider::addParents(const QString &mime, QStringList &result) |
| { |
| for (const QString &parent : m_parents.value(mime)) { |
| if (!result.contains(parent)) |
| result.append(parent); |
| } |
| } |
| |
| void QMimeXMLProvider::addParent(const QString &child, const QString &parent) |
| { |
| m_parents[child].append(parent); |
| } |
| |
| void QMimeXMLProvider::addAliases(const QString &name, QStringList &result) |
| { |
| // Iterate through the whole hash. This method is rarely used. |
| for (auto it = m_aliases.constBegin(), end = m_aliases.constEnd() ; it != end ; ++it) { |
| if (it.value() == name) { |
| if (!result.contains(it.key())) |
| result.append(it.key()); |
| } |
| } |
| |
| } |
| |
| QString QMimeXMLProvider::resolveAlias(const QString &name) |
| { |
| return m_aliases.value(name); |
| } |
| |
| void QMimeXMLProvider::addAlias(const QString &alias, const QString &name) |
| { |
| m_aliases.insert(alias, name); |
| } |
| |
| void QMimeXMLProvider::addAllMimeTypes(QList<QMimeType> &result) |
| { |
| if (result.isEmpty()) { // fast path |
| result = m_nameMimeTypeMap.values(); |
| } else { |
| for (auto it = m_nameMimeTypeMap.constBegin(), end = m_nameMimeTypeMap.constEnd() ; it != end ; ++it) { |
| const QString newMime = it.key(); |
| if (std::find_if(result.constBegin(), result.constEnd(), [newMime](const QMimeType &mime) -> bool { return mime.name() == newMime; }) |
| == result.constEnd()) |
| result.append(it.value()); |
| } |
| } |
| } |
| |
| void QMimeXMLProvider::addMagicMatcher(const QMimeMagicRuleMatcher &matcher) |
| { |
| m_magicMatchers.append(matcher); |
| } |
| |
| QT_END_NAMESPACE |