1/* This file is part of the KDE project
2 Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
3 Copyright (C) 2003, 2007 David Faure <faure@kde.org>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public
7 License version 2 or at your option version 3 as published by
8 the Free Software Foundation.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; see the file COPYING. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21#include "mimetypedata.h"
22#include "sharedmimeinfoversion.h"
23#include <kprotocolmanager.h>
24#include "mimetypewriter.h"
25#include <kdebug.h>
26#include <kservice.h>
27#include <ksharedconfig.h>
28#include <kconfiggroup.h>
29#include <kmimetypetrader.h>
30
31MimeTypeData::MimeTypeData(const QString& major)
32 : m_askSave(AskSaveDefault),
33 m_bNewItem(false),
34 m_bFullInit(true),
35 m_isGroup(true),
36 m_appServicesModified(false),
37 m_embedServicesModified(false),
38 m_major(major)
39{
40 m_autoEmbed = readAutoEmbed();
41}
42
43MimeTypeData::MimeTypeData(const KMimeType::Ptr mime)
44 : m_mimetype(mime),
45 m_askSave(AskSaveDefault), // TODO: the code for initializing this is missing. FileTypeDetails initializes the checkbox instead...
46 m_bNewItem(false),
47 m_bFullInit(false),
48 m_isGroup(false),
49 m_appServicesModified(false),
50 m_embedServicesModified(false)
51{
52 const QString mimeName = m_mimetype->name();
53 const int index = mimeName.indexOf('/');
54 if (index != -1) {
55 m_major = mimeName.left(index);
56 m_minor = mimeName.mid(index+1);
57 } else {
58 m_major = mimeName;
59 }
60 initFromKMimeType();
61}
62
63MimeTypeData::MimeTypeData(const QString& mimeName, bool)
64 : m_mimetype(0),
65 m_askSave(AskSaveDefault),
66 m_bNewItem(true),
67 m_bFullInit(false),
68 m_isGroup(false),
69 m_appServicesModified(false),
70 m_embedServicesModified(false)
71{
72 const int index = mimeName.indexOf('/');
73 if (index != -1) {
74 m_major = mimeName.left(index);
75 m_minor = mimeName.mid(index+1);
76 } else {
77 m_major = mimeName;
78 }
79 m_autoEmbed = UseGroupSetting;
80 // all the rest is empty by default
81}
82
83void MimeTypeData::initFromKMimeType()
84{
85 m_comment = m_mimetype->comment();
86 m_userSpecifiedIcon = m_mimetype->userSpecifiedIconName();
87 setPatterns(m_mimetype->patterns());
88 m_autoEmbed = readAutoEmbed();
89}
90
91MimeTypeData::AutoEmbed MimeTypeData::readAutoEmbed() const
92{
93 const KSharedConfig::Ptr config = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals);
94 const QString key = QString("embed-") + name();
95 const KConfigGroup group(config, "EmbedSettings");
96 if (m_isGroup) {
97 // embedding is false by default except for image/*, multipart/* and inode/* (hardcoded in konq)
98 const bool defaultValue = (m_major == "image" || m_major == "multipart" || m_major == "inode");
99 return group.readEntry(key, defaultValue) ? Yes : No;
100 } else {
101 if (group.hasKey(key))
102 return group.readEntry(key, false) ? Yes : No;
103 // TODO if ( !mimetype->property( "X-KDE-LocalProtocol" ).toString().isEmpty() )
104 // TODO return MimeTypeData::Yes; // embed by default for zip, tar etc.
105 return MimeTypeData::UseGroupSetting;
106 }
107}
108
109void MimeTypeData::writeAutoEmbed()
110{
111 KSharedConfig::Ptr config = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals);
112 if (!config->isConfigWritable(true))
113 return;
114
115 const QString key = QString("embed-") + name();
116 KConfigGroup group(config, "EmbedSettings");
117 if (m_isGroup) {
118 group.writeEntry(key, m_autoEmbed == Yes);
119 } else {
120 if (m_autoEmbed == UseGroupSetting)
121 group.deleteEntry(key);
122 else
123 group.writeEntry(key, m_autoEmbed == Yes);
124 }
125}
126
127bool MimeTypeData::isEssential() const
128{
129 // Keep in sync with KMimeType::checkEssentialMimeTypes
130 const QString n = name();
131 if ( n == "application/octet-stream" )
132 return true;
133 if ( n == "inode/directory" )
134 return true;
135 if ( n == "inode/blockdevice" )
136 return true;
137 if ( n == "inode/chardevice" )
138 return true;
139 if ( n == "inode/socket" )
140 return true;
141 if ( n == "inode/fifo" )
142 return true;
143 if ( n == "application/x-shellscript" )
144 return true;
145 if ( n == "application/x-executable" )
146 return true;
147 if ( n == "application/x-desktop" )
148 return true;
149 return false;
150}
151
152void MimeTypeData::setUserSpecifiedIcon(const QString& icon)
153{
154 m_userSpecifiedIcon = icon;
155}
156
157QStringList MimeTypeData::getAppOffers() const
158{
159 QStringList services;
160 const KService::List offerList =
161 KMimeTypeTrader::self()->query(name(), "Application");
162 KService::List::const_iterator it(offerList.begin());
163 for (; it != offerList.constEnd(); ++it) {
164 if ((*it)->allowAsDefault())
165 services.append((*it)->storageId());
166 }
167 return services;
168}
169
170QStringList MimeTypeData::getPartOffers() const
171{
172 QStringList services;
173 const KService::List partOfferList =
174 KMimeTypeTrader::self()->query(name(), "KParts/ReadOnlyPart");
175 for ( KService::List::const_iterator it = partOfferList.begin(); it != partOfferList.constEnd(); ++it)
176 services.append((*it)->storageId());
177 return services;
178}
179
180void MimeTypeData::getMyServiceOffers() const
181{
182 m_appServices = getAppOffers();
183 m_embedServices = getPartOffers();
184 m_bFullInit = true;
185}
186
187QStringList MimeTypeData::appServices() const
188{
189 if (!m_bFullInit) {
190 getMyServiceOffers();
191 }
192 return m_appServices;
193}
194
195QStringList MimeTypeData::embedServices() const
196{
197 if (!m_bFullInit) {
198 getMyServiceOffers();
199 }
200 return m_embedServices;
201}
202
203bool MimeTypeData::isMimeTypeDirty() const
204{
205 Q_ASSERT(!m_isGroup);
206 if (m_bNewItem)
207 return true;
208
209 if (!m_mimetype) {
210 kWarning() << "MimeTypeData for" << name() << "says 'not new' but is without a mimetype? Should not happen.";
211 return true;
212 }
213
214 if (m_mimetype->comment() != m_comment) {
215 kDebug() << "Mimetype Comment Dirty: old=" << m_mimetype->comment() << "m_comment=" << m_comment;
216 return true;
217 }
218 if (m_mimetype->userSpecifiedIconName() != m_userSpecifiedIcon) {
219 kDebug() << "Mimetype Icon Dirty: old=" << m_mimetype->iconName() << "m_userSpecifiedIcon=" << m_userSpecifiedIcon;
220 return true;
221 }
222
223 QStringList storedPatterns = m_mimetype->patterns();
224 storedPatterns.sort(); // see ctor
225 if ( storedPatterns != m_patterns) {
226 kDebug() << "Mimetype Patterns Dirty: old=" << storedPatterns
227 << "m_patterns=" << m_patterns;
228 return true;
229 }
230
231 if (readAutoEmbed() != m_autoEmbed)
232 return true;
233 return false;
234}
235
236bool MimeTypeData::isServiceListDirty() const
237{
238 return !m_isGroup && (m_appServicesModified || m_embedServicesModified);
239}
240
241bool MimeTypeData::isDirty() const
242{
243 if ( m_bNewItem ) {
244 kDebug() << "New item, need to save it";
245 return true;
246 }
247
248 if ( !m_isGroup ) {
249 if (isServiceListDirty())
250 return true;
251 if (isMimeTypeDirty())
252 return true;
253 }
254 else // is a group
255 {
256 if (readAutoEmbed() != m_autoEmbed)
257 return true;
258 }
259
260 if (m_askSave != AskSaveDefault)
261 return true;
262
263 // nothing seems to have changed, it's not dirty.
264 return false;
265}
266
267bool MimeTypeData::sync()
268{
269 if (m_isGroup) {
270 writeAutoEmbed();
271 return false;
272 }
273
274 if (m_askSave != AskSaveDefault) {
275 KSharedConfig::Ptr config = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals);
276 if (!config->isConfigWritable(true))
277 return false;
278 KConfigGroup cg = config->group("Notification Messages");
279 if (m_askSave == AskSaveYes) {
280 // Ask
281 cg.deleteEntry("askSave"+name());
282 cg.deleteEntry("askEmbedOrSave"+name());
283 } else {
284 // Do not ask, open
285 cg.writeEntry("askSave"+name(), "no" );
286 cg.writeEntry("askEmbedOrSave"+name(), "no" );
287 }
288 }
289
290 writeAutoEmbed();
291
292 bool needUpdateMimeDb = false;
293 if (isMimeTypeDirty()) {
294 MimeTypeWriter mimeTypeWriter(name());
295 mimeTypeWriter.setComment(m_comment);
296 if (SharedMimeInfoVersion::supportsIcon()) {
297 // Very important: don't write <icon> if shared-mime-info doesn't support it,
298 // it would abort on it!
299 if (!m_userSpecifiedIcon.isEmpty()) {
300 mimeTypeWriter.setIconName(m_userSpecifiedIcon);
301 }
302 }
303 mimeTypeWriter.setPatterns(m_patterns);
304 if (!mimeTypeWriter.write())
305 return false;
306
307 needUpdateMimeDb = true;
308 }
309
310 syncServices();
311
312 return needUpdateMimeDb;
313}
314
315void MimeTypeData::syncServices()
316{
317 if (!m_bFullInit)
318 return;
319
320 KSharedConfig::Ptr profile = KSharedConfig::openConfig("mimeapps.list", KConfig::NoGlobals, "xdgdata-apps");
321
322 if (!profile->isConfigWritable(true)) // warn user if mimeapps.list is root-owned (#155126/#94504)
323 return;
324
325 const QStringList oldAppServices = getAppOffers();
326 if (oldAppServices != m_appServices) {
327 // Save preferred services
328 KConfigGroup addedApps(profile, "Added Associations");
329 saveServices(addedApps, m_appServices);
330 KConfigGroup removedApps(profile, "Removed Associations");
331 saveRemovedServices(removedApps, m_appServices, oldAppServices);
332 }
333
334 const QStringList oldPartServices = getPartOffers();
335 if (oldPartServices != m_embedServices) {
336 // Handle removed services
337 KConfigGroup addedParts(profile, "Added KDE Service Associations");
338 saveServices(addedParts, m_embedServices);
339 KConfigGroup removedParts(profile, "Removed KDE Service Associations");
340 saveRemovedServices(removedParts, m_embedServices, oldPartServices);
341 }
342
343 m_appServicesModified = false;
344 m_embedServicesModified = false;
345}
346
347static QStringList collectStorageIds(const QStringList& services)
348{
349 QStringList serviceList;
350 QStringList::const_iterator it(services.begin());
351 for (int i = services.count(); it != services.end(); ++it, i--) {
352
353 KService::Ptr pService = KService::serviceByStorageId(*it);
354 if (!pService) {
355 kWarning() << "service with storage id" << *it << "not found";
356 continue; // Where did that one go?
357 }
358
359 serviceList.append(pService->storageId());
360 }
361 return serviceList;
362}
363
364void MimeTypeData::saveRemovedServices(KConfigGroup & config, const QStringList& services, const QStringList& oldServices)
365{
366 QStringList removedServiceList = config.readXdgListEntry(name());
367
368 Q_FOREACH(const QString& service, services) {
369 // If removedServiceList.contains(service), then it was previously removed but has been added back
370 removedServiceList.removeAll(service);
371 }
372 Q_FOREACH(const QString& oldService, oldServices) {
373 if (!services.contains(oldService)) {
374 // The service was in m_appServices (or m_embedServices) but has been removed
375 removedServiceList.append(oldService);
376 }
377 }
378 if (removedServiceList.isEmpty())
379 config.deleteEntry(name());
380 else
381 config.writeXdgListEntry(name(), removedServiceList);
382}
383
384void MimeTypeData::saveServices(KConfigGroup & config, const QStringList& services)
385{
386 if (services.isEmpty())
387 config.deleteEntry(name());
388 else
389 config.writeXdgListEntry(name(), collectStorageIds(services));
390}
391
392void MimeTypeData::refresh()
393{
394 if (m_isGroup)
395 return;
396
397 m_mimetype = KMimeType::mimeType( name() );
398 if (m_mimetype) {
399 if (m_bNewItem) {
400 kDebug() << "OK, created" << name();
401 m_bNewItem = false; // if this was a new mimetype, we just created it
402 }
403 if (!isMimeTypeDirty()) {
404 // Update from the xml, in case something was changed from out of this kcm
405 // (e.g. using KOpenWithDialog, or keditfiletype + kcmshell filetypes)
406 initFromKMimeType();
407 }
408 if (!m_appServicesModified && !m_embedServicesModified) {
409 m_bFullInit = false; // refresh services too
410 }
411 }
412}
413
414void MimeTypeData::getAskSave(bool &_askSave)
415{
416 if (m_askSave == AskSaveYes)
417 _askSave = true;
418 if (m_askSave == AskSaveNo)
419 _askSave = false;
420}
421
422void MimeTypeData::setAskSave(bool _askSave)
423{
424 m_askSave = _askSave ? AskSaveYes : AskSaveNo;
425}
426
427bool MimeTypeData::canUseGroupSetting() const
428{
429 // "Use group settings" isn't available for zip, tar etc.; those have a builtin default...
430 if (!m_mimetype) // e.g. new mimetype
431 return true;
432 const bool hasLocalProtocolRedirect = !KProtocolManager::protocolForArchiveMimetype(name()).isEmpty();
433 return !hasLocalProtocolRedirect;
434}
435
436void MimeTypeData::setPatterns(const QStringList &p)
437{
438 m_patterns = p;
439 // Sort them, since update-mime-database doesn't respect order (order of globs file != order of xml),
440 // and this code says things like if (m_mimetype->patterns() == m_patterns).
441 // We could also sort in KMimeType::setPatterns but this would just slow down the
442 // normal use case (anything else than this KCM) for no good reason.
443 m_patterns.sort();
444}
445
446bool MimeTypeData::matchesFilter(const QString& filter) const
447{
448 if (name().contains(filter, Qt::CaseInsensitive))
449 return true;
450
451 if (m_comment.contains(filter, Qt::CaseInsensitive))
452 return true;
453
454 if (!m_patterns.filter(filter, Qt::CaseInsensitive).isEmpty())
455 return true;
456
457 return false;
458}
459
460void MimeTypeData::setAppServices(const QStringList &dsl)
461{
462 m_appServices = dsl;
463 m_appServicesModified = true;
464}
465
466void MimeTypeData::setEmbedServices(const QStringList &dsl)
467{
468 m_embedServices = dsl;
469 m_embedServicesModified = true;
470}
471
472QString MimeTypeData::icon() const
473{
474 if (!m_userSpecifiedIcon.isEmpty())
475 return m_userSpecifiedIcon;
476 if (m_mimetype)
477 return m_mimetype->iconName();
478 return QString();
479}
480