2 ******************************************************************************
4 * @file mimedatabase.cpp
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * Parts by Nokia Corporation (qt-info@nokia.com) Copyright (C) 2009.
7 * @addtogroup GCSPlugins GCS Plugins
9 * @addtogroup CorePlugin Core Plugin
11 * @brief The Core GCS plugin
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include "mimedatabase.h"
31 #include <utils/qtcassert.h>
33 #include <QtCore/QByteArray>
34 #include <QtCore/QCoreApplication>
35 #include <QtCore/QDebug>
36 #include <QtCore/QFile>
37 #include <QtCore/QFileInfo>
38 #include <QtCore/QLocale>
39 #include <QtCore/QMap>
40 #include <QtCore/QMultiHash>
41 #include <QtCore/QRegExp>
42 #include <QtCore/QSharedData>
43 #include <QtCore/QSharedPointer>
44 #include <QtCore/QStringList>
45 #include <QtCore/QTextStream>
47 #include <QtCore/QXmlStreamReader>
49 enum { debugMimeDB
= 0 };
51 // XML tags in mime files
52 static const char *mimeInfoTagC
= "mime-info";
53 static const char *mimeTypeTagC
= "mime-type";
54 static const char *mimeTypeAttributeC
= "type";
55 static const char *subClassTagC
= "sub-class-of";
56 static const char *commentTagC
= "comment";
57 static const char *globTagC
= "glob";
58 static const char *aliasTagC
= "alias";
59 static const char *patternAttributeC
= "pattern";
60 static const char *localeAttributeC
= "xml:lang";
62 static const char *magicTagC
= "magic";
63 static const char *priorityAttributeC
= "priority";
64 static const char *matchTagC
= "match";
65 static const char *matchValueAttributeC
= "value";
66 static const char *matchTypeAttributeC
= "type";
67 static const char *matchStringTypeValueC
= "string";
68 static const char *matchOffsetAttributeC
= "offset";
71 static const char *textTypeC
= "text/plain";
72 static const char *binaryTypeC
= "application/octet-stream";
74 // UTF16 byte order marks
75 static const char bigEndianByteOrderMarkC
[] = "\xFE\xFF";
76 static const char littleEndianByteOrderMarkC
[] = "\xFF\xFE";
78 // Fallback priorities, must be low.
79 enum { BinaryMatchPriority
= 1, TextMatchPriority
= 2 };
81 /* Parse sth like (<mime-info> being optional):
83 ?xml version="1.0" encoding="UTF-8"?>
84 <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
85 <!-- Mime types must match the desktop file associations -->
86 <mime-type type="application/vnd.nokia.qt.qmakeprofile">
87 <comment xml:lang="en">Qt QMake Profile</comment>
88 <glob pattern="*.pro"/>
96 // FileMatchContext: Passed on to the mimetypes from the database
97 // when looking for a file match. It exists to enable reading the file
98 // contents "on demand" (as opposed to each mime type trying to open
99 // and read while checking).
101 class FileMatchContext
{
102 Q_DISABLE_COPY(FileMatchContext
)
104 // Max data to be read from a file
105 enum { MaxData
= 2048 };
107 explicit FileMatchContext(const QFileInfo
&fi
);
109 inline QString
fileName() const
113 // Return (cached) first MaxData bytes of file
118 // File cannot be read/does not exist
125 const QFileInfo m_fileInfo
;
126 const QString m_fileName
;
131 FileMatchContext::FileMatchContext(const QFileInfo
&fi
) :
133 m_fileName(fi
.fileName()),
134 m_state(fi
.isFile() && fi
.isReadable() && fi
.size() > 0 ? DataNotRead
: NoDataAvailable
)
137 QByteArray
FileMatchContext::data()
139 // Do we need to read?
140 if (m_state
== DataNotRead
) {
141 const QString fullName
= m_fileInfo
.absoluteFilePath();
142 QFile
file(fullName
);
143 if (file
.open(QIODevice::ReadOnly
)) {
144 m_data
= file
.read(MaxData
);
147 qWarning("%s failed to open %s: %s\n", Q_FUNC_INFO
, fullName
.toUtf8().constData(), file
.errorString().toUtf8().constData());
148 m_state
= NoDataAvailable
;
154 // The binary fallback matcher for "application/octet-stream".
155 class BinaryMatcher
: public IMagicMatcher
{
156 Q_DISABLE_COPY(BinaryMatcher
)
159 virtual bool matches(const QByteArray
& /*data*/) const
163 virtual int priority() const
165 return BinaryMatchPriority
;
169 // A heuristic text file matcher: If the data do not contain any character
170 // below tab (9), detect as text.
171 class HeuristicTextMagicMatcher
: public IMagicMatcher
{
172 Q_DISABLE_COPY(HeuristicTextMagicMatcher
)
174 HeuristicTextMagicMatcher() {}
175 virtual bool matches(const QByteArray
&data
) const;
176 virtual int priority() const
178 return TextMatchPriority
;
181 static bool isTextFile(const QByteArray
&data
);
184 bool HeuristicTextMagicMatcher::isTextFile(const QByteArray
&data
)
186 const int size
= data
.size();
188 for (int i
= 0; i
< size
; i
++) {
189 const char c
= data
.at(i
);
190 if (c
>= 0x01 && c
< 0x09) { // Sure-fire binary
193 if (c
== 0) { // Check for UTF16
194 return data
.startsWith(bigEndianByteOrderMarkC
) || data
.startsWith(littleEndianByteOrderMarkC
);
200 bool HeuristicTextMagicMatcher::matches(const QByteArray
&data
) const
202 const bool rc
= isTextFile(data
);
205 qDebug() << Q_FUNC_INFO
<< " on " << data
.size() << " returns " << rc
;
209 } // namespace Internal
212 MagicRule::MagicRule(const QByteArray
&pattern
, int startPos
, int endPos
) :
214 m_startPos(startPos
),
218 bool MagicRule::matches(const QByteArray
&data
) const
221 const int dataSize
= data
.size();
223 if ((m_startPos
+ m_pattern
.size()) >= dataSize
) {
226 // Most common: some string at position 0:
227 if (m_startPos
== 0 && m_startPos
== m_endPos
) {
228 return data
.startsWith(m_pattern
);
231 const int index
= data
.indexOf(m_pattern
, m_startPos
);
232 return index
!= -1 && index
< m_endPos
;
235 MagicRule
*MagicRule::createStringRule(const QString
&c
, int startPos
, int endPos
)
237 return new MagicRule(c
.toUtf8(), startPos
, endPos
);
241 MagicRuleMatcher::MagicRuleMatcher() :
245 void MagicRuleMatcher::add(const MagicRuleSharedPointer
&rule
)
247 m_list
.push_back(rule
);
250 bool MagicRuleMatcher::matches(const QByteArray
&data
) const
252 const MagicRuleList::const_iterator cend
= m_list
.constEnd();
254 for (MagicRuleList::const_iterator it
= m_list
.constBegin(); it
!= cend
; ++it
) {
255 if ((*it
)->matches(data
)) {
262 int MagicRuleMatcher::priority() const
267 void MagicRuleMatcher::setPriority(int p
)
272 // ---------- MimeTypeData
273 class MimeTypeData
: public QSharedData
{
275 typedef QHash
<QString
, QString
> LocaleHash
;
277 void debug(QTextStream
&str
, int indent
= 0) const;
282 LocaleHash localeComments
;
284 QList
<QRegExp
> globPatterns
;
285 QStringList subClassesOf
;
286 QString preferredSuffix
;
287 QStringList suffixes
;
289 typedef QSharedPointer
<IMagicMatcher
> IMagicMatcherSharedPointer
;
290 typedef QList
<IMagicMatcherSharedPointer
> IMagicMatcherList
;
291 IMagicMatcherList magicMatchers
;
294 void MimeTypeData::clear()
299 globPatterns
.clear();
300 subClassesOf
.clear();
301 preferredSuffix
.clear();
303 magicMatchers
.clear();
306 void MimeTypeData::debug(QTextStream
&str
, int indent
) const
308 const QString indentS
= QString(indent
, QLatin1Char(' '));
309 const QString comma
= QString(1, QLatin1Char(','));
311 str
<< indentS
<< "Type: " << type
;
312 if (!aliases
.empty()) {
313 str
<< " Aliases: " << aliases
.join(comma
);
315 str
<< ", magic: " << magicMatchers
.size() << '\n';
316 str
<< indentS
<< "Comment: " << comment
<< '\n';
317 if (!subClassesOf
.empty()) {
318 str
<< indentS
<< "SubClassesOf: " << subClassesOf
.join(comma
) << '\n';
320 if (!globPatterns
.empty()) {
321 str
<< indentS
<< "Glob: ";
322 foreach(const QRegExp
&r
, globPatterns
)
323 str
<< r
.pattern() << ' ';
325 if (!suffixes
.empty()) {
326 str
<< indentS
<< "Suffixes: " << suffixes
.join(comma
)
327 << " preferred: " << preferredSuffix
<< '\n';
333 // ---------------- MimeType
334 MimeType::MimeType() :
335 m_d(new MimeTypeData
)
338 MimeType::MimeType(const MimeType
&rhs
) :
342 MimeType
&MimeType::operator=(const MimeType
&rhs
)
350 MimeType::MimeType(const MimeTypeData
&d
) :
351 m_d(new MimeTypeData(d
))
354 MimeType::~MimeType()
357 void MimeType::clear()
362 bool MimeType::isNull() const
364 return m_d
->type
.isEmpty();
367 MimeType::operator bool() const
372 bool MimeType::isTopLevel() const
374 return m_d
->subClassesOf
.empty();
377 QString
MimeType::type() const
382 void MimeType::setType(const QString
&type
)
387 QString
MimeType::comment() const
392 void MimeType::setComment(const QString
&comment
)
394 m_d
->comment
= comment
;
397 // Return "en", "de", etc. derived from "en_US", de_DE".
398 static inline QString
systemLanguage()
400 QString name
= QLocale::system().name();
401 const int underScorePos
= name
.indexOf(QLatin1Char('_'));
403 if (underScorePos
!= -1) {
404 name
.truncate(underScorePos
);
409 QString
MimeType::localeComment(const QString
&localeArg
) const
411 const QString locale
= localeArg
.isEmpty() ? systemLanguage() : localeArg
;
412 const MimeTypeData::LocaleHash::const_iterator it
= m_d
->localeComments
.constFind(locale
);
414 if (it
== m_d
->localeComments
.constEnd()) {
420 void MimeType::setLocaleComment(const QString
&locale
, const QString
&comment
)
422 m_d
->localeComments
[locale
] = comment
;
425 QStringList
MimeType::aliases() const
430 void MimeType::setAliases(const QStringList
&a
)
435 QList
<QRegExp
> MimeType::globPatterns() const
437 return m_d
->globPatterns
;
440 void MimeType::setGlobPatterns(const QList
<QRegExp
> &g
)
442 m_d
->globPatterns
= g
;
445 QStringList
MimeType::subClassesOf() const
447 return m_d
->subClassesOf
;
450 void MimeType::setSubClassesOf(const QStringList
&s
)
452 m_d
->subClassesOf
= s
;
455 QString
MimeType::preferredSuffix() const
457 return m_d
->preferredSuffix
;
460 bool MimeType::setPreferredSuffix(const QString
&s
)
462 if (!m_d
->suffixes
.contains(s
)) {
463 qWarning("%s: Attempt to set preferred suffix to '%s', which is not in the list of suffixes: %s.",
464 m_d
->type
.toUtf8().constData(),
465 s
.toUtf8().constData(),
466 m_d
->suffixes
.join(QLatin1String(",")).toUtf8().constData());
469 m_d
->preferredSuffix
= s
;
473 static QString
formatFilterString(const QString
&description
, const QList
<QRegExp
> &globs
)
477 if (globs
.empty()) { // Binary files
481 QTextStream
str(&rc
);
483 if (!globs
.empty()) {
485 const int size
= globs
.size();
486 for (int i
= 0; i
< size
; i
++) {
490 str
<< globs
.at(i
).pattern();
498 QString
MimeType::filterString() const
500 // @todo: Use localeComment() once the GCS is shipped with translations
501 return formatFilterString(comment(), m_d
->globPatterns
);
504 bool MimeType::matchesType(const QString
&type
) const
506 return m_d
->type
== type
|| m_d
->aliases
.contains(type
);
509 unsigned MimeType::matchesFile(const QFileInfo
&file
) const
511 Internal::FileMatchContext
context(file
);
513 return matchesFile(context
);
516 unsigned MimeType::matchesFile(Internal::FileMatchContext
&c
) const
519 foreach(QRegExp pattern
, m_d
->globPatterns
) {
520 if (pattern
.exactMatch(c
.fileName())) {
521 return GlobMatchPriority
;
525 // Nope, try magic matchers on context data
526 if (m_d
->magicMatchers
.isEmpty()) {
530 const QByteArray data
= c
.data();
531 if (!data
.isEmpty()) {
532 foreach(MimeTypeData::IMagicMatcherSharedPointer matcher
, m_d
->magicMatchers
) {
533 if (matcher
->matches(data
)) {
534 return matcher
->priority();
541 QStringList
MimeType::suffixes() const
543 return m_d
->suffixes
;
546 void MimeType::setSuffixes(const QStringList
&s
)
551 void MimeType::addMagicMatcher(const QSharedPointer
<IMagicMatcher
> &matcher
)
553 m_d
->magicMatchers
.push_back(matcher
);
556 QDebug
operator<<(QDebug d
, const MimeType
&mt
)
569 // MimeDatabase helpers: Generic parser for a sequence of <mime-type>.
570 // Calls abstract handler function process for MimeType it finds.
571 class BaseMimeTypeParser
{
572 Q_DISABLE_COPY(BaseMimeTypeParser
)
574 BaseMimeTypeParser();
575 virtual ~BaseMimeTypeParser() {}
577 bool parse(QIODevice
*dev
, const QString
&fileName
, QString
*errorMessage
);
580 // Overwrite to process the sequence of parsed data
581 virtual bool process(const MimeType
&t
, QString
*errorMessage
) = 0;
583 void addGlobPattern(const QString
&pattern
, MimeTypeData
*d
) const;
585 enum ParseStage
{ ParseBeginning
,
594 ParseOtherMimeTypeSubTag
,
597 static ParseStage
nextStage(ParseStage currentStage
, const QStringRef
&startElement
);
599 const QRegExp m_suffixPattern
;
602 BaseMimeTypeParser::BaseMimeTypeParser() :
603 // RE to match a suffix glob pattern: "*.ext" (and not sth like "Makefile" or
605 m_suffixPattern(QLatin1String("^\\*\\.[\\w+]+$"))
607 QTC_ASSERT(m_suffixPattern
.isValid(), /**/);
610 void BaseMimeTypeParser::addGlobPattern(const QString
&pattern
, MimeTypeData
*d
) const
612 if (pattern
.isEmpty()) {
615 // Collect patterns as a QRegExp list and filter out the plain
616 // suffix ones for our suffix list. Use first one as preferred
617 const QRegExp
wildCard(pattern
, Qt::CaseSensitive
, QRegExp::Wildcard
);
618 if (!wildCard
.isValid()) {
619 qWarning("%s: Invalid wildcard '%s'.",
620 Q_FUNC_INFO
, pattern
.toUtf8().constData());
624 d
->globPatterns
.push_back(wildCard
);
625 if (m_suffixPattern
.exactMatch(pattern
)) {
626 const QString suffix
= pattern
.right(pattern
.size() - 2);
627 d
->suffixes
.push_back(suffix
);
628 if (d
->preferredSuffix
.isEmpty()) {
629 d
->preferredSuffix
= suffix
;
634 BaseMimeTypeParser::ParseStage
BaseMimeTypeParser::nextStage(ParseStage currentStage
, const QStringRef
&startElement
)
636 switch (currentStage
) {
638 if (startElement
== QLatin1String(mimeInfoTagC
)) {
639 return ParseMimeInfo
;
641 if (startElement
== QLatin1String(mimeTypeTagC
)) {
642 return ParseMimeType
;
647 return startElement
== QLatin1String(mimeTypeTagC
) ? ParseMimeType
: ParseError
;
651 case ParseGlobPattern
:
654 case ParseOtherMimeTypeSubTag
:
655 case ParseMagicMatchRule
:
656 if (startElement
== QLatin1String(mimeTypeTagC
)) { // Sequence of <mime-type>
657 return ParseMimeType
;
659 if (startElement
== QLatin1String(commentTagC
)) {
662 if (startElement
== QLatin1String(globTagC
)) {
663 return ParseGlobPattern
;
665 if (startElement
== QLatin1String(subClassTagC
)) {
666 return ParseSubClass
;
668 if (startElement
== QLatin1String(aliasTagC
)) {
671 if (startElement
== QLatin1String(magicTagC
)) {
674 return ParseOtherMimeTypeSubTag
;
677 if (startElement
== QLatin1String(matchTagC
)) {
678 return ParseMagicMatchRule
;
687 // Parse int number from an (attribute) string)
688 static bool parseNumber(const QString
&n
, int *target
, QString
*errorMessage
)
692 *target
= n
.toInt(&ok
);
694 *errorMessage
= QString::fromLatin1("Not a number '%1'.").arg(n
);
700 // Evaluate a magic match rule like
701 // <match value="must be converted with BinHex" type="string" offset="11"/>
702 // <match value="0x9501" type="big16" offset="0:64"/>
703 static bool addMagicMatchRule(const QXmlStreamAttributes
&atts
,
704 MagicRuleMatcher
*ruleMatcher
,
705 QString
*errorMessage
)
707 const QString type
= atts
.value(QLatin1String(matchTypeAttributeC
)).toString();
709 if (type
!= QLatin1String(matchStringTypeValueC
)) {
710 qWarning("%s: match type %s is not supported.", Q_FUNC_INFO
, type
.toUtf8().constData());
713 const QString value
= atts
.value(QLatin1String(matchValueAttributeC
)).toString();
714 if (value
.isEmpty()) {
715 *errorMessage
= QString::fromLatin1("Empty match value detected.");
718 // Parse for offset as "1" or "1:10"
719 int startPos
, endPos
;
720 const QString offsetS
= atts
.value(QLatin1String(matchOffsetAttributeC
)).toString();
721 const int colonIndex
= offsetS
.indexOf(QLatin1Char(':'));
722 const QString startPosS
= colonIndex
== -1 ? offsetS
: offsetS
.mid(0, colonIndex
);
723 const QString endPosS
= colonIndex
== -1 ? offsetS
: offsetS
.mid(colonIndex
+ 1);
724 if (!parseNumber(startPosS
, &startPos
, errorMessage
) || !parseNumber(endPosS
, &endPos
, errorMessage
)) {
728 qDebug() << Q_FUNC_INFO
<< value
<< startPos
<< endPos
;
730 ruleMatcher
->add(QSharedPointer
<MagicRule
>(MagicRule::createStringRule(value
, startPos
, endPos
)));
734 bool BaseMimeTypeParser::parse(QIODevice
*dev
, const QString
&fileName
, QString
*errorMessage
)
737 MagicRuleMatcher
*ruleMatcher
= 0;
739 QXmlStreamReader
reader(dev
);
740 ParseStage ps
= ParseBeginning
;
742 while (!reader
.atEnd()) {
743 switch (reader
.readNext()) {
744 case QXmlStreamReader::StartElement
:
745 ps
= nextStage(ps
, reader
.name());
747 case ParseMimeType
: // start parsing a type
748 { const QString type
= reader
.attributes().value(QLatin1String(mimeTypeAttributeC
)).toString();
749 if (type
.isEmpty()) {
750 reader
.raiseError(QString::fromLatin1("Missing 'type'-attribute"));
756 case ParseGlobPattern
:
757 addGlobPattern(reader
.attributes().value(QLatin1String(patternAttributeC
)).toString(), &data
);
761 const QString inheritsFrom
= reader
.attributes().value(QLatin1String(mimeTypeAttributeC
)).toString();
762 if (!inheritsFrom
.isEmpty()) {
763 data
.subClassesOf
.push_back(inheritsFrom
);
769 // comments have locale attributes. We want the default, English one
770 QString locale
= reader
.attributes().value(QLatin1String(localeAttributeC
)).toString();
771 const QString comment
= QCoreApplication::translate("MimeType", reader
.readElementText().toLatin1());
772 if (locale
.isEmpty()) {
773 data
.comment
= comment
;
775 data
.localeComments
.insert(locale
, comment
);
781 const QString alias
= reader
.attributes().value(QLatin1String(mimeTypeAttributeC
)).toString();
782 if (!alias
.isEmpty()) {
783 data
.aliases
.push_back(alias
);
790 const QString priorityS
= reader
.attributes().value(QLatin1String(priorityAttributeC
)).toString();
791 if (!priorityS
.isEmpty()) {
792 if (!parseNumber(priorityS
, &priority
, errorMessage
)) {
796 ruleMatcher
= new MagicRuleMatcher
;
797 ruleMatcher
->setPriority(priority
);
800 case ParseMagicMatchRule
:
801 if (!addMagicMatchRule(reader
.attributes(), ruleMatcher
, errorMessage
)) {
806 reader
.raiseError(QString::fromLatin1("Unexpected element <%1>").arg(reader
.name().toString()));
810 } // switch nextStage
812 // continue switch QXmlStreamReader::Token...
813 case QXmlStreamReader::EndElement
: // Finished element
814 if (reader
.name() == QLatin1String(mimeTypeTagC
)) {
815 if (!process(MimeType(data
), errorMessage
)) {
820 // Finished a match sequence
821 if (reader
.name() == QLatin1String(QLatin1String(magicTagC
))) {
822 data
.magicMatchers
.push_back(QSharedPointer
<IMagicMatcher
>(ruleMatcher
));
830 } // switch reader.readNext()
833 if (reader
.hasError()) {
834 *errorMessage
= QString::fromLatin1("An error has been encountered at line %1 of %2: %3:").arg(reader
.lineNumber()).arg(fileName
, reader
.errorString());
839 } // namespace Internal
841 // MimeMapEntry: Entry of a type map, consisting of type and level.
843 enum { Dangling
= 32767 };
845 struct MimeMapEntry
{
846 explicit MimeMapEntry(const MimeType
&t
= MimeType(), int aLevel
= Dangling
);
848 int level
; // hierachy level
851 MimeMapEntry::MimeMapEntry(const MimeType
&t
, int aLevel
) :
856 /* MimeDatabasePrivate: Requirements for storage:
857 * - Must be robust in case of incomplete hierachies, dangling entries
858 * - Plugins will not load and register their mime types in order
860 * - Multiple inheritance (several subClassesOf) can occur
861 * - Provide quick lookup by name
862 * - Provide quick lookup by file type.
863 * This basically rules out some pointer-based tree, so the structure choosen
865 * - An alias map <QString->QString> for mapping aliases to types
866 * - A Map <QString-MimeMapEntry> for the types (MimeMapEntry being a pair of
867 * MimeType and (hierarchy) level.
868 * - A map <QString->QString> representing parent->child relations (enabling
869 * recursing over children)
870 * Using strings avoids dangling pointers.
871 * The hierarchy level is used for mapping by file types. When findByFile()
872 * is first called after addMimeType() it recurses over the hierarchy and sets
873 * the hierarchy level of the entries accordingly (0 toplevel, 1 first
874 * order...). It then does several passes over the type map, checking the
875 * globs for maxLevel, maxLevel-1....until it finds a match (idea being to
876 * to check the most specific types first). Starting a recursion from the
877 * leaves is not suitable since it will hit parent nodes several times. */
879 class MimeDatabasePrivate
{
880 Q_DISABLE_COPY(MimeDatabasePrivate
)
882 MimeDatabasePrivate();
884 bool addMimeTypes(const QString
&fileName
, QString
*errorMessage
);
885 bool addMimeTypes(QIODevice
*device
, QString
*errorMessage
);
886 bool addMimeType(MimeType mt
);
888 // Returns a mime type or Null one if none found
889 MimeType
findByType(const QString
&type
) const;
890 // Returns a mime type or Null one if none found
891 MimeType
findByFile(const QFileInfo
&f
) const;
893 bool setPreferredSuffix(const QString
&typeOrAlias
, const QString
&suffix
);
895 // Return all known suffixes
896 QStringList
suffixes() const;
897 QStringList
filterStrings() const;
899 void debug(QTextStream
&str
) const;
902 typedef QHash
<QString
, MimeMapEntry
> TypeMimeTypeMap
;
903 typedef QHash
<QString
, QString
> AliasMap
;
904 typedef QMultiHash
<QString
, QString
> ParentChildrenMap
;
906 bool addMimeTypes(QIODevice
*device
, const QString
&fileName
, QString
*errorMessage
);
907 inline const QString
&resolveAlias(const QString
&name
) const;
908 MimeType
findByFile(const QFileInfo
&f
, unsigned *priority
) const;
909 void determineLevels();
910 void raiseLevelRecursion(MimeMapEntry
&e
, int level
);
912 TypeMimeTypeMap m_typeMimeTypeMap
;
914 ParentChildrenMap m_parentChildrenMap
;
918 MimeDatabasePrivate::MimeDatabasePrivate() :
923 // Parser that builds MimeDB hierarchy by adding to MimeDatabasePrivate
924 class MimeTypeParser
: public BaseMimeTypeParser
{
926 explicit MimeTypeParser(MimeDatabasePrivate
&db
) : m_db(db
) {}
928 virtual bool process(const MimeType
&t
, QString
*)
930 m_db
.addMimeType(t
); return true;
933 MimeDatabasePrivate
&m_db
;
935 } // namespace Internal
937 bool MimeDatabasePrivate::addMimeTypes(QIODevice
*device
, const QString
&fileName
, QString
*errorMessage
)
939 Internal::MimeTypeParser
parser(*this);
941 return parser
.parse(device
, fileName
, errorMessage
);
944 bool MimeDatabasePrivate::addMimeTypes(const QString
&fileName
, QString
*errorMessage
)
946 QFile
file(fileName
);
948 if (!file
.open(QIODevice::ReadOnly
| QIODevice::Text
)) {
949 *errorMessage
= QString::fromLatin1("Cannot open %1: %2").arg(fileName
, file
.errorString());
952 return addMimeTypes(&file
, fileName
, errorMessage
);
955 bool MimeDatabasePrivate::addMimeTypes(QIODevice
*device
, QString
*errorMessage
)
957 return addMimeTypes(device
, QLatin1String("<stream>"), errorMessage
);
960 bool MimeDatabasePrivate::addMimeType(MimeType mt
)
966 const QString type
= mt
.type();
967 // Hack: Add a magic text matcher to "text/plain" and the fallback matcher to
968 // binary types "application/octet-stream"
969 if (type
== QLatin1String(textTypeC
)) {
970 mt
.addMagicMatcher(QSharedPointer
<IMagicMatcher
>(new Internal::HeuristicTextMagicMatcher
));
972 if (type
== QLatin1String(binaryTypeC
)) {
973 mt
.addMagicMatcher(QSharedPointer
<IMagicMatcher
>(new Internal::BinaryMatcher
));
977 m_typeMimeTypeMap
.insert(type
, MimeMapEntry(mt
));
978 // Register the children, resolved via alias map. Note that it is still
979 // possible that aliases end up in the map if the parent classes are not inserted
980 // at this point (thus their aliases not known).
981 const QStringList subClassesOf
= mt
.subClassesOf();
982 if (!subClassesOf
.empty()) {
983 const QStringList::const_iterator socend
= subClassesOf
.constEnd();
984 for (QStringList::const_iterator soit
= subClassesOf
.constBegin(); soit
!= socend
; ++soit
) {
985 m_parentChildrenMap
.insert(resolveAlias(*soit
), type
);
989 const QStringList aliases
= mt
.aliases();
990 if (!aliases
.empty()) {
991 const QStringList::const_iterator cend
= aliases
.constEnd();
992 for (QStringList::const_iterator it
= aliases
.constBegin(); it
!= cend
; ++it
) {
993 m_aliasMap
.insert(*it
, type
);
996 m_maxLevel
= -1; // Mark as dirty
1000 const QString
&MimeDatabasePrivate::resolveAlias(const QString
&name
) const
1002 const AliasMap::const_iterator aliasIt
= m_aliasMap
.constFind(name
);
1004 return aliasIt
== m_aliasMap
.constEnd() ? name
: aliasIt
.value();
1007 void MimeDatabasePrivate::raiseLevelRecursion(MimeMapEntry
&e
, int level
)
1009 if (e
.level
== Dangling
|| e
.level
< level
) {
1012 if (m_maxLevel
< level
) {
1015 // At all events recurse over children since nodes might have been
1017 const QStringList childTypes
= m_parentChildrenMap
.values(e
.type
.type());
1018 if (childTypes
.empty()) {
1021 // look them up in the type->mime type map
1022 const int nextLevel
= level
+ 1;
1023 const TypeMimeTypeMap::iterator tm_end
= m_typeMimeTypeMap
.end();
1024 const QStringList::const_iterator cend
= childTypes
.constEnd();
1025 for (QStringList::const_iterator it
= childTypes
.constBegin(); it
!= cend
; ++it
) {
1026 const TypeMimeTypeMap::iterator tm_it
= m_typeMimeTypeMap
.find(resolveAlias(*it
));
1027 if (tm_it
== tm_end
) {
1028 qWarning("%s: Inconsistent mime hierarchy detected, child %s of %s cannot be found.",
1029 Q_FUNC_INFO
, it
->toUtf8().constData(), e
.type
.type().toUtf8().constData());
1031 raiseLevelRecursion(*tm_it
, nextLevel
);
1036 void MimeDatabasePrivate::determineLevels()
1038 // Loop over toplevels and recurse down their hierarchies.
1039 // Determine top levels by subtracting the children from the parent
1040 // set. Note that a toplevel at this point might have 'subclassesOf'
1041 // set to some class that is not in the DB, so, checking for an empty
1042 // 'subclassesOf' set is not sufficient to find the toplevels.
1043 // First, take the parent->child entries whose parent exists and build
1044 // sets of parents/children
1045 QSet
<QString
> parentSet
, childrenSet
;
1046 const ParentChildrenMap::const_iterator pcend
= m_parentChildrenMap
.constEnd();
1047 for (ParentChildrenMap::const_iterator it
= m_parentChildrenMap
.constBegin(); it
!= pcend
; ++it
) {
1048 if (m_typeMimeTypeMap
.contains(it
.key())) {
1049 parentSet
.insert(it
.key());
1050 childrenSet
.insert(it
.value());
1053 const QSet
<QString
> topLevels
= parentSet
.subtract(childrenSet
);
1055 qDebug() << Q_FUNC_INFO
<< "top levels" << topLevels
;
1057 const TypeMimeTypeMap::iterator tm_end
= m_typeMimeTypeMap
.end();
1058 const QSet
<QString
>::const_iterator tl_cend
= topLevels
.constEnd();
1059 for (QSet
<QString
>::const_iterator tl_it
= topLevels
.constBegin(); tl_it
!= tl_cend
; ++tl_it
) {
1060 const TypeMimeTypeMap::iterator tm_it
= m_typeMimeTypeMap
.find(resolveAlias(*tl_it
));
1061 if (tm_it
== tm_end
) {
1062 qWarning("%s: Inconsistent mime hierarchy detected, top level element %s cannot be found.",
1063 Q_FUNC_INFO
, tl_it
->toUtf8().constData());
1065 raiseLevelRecursion(tm_it
.value(), 0);
1070 bool MimeDatabasePrivate::setPreferredSuffix(const QString
&typeOrAlias
, const QString
&suffix
)
1072 TypeMimeTypeMap::iterator tit
= m_typeMimeTypeMap
.find(resolveAlias(typeOrAlias
));
1074 if (tit
!= m_typeMimeTypeMap
.end()) {
1075 return tit
.value().type
.setPreferredSuffix(suffix
);
1080 // Returns a mime type or Null one if none found
1081 MimeType
MimeDatabasePrivate::findByType(const QString
&typeOrAlias
) const
1083 const TypeMimeTypeMap::const_iterator tit
= m_typeMimeTypeMap
.constFind(resolveAlias(typeOrAlias
));
1085 if (tit
!= m_typeMimeTypeMap
.constEnd()) {
1086 return tit
.value().type
;
1091 // Debugging wrapper around findByFile()
1092 MimeType
MimeDatabasePrivate::findByFile(const QFileInfo
&f
) const
1094 unsigned priority
= 0;
1097 qDebug() << '>' << Q_FUNC_INFO
<< f
.fileName();
1099 const MimeType rc
= findByFile(f
, &priority
);
1102 qDebug() << "<MimeDatabase::findByFile: match prio=" << priority
<< rc
.type();
1104 qDebug() << "<MimeDatabase::findByFile: no match";
1110 // Returns a mime type or Null one if none found
1111 MimeType
MimeDatabasePrivate::findByFile(const QFileInfo
&f
, unsigned *priorityPtr
) const
1113 typedef QList
<MimeMapEntry
> MimeMapEntryList
__attribute__((unused
));
1115 // Is the hierarchy set up in case we find several matches?
1116 if (m_maxLevel
< 0) {
1117 MimeDatabasePrivate
*db
= const_cast<MimeDatabasePrivate
*>(this);
1118 db
->determineLevels();
1120 // Starting from max level (most specific): Try to find a match of
1121 // best (max) priority. Return if a glob match triggers.
1123 unsigned maxPriority
= 0;
1125 Internal::FileMatchContext
context(f
);
1126 const TypeMimeTypeMap::const_iterator cend
= m_typeMimeTypeMap
.constEnd();
1127 for (int level
= m_maxLevel
; level
>= 0; level
--) {
1128 for (TypeMimeTypeMap::const_iterator it
= m_typeMimeTypeMap
.constBegin(); it
!= cend
; ++it
) {
1129 if (it
.value().level
== level
) {
1130 const unsigned priority
= it
.value().type
.matchesFile(context
);
1131 if (debugMimeDB
> 1) {
1132 qDebug() << "pass" << level
<< it
.value().type
.type() << " matches " << priority
;
1135 if (priority
> maxPriority
) {
1136 rc
= it
.value().type
;
1137 maxPriority
= priority
;
1138 // Glob (exact) match?! We are done
1139 if (maxPriority
== MimeType::GlobMatchPriority
) {
1140 *priorityPtr
= priority
;
1151 // Return all known suffixes
1152 QStringList
MimeDatabasePrivate::suffixes() const
1155 const TypeMimeTypeMap::const_iterator cend
= m_typeMimeTypeMap
.constEnd();
1157 for (TypeMimeTypeMap::const_iterator it
= m_typeMimeTypeMap
.constBegin(); it
!= cend
; ++it
) {
1158 rc
+= it
.value().type
.suffixes();
1163 QStringList
MimeDatabasePrivate::filterStrings() const
1166 const TypeMimeTypeMap::const_iterator cend
= m_typeMimeTypeMap
.constEnd();
1168 for (TypeMimeTypeMap::const_iterator it
= m_typeMimeTypeMap
.constBegin(); it
!= cend
; ++it
) {
1169 rc
+= it
.value().type
.filterString();
1174 void MimeDatabasePrivate::debug(QTextStream
&str
) const
1176 str
<< ">MimeDatabase\n";
1177 const TypeMimeTypeMap::const_iterator cend
= m_typeMimeTypeMap
.constEnd();
1178 for (TypeMimeTypeMap::const_iterator it
= m_typeMimeTypeMap
.constBegin(); it
!= cend
; ++it
) {
1179 str
<< "Entry level " << it
.value().level
<< '\n';
1180 it
.value().type
.m_d
->debug(str
);
1182 str
<< "<MimeDatabase\n";
1185 // --------------- MimeDatabase
1186 MimeDatabase::MimeDatabase() :
1187 m_d(new MimeDatabasePrivate
)
1190 MimeDatabase::~MimeDatabase()
1195 MimeType
MimeDatabase::findByType(const QString
&typeOrAlias
) const
1197 return m_d
->findByType(typeOrAlias
);
1200 MimeType
MimeDatabase::findByFile(const QFileInfo
&f
) const
1202 return m_d
->findByFile(f
);
1205 bool MimeDatabase::addMimeType(const MimeType
&mt
)
1207 return m_d
->addMimeType(mt
);
1210 bool MimeDatabase::addMimeTypes(const QString
&fileName
, QString
*errorMessage
)
1212 return m_d
->addMimeTypes(fileName
, errorMessage
);
1215 bool MimeDatabase::addMimeTypes(QIODevice
*device
, QString
*errorMessage
)
1217 return m_d
->addMimeTypes(device
, errorMessage
);
1220 QStringList
MimeDatabase::suffixes() const
1222 return m_d
->suffixes();
1225 QStringList
MimeDatabase::filterStrings() const
1227 return m_d
->filterStrings();
1230 QString
MimeDatabase::preferredSuffixByType(const QString
&type
) const
1232 if (const MimeType mt
= findByType(type
)) {
1233 return mt
.preferredSuffix();
1238 QString
MimeDatabase::preferredSuffixByFile(const QFileInfo
&f
) const
1240 if (const MimeType mt
= findByFile(f
)) {
1241 return mt
.preferredSuffix();
1246 bool MimeDatabase::setPreferredSuffix(const QString
&typeOrAlias
, const QString
&suffix
)
1248 return m_d
->setPreferredSuffix(typeOrAlias
, suffix
);
1251 QDebug
operator<<(QDebug d
, const MimeDatabase
&mt
)
1255 QTextStream
str(&s
);