QMimeBinaryProvider: cache comments and globPatterns

Avoid multiple re-reads of xml files, for example in dolphin,
which displays MIME type comments as file types.

Change-Id: Ia124930e2a1fdc99d8a4d160f2288a00f55e0e8e
Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
Alexander Volkov 2021-05-06 20:13:35 +03:00
parent ada29a19cd
commit d8dbc2b95a
3 changed files with 61 additions and 53 deletions

View File

@ -228,7 +228,20 @@ void QMimeDatabasePrivate::loadMimeTypePrivate(QMimeTypePrivate &mimePrivate)
return; // invalid mimetype
if (!mimePrivate.loaded) { // XML provider sets loaded=true, binary provider does this on demand
Q_ASSERT(mimePrivate.fromCache);
QMimeBinaryProvider::loadMimeTypePrivate(mimePrivate);
bool found = false;
for (const auto &provider : providers()) {
if (provider->loadMimeTypePrivate(mimePrivate)) {
found = true;
break;
}
}
if (!found) {
const QString file = mimePrivate.name + QLatin1String(".xml");
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);
}
mimePrivate.loaded = true;
}
}

View File

@ -203,11 +203,14 @@ void QMimeBinaryProvider::ensureLoaded()
const QString cacheFileName = m_directory + QLatin1String("/mime.cache");
m_cacheFile = new CacheFile(cacheFileName);
m_mimetypeListLoaded = false;
m_mimetypeExtra.clear();
} else {
if (checkCacheChanged())
if (checkCacheChanged()) {
m_mimetypeListLoaded = false;
else
m_mimetypeExtra.clear();
} else {
return; // nothing to do
}
}
if (!m_cacheFile->isValid()) { // verify existence and version
delete m_cacheFile;
@ -489,47 +492,44 @@ void QMimeBinaryProvider::addAllMimeTypes(QList<QMimeType> &result)
}
}
void QMimeBinaryProvider::loadMimeTypePrivate(QMimeTypePrivate &data)
bool QMimeBinaryProvider::loadMimeTypePrivate(QMimeTypePrivate &data)
{
#ifdef QT_NO_XMLSTREAMREADER
Q_UNUSED(data);
qWarning("Cannot load mime type since QXmlStreamReader is not available.");
return;
return false;
#else
if (data.loaded)
return;
data.loaded = true;
// load comment and globPatterns
return true;
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;
}
auto it = m_mimetypeExtra.constFind(data.name);
if (it == m_mimetypeExtra.constEnd()) {
// load comment and globPatterns
QString mainPattern;
// shared-mime-info since 1.3 lowercases the xml files
QString mimeFile = m_directory + QLatin1Char('/') + data.name.toLower() + QLatin1String(".xml");
if (!QFile::exists(mimeFile))
mimeFile = m_directory + QLatin1Char('/') + data.name + QLatin1String(".xml"); // pre-1.3
for (QStringList::const_reverse_iterator it = mimeFiles.crbegin(), end = mimeFiles.crend(); it != end; ++it) { // global first, then local.
QFile qfile(*it);
QFile qfile(mimeFile);
if (!qfile.open(QFile::ReadOnly))
continue;
return false;
auto insertIt = m_mimetypeExtra.insert(data.name, MimeTypeExtra{});
it = insertIt;
MimeTypeExtra &extra = insertIt.value();
QString mainPattern;
QXmlStreamReader xml(&qfile);
if (xml.readNextStartElement()) {
if (xml.name() != QLatin1String("mime-type")) {
continue;
return false;
}
const auto name = xml.attributes().value(QLatin1String("type"));
if (name.isEmpty())
continue;
return false;
if (name.compare(data.name, Qt::CaseInsensitive))
qWarning() << "Got name" << name << "in file" << file << "expected" << data.name;
qWarning() << "Got name" << name << "in file" << mimeFile << "expected" << data.name;
while (xml.readNextStartElement()) {
const auto tag = xml.name();
@ -539,49 +539,37 @@ void QMimeBinaryProvider::loadMimeTypePrivate(QMimeTypePrivate &data)
if (lang.isEmpty()) {
lang = QLatin1String("default"); // no locale attribute provided, treat it as default.
}
data.localeComments.insert(lang, text);
extra.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();
extra.globPatterns.clear();
mainPattern.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);
if (!extra.globPatterns.contains(pattern))
extra.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) {
// 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 (!mainPattern.isEmpty() &&
(extra.globPatterns.isEmpty() || extra.globPatterns.constFirst() != mainPattern)) {
// ensure it's first in the list of patterns
data.globPatterns.removeAll(mainPattern);
data.globPatterns.prepend(mainPattern);
extra.globPatterns.removeAll(mainPattern);
extra.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
const MimeTypeExtra &e = it.value();
data.localeComments = e.localeComments;
data.globPatterns = e.globPatterns;
return true;
#endif //QT_NO_XMLSTREAMREADER
}

View File

@ -79,6 +79,7 @@ public:
virtual void addAliases(const QString &name, QStringList &result) = 0;
virtual void findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate) = 0;
virtual void addAllMimeTypes(QList<QMimeType> &result) = 0;
virtual bool loadMimeTypePrivate(QMimeTypePrivate &) { return false; }
virtual void loadIcon(QMimeTypePrivate &) {}
virtual void loadGenericIcon(QMimeTypePrivate &) {}
virtual void ensureLoaded() {}
@ -107,7 +108,7 @@ public:
void addAliases(const QString &name, QStringList &result) override;
void findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate) override;
void addAllMimeTypes(QList<QMimeType> &result) override;
static void loadMimeTypePrivate(QMimeTypePrivate &);
bool loadMimeTypePrivate(QMimeTypePrivate &) override;
void loadIcon(QMimeTypePrivate &) override;
void loadGenericIcon(QMimeTypePrivate &) override;
void ensureLoaded() override;
@ -126,6 +127,12 @@ private:
QStringList m_cacheFileNames;
QSet<QString> m_mimetypeNames;
bool m_mimetypeListLoaded;
struct MimeTypeExtra
{
QHash<QString, QString> localeComments;
QStringList globPatterns;
};
QMap<QString, MimeTypeExtra> m_mimetypeExtra;
};
/*