1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // Copyright (C) 2015 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com> |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #include "qmimetype.h" |
6 | |
7 | #include "qmimetype_p.h" |
8 | #include "qmimedatabase_p.h" |
9 | #include "qmimeprovider_p.h" |
10 | |
11 | #include "qmimeglobpattern_p.h" |
12 | |
13 | #include <QtCore/QDebug> |
14 | #include <QtCore/QLocale> |
15 | #include <QtCore/QHashFunctions> |
16 | |
17 | #include <memory> |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | using namespace Qt::StringLiterals; |
22 | |
23 | QMimeTypePrivate::QMimeTypePrivate() |
24 | : loaded(false), fromCache(false) |
25 | {} |
26 | |
27 | QMimeTypePrivate::QMimeTypePrivate(const QMimeType &other) |
28 | : loaded(other.d->loaded), |
29 | name(other.d->name), |
30 | localeComments(other.d->localeComments), |
31 | genericIconName(other.d->genericIconName), |
32 | iconName(other.d->iconName), |
33 | globPatterns(other.d->globPatterns) |
34 | {} |
35 | |
36 | void QMimeTypePrivate::clear() |
37 | { |
38 | name.clear(); |
39 | localeComments.clear(); |
40 | genericIconName.clear(); |
41 | iconName.clear(); |
42 | globPatterns.clear(); |
43 | } |
44 | |
45 | void QMimeTypePrivate::addGlobPattern(const QString &pattern) |
46 | { |
47 | globPatterns.append(t: pattern); |
48 | } |
49 | |
50 | /*! |
51 | \class QMimeType |
52 | \inmodule QtCore |
53 | \ingroup shared |
54 | \brief The QMimeType class describes types of file or data, represented by a MIME type string. |
55 | |
56 | \since 5.0 |
57 | |
58 | For instance a file named "readme.txt" has the MIME type "text/plain". |
59 | The MIME type can be determined from the file name, or from the file |
60 | contents, or from both. MIME type determination can also be done on |
61 | buffers of data not coming from files. |
62 | |
63 | Determining the MIME type of a file can be useful to make sure your |
64 | application supports it. It is also useful in file-manager-like applications |
65 | or widgets, in order to display an appropriate \l {QMimeType::iconName}{icon} for the file, or even |
66 | the descriptive \l {QMimeType::comment()}{comment} in detailed views. |
67 | |
68 | To check if a file has the expected MIME type, you should use inherits() |
69 | rather than a simple string comparison based on the name(). This is because |
70 | MIME types can inherit from each other: for instance a C source file is |
71 | a specific type of plain text file, so text/x-csrc inherits text/plain. |
72 | |
73 | \sa QMimeDatabase, {MIME Type Browser Example} |
74 | */ |
75 | |
76 | /*! |
77 | \fn QMimeType &QMimeType::operator=(QMimeType &&other) |
78 | |
79 | Move-assigns \a other to this QMimeType instance. |
80 | |
81 | \since 5.2 |
82 | */ |
83 | |
84 | /*! |
85 | \fn QMimeType::QMimeType(); |
86 | Constructs this QMimeType object initialized with default property values that indicate an invalid MIME type. |
87 | */ |
88 | QMimeType::QMimeType() : |
89 | d(new QMimeTypePrivate()) |
90 | { |
91 | } |
92 | |
93 | /*! |
94 | \fn QMimeType::QMimeType(const QMimeType &other); |
95 | Constructs this QMimeType object as a copy of \a other. |
96 | */ |
97 | QMimeType::QMimeType(const QMimeType &other) : |
98 | d(other.d) |
99 | { |
100 | } |
101 | |
102 | /*! |
103 | \fn QMimeType &QMimeType::operator=(const QMimeType &other); |
104 | Assigns the data of \a other to this QMimeType object, and returns a reference to this object. |
105 | */ |
106 | QMimeType &QMimeType::operator=(const QMimeType &other) |
107 | { |
108 | if (d != other.d) |
109 | d = other.d; |
110 | return *this; |
111 | } |
112 | |
113 | /*! |
114 | \fn QMimeType::QMimeType(const QMimeTypePrivate &dd); |
115 | Assigns the data of the QMimeTypePrivate \a dd to this QMimeType object, and returns a reference to this object. |
116 | \internal |
117 | */ |
118 | QMimeType::QMimeType(const QMimeTypePrivate &dd) : |
119 | d(new QMimeTypePrivate(dd)) |
120 | { |
121 | } |
122 | |
123 | /*! |
124 | \fn void QMimeType::swap(QMimeType &other); |
125 | Swaps QMimeType \a other with this QMimeType object. |
126 | |
127 | This operation is very fast and never fails. |
128 | |
129 | The swap() method helps with the implementation of assignment |
130 | operators in an exception-safe way. For more information consult |
131 | \l {http://en.wikibooks.org/wiki/More_C++_Idioms/Copy-and-swap} |
132 | {More C++ Idioms - Copy-and-swap}. |
133 | */ |
134 | |
135 | /*! |
136 | \fn QMimeType::~QMimeType(); |
137 | Destroys the QMimeType object, and releases the d pointer. |
138 | */ |
139 | QMimeType::~QMimeType() |
140 | { |
141 | } |
142 | |
143 | /*! |
144 | \fn bool QMimeType::operator==(const QMimeType &other) const; |
145 | Returns \c true if \a other equals this QMimeType object, otherwise returns \c false. |
146 | The name is the unique identifier for a mimetype, so two mimetypes with |
147 | the same name, are equal. |
148 | */ |
149 | bool QMimeType::operator==(const QMimeType &other) const |
150 | { |
151 | return d == other.d || d->name == other.d->name; |
152 | } |
153 | |
154 | /*! |
155 | \since 5.6 |
156 | \relates QMimeType |
157 | |
158 | Returns the hash value for \a key, using |
159 | \a seed to seed the calculation. |
160 | */ |
161 | size_t qHash(const QMimeType &key, size_t seed) noexcept |
162 | { |
163 | return qHash(key: key.d->name, seed); |
164 | } |
165 | |
166 | /*! |
167 | \fn bool QMimeType::operator!=(const QMimeType &other) const; |
168 | Returns \c true if \a other does not equal this QMimeType object, otherwise returns \c false. |
169 | */ |
170 | |
171 | /*! |
172 | \property QMimeType::valid |
173 | \brief \c true if the QMimeType object contains valid data, \c false otherwise |
174 | |
175 | A valid MIME type has a non-empty name(). |
176 | The invalid MIME type is the default-constructed QMimeType. |
177 | |
178 | While this property was introduced in 5.10, the |
179 | corresponding accessor method has always been there. |
180 | */ |
181 | bool QMimeType::isValid() const |
182 | { |
183 | return !d->name.isEmpty(); |
184 | } |
185 | |
186 | /*! |
187 | \property QMimeType::isDefault |
188 | \brief \c true if this MIME type is the default MIME type which |
189 | applies to all files: application/octet-stream. |
190 | |
191 | While this property was introduced in 5.10, the |
192 | corresponding accessor method has always been there. |
193 | */ |
194 | bool QMimeType::isDefault() const |
195 | { |
196 | return d->name == QMimeDatabasePrivate::instance()->defaultMimeType(); |
197 | } |
198 | |
199 | /*! |
200 | \property QMimeType::name |
201 | \brief the name of the MIME type |
202 | |
203 | While this property was introduced in 5.10, the |
204 | corresponding accessor method has always been there. |
205 | */ |
206 | QString QMimeType::name() const |
207 | { |
208 | return d->name; |
209 | } |
210 | |
211 | /*! |
212 | \property QMimeType::comment |
213 | \brief the description of the MIME type to be displayed on user interfaces |
214 | |
215 | The default language (QLocale().name()) is used to select the appropriate translation. |
216 | |
217 | While this property was introduced in 5.10, the |
218 | corresponding accessor method has always been there. |
219 | */ |
220 | QString QMimeType::() const |
221 | { |
222 | QMimeDatabasePrivate::instance()->loadMimeTypePrivate(mimePrivate&: const_cast<QMimeTypePrivate&>(*d)); |
223 | |
224 | QStringList languageList = QLocale().uiLanguages(); |
225 | qsizetype defaultIndex = languageList.indexOf(str: u"en-US"_s ); |
226 | |
227 | // Include the default locale as fall-back. |
228 | if (defaultIndex >= 0) { |
229 | // en_US is generally the default, and may be omitted from the |
230 | // overtly-named locales in the MIME type's data (QTBUG-105007). |
231 | ++defaultIndex; // Skip over en-US. |
232 | // That's typically followed by en-Latn-US and en (in that order): |
233 | if (defaultIndex < languageList.size() && languageList.at(i: defaultIndex) == u"en-Latn-US" ) |
234 | ++defaultIndex; |
235 | if (defaultIndex < languageList.size() && languageList.at(i: defaultIndex) == u"en" ) |
236 | ++defaultIndex; |
237 | } else { |
238 | // Absent en-US, just append it: |
239 | defaultIndex = languageList.size(); |
240 | } |
241 | languageList.insert(i: defaultIndex, t: u"default"_s ); |
242 | |
243 | for (const QString &language : std::as_const(t&: languageList)) { |
244 | // uiLanguages() uses '-' as separator, MIME database uses '_' |
245 | const QString lang |
246 | = language == "C"_L1 ? u"en_US"_s : QString(language).replace(before: u'-', after: u'_'); |
247 | QString comm = d->localeComments.value(key: lang); |
248 | if (!comm.isEmpty()) |
249 | return comm; |
250 | const qsizetype cut = lang.indexOf(c: u'_'); |
251 | // If "de_CH" is missing, check for "de" (and similar): |
252 | if (cut != -1) { |
253 | comm = d->localeComments.value(key: lang.left(n: cut)); |
254 | if (!comm.isEmpty()) |
255 | return comm; |
256 | } |
257 | } |
258 | |
259 | // Use the mimetype name as fallback |
260 | return d->name; |
261 | } |
262 | |
263 | /*! |
264 | \property QMimeType::genericIconName |
265 | \brief the file name of a generic icon that represents the MIME type |
266 | |
267 | This should be used if the icon returned by iconName() cannot be found on |
268 | the system. It is used for categories of similar types (like spreadsheets |
269 | or archives) that can use a common icon. |
270 | The freedesktop.org Icon Naming Specification lists a set of such icon names. |
271 | |
272 | The icon name can be given to QIcon::fromTheme() in order to load the icon. |
273 | |
274 | While this property was introduced in 5.10, the |
275 | corresponding accessor method has always been there. |
276 | */ |
277 | QString QMimeType::genericIconName() const |
278 | { |
279 | QMimeDatabasePrivate::instance()->loadGenericIcon(mimePrivate&: const_cast<QMimeTypePrivate&>(*d)); |
280 | if (d->genericIconName.isEmpty()) { |
281 | // From the spec: |
282 | // If the generic icon name is empty (not specified by the mimetype definition) |
283 | // then the mimetype is used to generate the generic icon by using the top-level |
284 | // media type (e.g. "video" in "video/ogg") and appending "-x-generic" |
285 | // (i.e. "video-x-generic" in the previous example). |
286 | const QString group = name(); |
287 | QStringView groupRef(group); |
288 | const qsizetype slashindex = groupRef.indexOf(c: u'/'); |
289 | if (slashindex != -1) |
290 | groupRef = groupRef.left(n: slashindex); |
291 | return groupRef + "-x-generic"_L1 ; |
292 | } |
293 | return d->genericIconName; |
294 | } |
295 | |
296 | static QString make_default_icon_name_from_mimetype_name(QString iconName) |
297 | { |
298 | const qsizetype slashindex = iconName.indexOf(c: u'/'); |
299 | if (slashindex != -1) |
300 | iconName[slashindex] = u'-'; |
301 | return iconName; |
302 | } |
303 | |
304 | /*! |
305 | \property QMimeType::iconName |
306 | \brief the file name of an icon image that represents the MIME type |
307 | |
308 | The icon name can be given to QIcon::fromTheme() in order to load the icon. |
309 | |
310 | While this property was introduced in 5.10, the |
311 | corresponding accessor method has always been there. |
312 | */ |
313 | QString QMimeType::iconName() const |
314 | { |
315 | QMimeDatabasePrivate::instance()->loadIcon(mimePrivate&: const_cast<QMimeTypePrivate&>(*d)); |
316 | if (d->iconName.isEmpty()) { |
317 | return make_default_icon_name_from_mimetype_name(iconName: name()); |
318 | } |
319 | return d->iconName; |
320 | } |
321 | |
322 | /*! |
323 | \property QMimeType::globPatterns |
324 | \brief the list of glob matching patterns |
325 | |
326 | While this property was introduced in 5.10, the |
327 | corresponding accessor method has always been there. |
328 | */ |
329 | QStringList QMimeType::globPatterns() const |
330 | { |
331 | QMimeDatabasePrivate::instance()->loadMimeTypePrivate(mimePrivate&: const_cast<QMimeTypePrivate&>(*d)); |
332 | return d->globPatterns; |
333 | } |
334 | |
335 | /*! |
336 | \property QMimeType::parentMimeTypes |
337 | \brief the names of parent MIME types |
338 | |
339 | A type is a subclass of another type if any instance of the first type is |
340 | also an instance of the second. For example, all image/svg+xml files are also |
341 | text/xml, text/plain and application/octet-stream files. Subclassing is about |
342 | the format, rather than the category of the data (for example, there is no |
343 | 'generic spreadsheet' class that all spreadsheets inherit from). |
344 | Conversely, the parent mimetype of image/svg+xml is text/xml. |
345 | |
346 | A mimetype can have multiple parents. For instance application/x-perl |
347 | has two parents: application/x-executable and text/plain. This makes |
348 | it possible to both execute perl scripts, and to open them in text editors. |
349 | |
350 | While this property was introduced in 5.10, the |
351 | corresponding accessor method has always been there. |
352 | */ |
353 | QStringList QMimeType::parentMimeTypes() const |
354 | { |
355 | return QMimeDatabasePrivate::instance()->mimeParents(mimeName: d->name); |
356 | } |
357 | |
358 | static void collectParentMimeTypes(const QString &mime, QStringList &allParents) |
359 | { |
360 | const QStringList parents = QMimeDatabasePrivate::instance()->mimeParents(mimeName: mime); |
361 | for (const QString &parent : parents) { |
362 | // I would use QSet, but since order matters I better not |
363 | if (!allParents.contains(str: parent)) |
364 | allParents.append(t: parent); |
365 | } |
366 | // We want a breadth-first search, so that the least-specific parent (octet-stream) is last |
367 | // This means iterating twice, unfortunately. |
368 | for (const QString &parent : parents) |
369 | collectParentMimeTypes(mime: parent, allParents); |
370 | } |
371 | |
372 | /*! |
373 | \property QMimeType::allAncestors |
374 | \brief the names of direct and indirect parent MIME types |
375 | |
376 | Return all the parent mimetypes of this mimetype, direct and indirect. |
377 | This includes the parent(s) of its parent(s), etc. |
378 | |
379 | For instance, for image/svg+xml the list would be: |
380 | application/xml, text/plain, application/octet-stream. |
381 | |
382 | Note that application/octet-stream is the ultimate parent for all types |
383 | of files (but not directories). |
384 | |
385 | While this property was introduced in 5.10, the |
386 | corresponding accessor method has always been there. |
387 | */ |
388 | QStringList QMimeType::allAncestors() const |
389 | { |
390 | QStringList allParents; |
391 | collectParentMimeTypes(mime: d->name, allParents); |
392 | return allParents; |
393 | } |
394 | |
395 | /*! |
396 | \property QMimeType::aliases |
397 | \brief the list of aliases of this mimetype |
398 | |
399 | For instance, for text/csv, the returned list would be: |
400 | text/x-csv, text/x-comma-separated-values. |
401 | |
402 | Note that all QMimeType instances refer to proper mimetypes, |
403 | never to aliases directly. |
404 | |
405 | The order of the aliases in the list is undefined. |
406 | |
407 | While this property was introduced in 5.10, the |
408 | corresponding accessor method has always been there. |
409 | */ |
410 | QStringList QMimeType::aliases() const |
411 | { |
412 | return QMimeDatabasePrivate::instance()->listAliases(mimeName: d->name); |
413 | } |
414 | |
415 | /*! |
416 | \property QMimeType::suffixes |
417 | \brief the known suffixes for the MIME type |
418 | |
419 | No leading dot is included, so for instance this would return "jpg", "jpeg" for image/jpeg. |
420 | |
421 | While this property was introduced in 5.10, the |
422 | corresponding accessor method has always been there. |
423 | */ |
424 | QStringList QMimeType::suffixes() const |
425 | { |
426 | QMimeDatabasePrivate::instance()->loadMimeTypePrivate(mimePrivate&: const_cast<QMimeTypePrivate&>(*d)); |
427 | |
428 | QStringList result; |
429 | for (const QString &pattern : std::as_const(t&: d->globPatterns)) { |
430 | // Not a simple suffix if it looks like: README or *. or *.* or *.JP*G or *.JP? |
431 | if (pattern.startsWith(s: "*."_L1 ) && |
432 | pattern.size() > 2 && |
433 | pattern.indexOf(c: u'*', from: 2) < 0 && pattern.indexOf(c: u'?', from: 2) < 0) { |
434 | const QString suffix = pattern.mid(position: 2); |
435 | result.append(t: suffix); |
436 | } |
437 | } |
438 | |
439 | return result; |
440 | } |
441 | |
442 | /*! |
443 | \property QMimeType::preferredSuffix |
444 | \brief the preferred suffix for the MIME type |
445 | |
446 | No leading dot is included, so for instance this would return "pdf" for application/pdf. |
447 | The return value can be empty, for mime types which do not have any suffixes associated. |
448 | |
449 | While this property was introduced in 5.10, the |
450 | corresponding accessor method has always been there. |
451 | */ |
452 | QString QMimeType::preferredSuffix() const |
453 | { |
454 | if (isDefault()) // workaround for unwanted *.bin suffix for octet-stream, https://bugs.freedesktop.org/show_bug.cgi?id=101667, fixed upstream in 1.10 |
455 | return QString(); |
456 | const QStringList suffixList = suffixes(); |
457 | return suffixList.isEmpty() ? QString() : suffixList.at(i: 0); |
458 | } |
459 | |
460 | /*! |
461 | \property QMimeType::filterString |
462 | \brief a filter string usable for a file dialog |
463 | |
464 | While this property was introduced in 5.10, the |
465 | corresponding accessor method has always been there. |
466 | */ |
467 | QString QMimeType::filterString() const |
468 | { |
469 | QMimeDatabasePrivate::instance()->loadMimeTypePrivate(mimePrivate&: const_cast<QMimeTypePrivate&>(*d)); |
470 | QString filter; |
471 | |
472 | if (!d->globPatterns.empty()) { |
473 | filter += comment() + " ("_L1 ; |
474 | for (int i = 0; i < d->globPatterns.size(); ++i) { |
475 | if (i != 0) |
476 | filter += u' '; |
477 | filter += d->globPatterns.at(i); |
478 | } |
479 | filter += u')'; |
480 | } |
481 | |
482 | return filter; |
483 | } |
484 | |
485 | /*! |
486 | \fn bool QMimeType::inherits(const QString &mimeTypeName) const; |
487 | Returns \c true if this mimetype is \a mimeTypeName, |
488 | or inherits \a mimeTypeName (see parentMimeTypes()), |
489 | or \a mimeTypeName is an alias for this mimetype. |
490 | |
491 | This method has been made invokable from QML since 5.10. |
492 | */ |
493 | bool QMimeType::inherits(const QString &mimeTypeName) const |
494 | { |
495 | if (d->name == mimeTypeName) |
496 | return true; |
497 | return QMimeDatabasePrivate::instance()->mimeInherits(mime: d->name, parent: mimeTypeName); |
498 | } |
499 | |
500 | #ifndef QT_NO_DEBUG_STREAM |
501 | QDebug operator<<(QDebug debug, const QMimeType &mime) |
502 | { |
503 | QDebugStateSaver saver(debug); |
504 | if (!mime.isValid()) { |
505 | debug.nospace() << "QMimeType(invalid)" ; |
506 | } else { |
507 | debug.nospace() << "QMimeType(" << mime.name() << ")" ; |
508 | } |
509 | return debug; |
510 | } |
511 | #endif |
512 | |
513 | QT_END_NAMESPACE |
514 | |
515 | #include "moc_qmimetype.cpp" |
516 | |