1/*
2 Copyright (c) 2002 Marc Mutz <mutz@kde.org>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19
20// config keys:
21static const char configKeyDefaultIdentity[] = "Default Identity";
22
23#include "identitymanager.h"
24#include "identity.h" // for IdentityList::{export,import}Data
25
26#include <kpimutils/email.h> // for static helper functions
27
28#include <kemailsettings.h> // for IdentityEntry::fromControlCenter()
29#include <klocale.h>
30#include <klocalizedstring.h>
31#include <kglobal.h>
32#include <kdebug.h>
33#include <kconfig.h>
34#include <kuser.h>
35#include <kconfiggroup.h>
36
37#include <QList>
38#include <QRegExp>
39#include <QtDBus/QtDBus>
40#include <QHostInfo>
41
42#include <assert.h>
43#include <krandom.h>
44
45#include "identitymanageradaptor.h"
46
47using namespace KPIMIdentities;
48
49static QString newDBusObjectName()
50{
51 static int s_count = 0;
52 QString name( QLatin1String("/KPIMIDENTITIES_IdentityManager") );
53 if ( s_count++ ) {
54 name += QLatin1Char('_');
55 name += QString::number( s_count );
56 }
57 return name;
58}
59
60IdentityManager::IdentityManager( bool readonly, QObject *parent,
61 const char *name )
62 : QObject( parent )
63{
64 setObjectName( QLatin1String(name) );
65 KGlobal::locale()->insertCatalog( QLatin1String("libkpimidentities") );
66 new IdentityManagerAdaptor( this );
67 QDBusConnection dbus = QDBusConnection::sessionBus();
68 const QString dbusPath = newDBusObjectName();
69 setProperty( "uniqueDBusPath", dbusPath );
70 const QString dbusInterface = QLatin1String("org.kde.pim.IdentityManager");
71 dbus.registerObject( dbusPath, this );
72 dbus.connect( QString(), QString(), dbusInterface, QLatin1String("identitiesChanged"), this,
73 SLOT(slotIdentitiesChanged(QString)) );
74
75 mReadOnly = readonly;
76 mConfig = new KConfig( QLatin1String("emailidentities") );
77 readConfig( mConfig );
78 if ( mIdentities.isEmpty() ) {
79 kDebug( 5325 ) << "emailidentities is empty -> convert from kmailrc";
80 // No emailidentities file, or an empty one due to broken conversion
81 // (kconf_update bug in kdelibs <= 3.2.2)
82 // => convert it, i.e. read settings from kmailrc
83 KConfig kmailConf( QLatin1String("kmailrc") );
84 readConfig( &kmailConf );
85 }
86 // we need at least a default identity:
87 if ( mIdentities.isEmpty() ) {
88 kDebug( 5325 ) << "IdentityManager: No identity found. Creating default.";
89 createDefaultIdentity();
90 commit();
91 }
92
93 KConfig kmailConf( QLatin1String("kmail2rc") );
94 if (!mReadOnly) {
95 bool needCommit = false;
96 if (kmailConf.hasGroup(QLatin1String("Composer"))) {
97 KConfigGroup composerGroup = kmailConf.group(QLatin1String("Composer"));
98 if (composerGroup.hasKey(QLatin1String("pgp-auto-sign"))) {
99 const bool pgpAutoSign = composerGroup.readEntry(QLatin1String("pgp-auto-sign"), false);
100 QList<Identity>::iterator end = mIdentities.end();
101 for ( QList<Identity>::iterator it = mIdentities.begin(); it != end; ++it ) {
102 it->setPgpAutoSign(pgpAutoSign);
103 }
104 composerGroup.deleteEntry(QLatin1String("pgp-auto-sign"));
105 composerGroup.sync();
106 needCommit = true;
107 }
108 }
109 if (kmailConf.hasGroup(QLatin1String("General"))) {
110 KConfigGroup generalGroup = kmailConf.group(QLatin1String("General"));
111 if (generalGroup.hasKey(QLatin1String("Default domain"))) {
112 QString defaultDomain = generalGroup.readEntry(QLatin1String("Default domain"));
113 if (defaultDomain.isEmpty()) {
114 defaultDomain = QHostInfo::localHostName();
115 }
116 QList<Identity>::iterator end = mIdentities.end();
117 for ( QList<Identity>::iterator it = mIdentities.begin(); it != end; ++it ) {
118 it->setDefaultDomainName(defaultDomain);
119 }
120 generalGroup.deleteEntry(QLatin1String("Default domain"));
121 generalGroup.sync();
122 needCommit = true;
123 }
124 }
125 if (needCommit)
126 commit();
127 }
128
129 // Migration: people without settings in kemailsettings should get some
130 if ( KEMailSettings().getSetting( KEMailSettings::EmailAddress ).isEmpty() ) {
131 writeConfig();
132 }
133}
134
135IdentityManager::~IdentityManager()
136{
137 kWarning( hasPendingChanges(), 5325 )
138 << "IdentityManager: There were uncommitted changes!";
139 delete mConfig;
140}
141
142QString IdentityManager::makeUnique( const QString &name ) const
143{
144 int suffix = 1;
145 QString result = name;
146 while ( identities().contains( result ) ) {
147 result = i18nc( "%1: name; %2: number appended to it to make it unique "
148 "among a list of names", "%1 #%2",
149 name, suffix );
150 suffix++;
151 }
152 return result;
153}
154
155bool IdentityManager::isUnique( const QString &name ) const
156{
157 return !identities().contains( name );
158}
159
160void IdentityManager::commit()
161{
162 // early out:
163 if ( !hasPendingChanges() || mReadOnly ) {
164 return;
165 }
166
167 QList<uint> seenUOIDs;
168 QList<Identity>::ConstIterator end = mIdentities.constEnd();
169 for ( QList<Identity>::ConstIterator it = mIdentities.constBegin();
170 it != end; ++it ) {
171 seenUOIDs << ( *it ).uoid();
172 }
173
174 QList<uint> changedUOIDs;
175 // find added and changed identities:
176 for ( QList<Identity>::ConstIterator it = mShadowIdentities.constBegin();
177 it != mShadowIdentities.constEnd(); ++it ) {
178 int index = seenUOIDs.indexOf( ( *it ).uoid() );
179 if ( index != -1 ) {
180 uint uoid = seenUOIDs.at( index );
181 const Identity &orig = identityForUoid( uoid ); // look up in mIdentities
182 if ( *it != orig ) {
183 // changed identity
184 kDebug( 5325 ) << "emitting changed() for identity" << uoid;
185 emit changed( *it );
186 changedUOIDs << uoid;
187 }
188 seenUOIDs.removeAll( uoid );
189 } else {
190 // new identity
191 kDebug( 5325 ) << "emitting added() for identity" << ( *it ).uoid();
192 emit added( *it );
193 }
194 }
195
196 // what's left are deleted identities:
197 for ( QList<uint>::ConstIterator it = seenUOIDs.constBegin();
198 it != seenUOIDs.constEnd(); ++it ) {
199 kDebug( 5325 ) << "emitting deleted() for identity" << ( *it );
200 emit deleted( *it );
201 }
202
203 mIdentities = mShadowIdentities;
204 writeConfig();
205
206 // now that mIdentities has all the new info, we can emit the added/changed
207 // signals that ship a uoid. This is because the slots might use
208 // identityForUoid(uoid)...
209 QList<uint>::ConstIterator changedEnd( changedUOIDs.constEnd() );
210 for ( QList<uint>::ConstIterator it = changedUOIDs.constBegin();
211 it != changedEnd; ++it ) {
212 emit changed( *it );
213 }
214
215 emit changed(); // normal signal
216
217 // DBus signal for other IdentityManager instances
218 const QString ourIdentifier = QString::fromLatin1( "%1/%2" ).
219 arg( QDBusConnection::sessionBus().baseService() ).
220 arg( property( "uniqueDBusPath" ).toString() );
221 emit identitiesChanged( ourIdentifier );
222}
223
224void IdentityManager::rollback()
225{
226 mShadowIdentities = mIdentities;
227}
228
229bool IdentityManager::hasPendingChanges() const
230{
231 return mIdentities != mShadowIdentities;
232}
233
234QStringList IdentityManager::identities() const
235{
236 QStringList result;
237 ConstIterator end = mIdentities.constEnd();
238 for ( ConstIterator it = mIdentities.constBegin();
239 it != end; ++it ) {
240 result << ( *it ).identityName();
241 }
242 return result;
243}
244
245QStringList IdentityManager::shadowIdentities() const
246{
247 QStringList result;
248 ConstIterator end = mShadowIdentities.constEnd();
249 for ( ConstIterator it = mShadowIdentities.constBegin();
250 it != end; ++it ) {
251 result << ( *it ).identityName();
252 }
253 return result;
254}
255
256void IdentityManager::sort()
257{
258 qSort( mShadowIdentities );
259}
260
261void IdentityManager::writeConfig() const
262{
263 const QStringList identities = groupList( mConfig );
264 QStringList::const_iterator groupEnd = identities.constEnd();
265 for ( QStringList::const_iterator group = identities.constBegin();
266 group != groupEnd; ++group ) {
267 mConfig->deleteGroup( *group );
268 }
269 int i = 0;
270 ConstIterator end = mIdentities.constEnd();
271 for ( ConstIterator it = mIdentities.constBegin();
272 it != end; ++it, ++i ) {
273 KConfigGroup cg( mConfig, QString::fromLatin1( "Identity #%1" ).arg( i ) );
274 ( *it ).writeConfig( cg );
275 if ( ( *it ).isDefault() ) {
276 // remember which one is default:
277 KConfigGroup general( mConfig, "General" );
278 general.writeEntry( configKeyDefaultIdentity, ( *it ).uoid() );
279
280 // Also write the default identity to emailsettings
281 KEMailSettings es;
282 es.setSetting( KEMailSettings::RealName, ( *it ).fullName() );
283 es.setSetting( KEMailSettings::EmailAddress, ( *it ).primaryEmailAddress() );
284 es.setSetting( KEMailSettings::Organization, ( *it ).organization() );
285 es.setSetting( KEMailSettings::ReplyToAddress, ( *it ).replyToAddr() );
286 }
287 }
288 mConfig->sync();
289
290}
291
292void IdentityManager::readConfig( KConfig *config )
293{
294 mIdentities.clear();
295
296 const QStringList identities = groupList( config );
297 if ( identities.isEmpty() ) {
298 return; // nothing to be done...
299 }
300
301 KConfigGroup general( config, "General" );
302 uint defaultIdentity = general.readEntry( configKeyDefaultIdentity, 0 );
303 bool haveDefault = false;
304 QStringList::const_iterator groupEnd = identities.constEnd();
305 for ( QStringList::const_iterator group = identities.constBegin();
306 group != groupEnd; ++group ) {
307 KConfigGroup configGroup( config, *group );
308 mIdentities << Identity();
309 mIdentities.last().readConfig( configGroup );
310 if ( !haveDefault && mIdentities.last().uoid() == defaultIdentity ) {
311 haveDefault = true;
312 mIdentities.last().setIsDefault( true );
313 }
314 }
315
316 if ( !haveDefault ) {
317 kWarning( 5325 ) << "IdentityManager: There was no default identity."
318 << "Marking first one as default.";
319 mIdentities.first().setIsDefault( true );
320 }
321 qSort( mIdentities );
322
323 mShadowIdentities = mIdentities;
324}
325
326QStringList IdentityManager::groupList( KConfig *config ) const
327{
328 return config->groupList().filter( QRegExp( QLatin1String("^Identity #\\d+$") ) );
329}
330
331IdentityManager::ConstIterator IdentityManager::begin() const
332{
333 return mIdentities.begin();
334}
335
336IdentityManager::ConstIterator IdentityManager::end() const
337{
338 return mIdentities.end();
339}
340
341IdentityManager::Iterator IdentityManager::modifyBegin()
342{
343 return mShadowIdentities.begin();
344}
345
346IdentityManager::Iterator IdentityManager::modifyEnd()
347{
348 return mShadowIdentities.end();
349}
350
351const Identity &IdentityManager::identityForUoid( uint uoid ) const
352{
353 for ( ConstIterator it = begin(); it != end(); ++it ) {
354 if ( ( *it ).uoid() == uoid ) {
355 return ( *it );
356 }
357 }
358 return Identity::null();
359}
360
361const Identity &IdentityManager::identityForUoidOrDefault( uint uoid ) const
362{
363 const Identity &ident = identityForUoid( uoid );
364 if ( ident.isNull() ) {
365 return defaultIdentity();
366 } else {
367 return ident;
368 }
369}
370
371const Identity &IdentityManager::identityForAddress(
372 const QString &addresses ) const
373{
374 const QStringList addressList = KPIMUtils::splitAddressList( addresses );
375 foreach ( const QString &fullAddress, addressList ) {
376 const QString addrSpec = KPIMUtils::extractEmailAddress( fullAddress ).toLower();
377 for ( ConstIterator it = begin(); it != end(); ++it ) {
378 const Identity &identity = *it;
379 if ( identity.matchesEmailAddress( addrSpec ) ) {
380 return identity;
381 }
382 }
383 }
384 return Identity::null();
385}
386
387bool IdentityManager::thatIsMe( const QString &addressList ) const
388{
389 return !identityForAddress( addressList ).isNull();
390}
391
392Identity &IdentityManager::modifyIdentityForName( const QString &name )
393{
394 for ( Iterator it = modifyBegin(); it != modifyEnd(); ++it ) {
395 if ( ( *it ).identityName() == name ) {
396 return ( *it );
397 }
398 }
399
400 kWarning( 5325 ) << "IdentityManager::modifyIdentityForName() used as"
401 << "newFromScratch() replacement!"
402 << endl << " name == \"" << name << "\"";
403 return newFromScratch( name );
404}
405
406Identity &IdentityManager::modifyIdentityForUoid( uint uoid )
407{
408 for ( Iterator it = modifyBegin(); it != modifyEnd(); ++it ) {
409 if ( ( *it ).uoid() == uoid ) {
410 return ( *it );
411 }
412 }
413
414 kWarning( 5325 ) << "IdentityManager::identityForUoid() used as"
415 << "newFromScratch() replacement!"
416 << endl << " uoid == \"" << uoid << "\"";
417 return newFromScratch( i18n( "Unnamed" ) );
418}
419
420const Identity &IdentityManager::defaultIdentity() const
421{
422 for ( ConstIterator it = begin(); it != end(); ++it ) {
423 if ( ( *it ).isDefault() ) {
424 return ( *it );
425 }
426 }
427
428 if ( mIdentities.isEmpty() ) {
429 kFatal( 5325 ) << "IdentityManager: No default identity found!";
430 } else {
431 kWarning( 5325 ) << "IdentityManager: No default identity found!";
432 }
433 return *begin();
434}
435
436bool IdentityManager::setAsDefault( uint uoid )
437{
438 // First, check if the identity actually exists:
439 bool found = false;
440 for ( ConstIterator it = mShadowIdentities.constBegin();
441 it != mShadowIdentities.constEnd(); ++it ) {
442 if ( ( *it ).uoid() == uoid ) {
443 found = true;
444 break;
445 }
446 }
447
448 if ( !found ) {
449 return false;
450 }
451
452 // Then, change the default as requested:
453 for ( Iterator it = modifyBegin(); it != modifyEnd(); ++it ) {
454 ( *it ).setIsDefault( ( *it ).uoid() == uoid );
455 }
456
457 // and re-sort:
458 sort();
459 return true;
460}
461
462bool IdentityManager::removeIdentity( const QString &name )
463{
464 if ( mShadowIdentities.size() <= 1 ) {
465 return false;
466 }
467
468 for ( Iterator it = modifyBegin(); it != modifyEnd(); ++it ) {
469 if ( ( *it ).identityName() == name ) {
470 bool removedWasDefault = ( *it ).isDefault();
471 mShadowIdentities.erase( it );
472 if ( removedWasDefault && !mShadowIdentities.isEmpty() ) {
473 mShadowIdentities.first().setIsDefault( true );
474 }
475 return true;
476 }
477 }
478 return false;
479}
480
481bool IdentityManager::removeIdentityForced( const QString &name )
482{
483 for ( Iterator it = modifyBegin(); it != modifyEnd(); ++it ) {
484 if ( ( *it ).identityName() == name ) {
485 bool removedWasDefault = ( *it ).isDefault();
486 mShadowIdentities.erase( it );
487 if ( removedWasDefault && !mShadowIdentities.isEmpty() ) {
488 mShadowIdentities.first().setIsDefault( true );
489 }
490 return true;
491 }
492 }
493 return false;
494}
495
496Identity &IdentityManager::newFromScratch( const QString &name )
497{
498 return newFromExisting( Identity( name ) );
499}
500
501Identity &IdentityManager::newFromControlCenter( const QString &name )
502{
503 KEMailSettings es;
504 es.setProfile( es.defaultProfileName() );
505
506 return
507 newFromExisting( Identity( name,
508 es.getSetting( KEMailSettings::RealName ),
509 es.getSetting( KEMailSettings::EmailAddress ),
510 es.getSetting( KEMailSettings::Organization ),
511 es.getSetting( KEMailSettings::ReplyToAddress ) ) );
512}
513
514Identity &IdentityManager::newFromExisting( const Identity &other, const QString &name )
515{
516 mShadowIdentities << other;
517 Identity &result = mShadowIdentities.last();
518 result.setIsDefault( false ); // we don't want two default identities!
519 result.setUoid( newUoid() ); // we don't want two identies w/ same UOID
520 if ( !name.isNull() ) {
521 result.setIdentityName( name );
522 }
523 return result;
524}
525
526void IdentityManager::createDefaultIdentity()
527{
528 QString fullName, emailAddress;
529 bool done = false;
530
531 // Check if the application has any settings
532 createDefaultIdentity( fullName, emailAddress );
533
534 // If not, then use the kcontrol settings
535 if ( fullName.isEmpty() && emailAddress.isEmpty() ) {
536 KEMailSettings emailSettings;
537 fullName = emailSettings.getSetting( KEMailSettings::RealName );
538 emailAddress = emailSettings.getSetting( KEMailSettings::EmailAddress );
539
540 if ( !fullName.isEmpty() && !emailAddress.isEmpty() ) {
541 newFromControlCenter( i18nc( "use default address from control center",
542 "Default" ) );
543 done = true;
544 } else {
545 // If KEmailSettings doesn't have name and address, generate something from KUser
546 KUser user;
547 if ( fullName.isEmpty() ) {
548 fullName = user.property( KUser::FullName ).toString();
549 }
550 if ( emailAddress.isEmpty() ) {
551 emailAddress = user.loginName();
552 if ( !emailAddress.isEmpty() ) {
553 KConfigGroup general( mConfig, "General" );
554 QString defaultdomain = general.readEntry( "Default domain" );
555 if ( !defaultdomain.isEmpty() ) {
556 emailAddress += QLatin1Char('@') + defaultdomain;
557 } else {
558 emailAddress.clear();
559 }
560 }
561 }
562 }
563 }
564
565 if ( !done ) {
566 // Default identity name
567 QString name( i18nc( "Default name for new email accounts/identities.", "Unnamed" ) );
568
569 if ( !emailAddress.isEmpty() ) {
570 // If we have an email address, create a default identity name from it
571 QString idName = emailAddress;
572 int pos = idName.indexOf( QLatin1Char('@') );
573 if ( pos != -1 ) {
574 name = idName.mid( pos + 1, -1 );
575 }
576
577 // Make the name a bit more human friendly
578 name.replace( QLatin1Char('.'), QLatin1Char(' ') );
579 pos = name.indexOf( QLatin1Char(' ') );
580 if ( pos != 0 ) {
581 name[pos + 1] = name[pos + 1].toUpper();
582 }
583 name[0] = name[0].toUpper();
584 } else if ( !fullName.isEmpty() ) {
585 // If we have a full name, create a default identity name from it
586 name = fullName;
587 }
588 mShadowIdentities << Identity( name, fullName, emailAddress );
589 }
590
591 mShadowIdentities.last().setIsDefault( true );
592 mShadowIdentities.last().setUoid( newUoid() );
593 if ( mReadOnly ) { // commit won't do it in readonly mode
594 mIdentities = mShadowIdentities;
595 }
596}
597
598int IdentityManager::newUoid()
599{
600 int uoid;
601
602 // determine the UOIDs of all saved identities
603 QList<uint> usedUOIDs;
604 QList<Identity>::ConstIterator end( mIdentities.constEnd() );
605 for ( QList<Identity>::ConstIterator it = mIdentities.constBegin();
606 it != end; ++it ) {
607 usedUOIDs << ( *it ).uoid();
608 }
609
610 if ( hasPendingChanges() ) {
611 // add UOIDs of all shadow identities. Yes, we will add a lot of duplicate
612 // UOIDs, but avoiding duplicate UOIDs isn't worth the effort.
613 QList<Identity>::ConstIterator endShadow( mShadowIdentities.constEnd() );
614 for ( QList<Identity>::ConstIterator it = mShadowIdentities.constBegin();
615 it != endShadow; ++it ) {
616 usedUOIDs << ( *it ).uoid();
617 }
618 }
619
620 usedUOIDs << 0; // no UOID must be 0 because this value always refers to the
621 // default identity
622
623 do {
624 uoid = KRandom::random();
625 } while ( usedUOIDs.indexOf( uoid ) != -1 );
626
627 return uoid;
628}
629
630QStringList KPIMIdentities::IdentityManager::allEmails() const
631{
632 QStringList lst;
633 for ( ConstIterator it = begin(); it != end(); ++it ) {
634 lst << ( *it ).primaryEmailAddress();
635 if ( !( *it ).emailAliases().isEmpty() ) {
636 lst << ( *it ).emailAliases();
637 }
638 }
639 return lst;
640}
641
642void KPIMIdentities::IdentityManager::slotRollback()
643{
644 rollback();
645}
646
647void KPIMIdentities::IdentityManager::slotIdentitiesChanged( const QString &id )
648{
649 kDebug( 5325 ) << " KPIMIdentities::IdentityManager::slotIdentitiesChanged :" << id;
650 const QString ourIdentifier = QString::fromLatin1( "%1/%2" ).
651 arg( QDBusConnection::sessionBus().baseService() ).
652 arg( property( "uniqueDBusPath" ).toString() );
653 if ( id != ourIdentifier ) {
654 mConfig->reparseConfiguration();
655 Q_ASSERT( !hasPendingChanges() );
656 readConfig( mConfig );
657 emit changed();
658 }
659}
660
661