/* Copyright (c) 2002 Marc Mutz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // config keys: static const char configKeyDefaultIdentity[] = "Default Identity"; #include "identitymanager.h" #include "identity.h" // for IdentityList::{export,import}Data #include // for static helper functions #include // for IdentityEntry::fromControlCenter() #include #include #include #include #include #include #include #include #include #include #include #include #include #include "identitymanageradaptor.h" using namespace KPIMIdentities; static QString newDBusObjectName() { static int s_count = 0; QString name( QLatin1String("/KPIMIDENTITIES_IdentityManager") ); if ( s_count++ ) { name += QLatin1Char('_'); name += QString::number( s_count ); } return name; } IdentityManager::IdentityManager( bool readonly, QObject *parent, const char *name ) : QObject( parent ) { setObjectName( QLatin1String(name) ); KGlobal::locale()->insertCatalog( QLatin1String("libkpimidentities") ); new IdentityManagerAdaptor( this ); QDBusConnection dbus = QDBusConnection::sessionBus(); const QString dbusPath = newDBusObjectName(); setProperty( "uniqueDBusPath", dbusPath ); const QString dbusInterface = QLatin1String("org.kde.pim.IdentityManager"); dbus.registerObject( dbusPath, this ); dbus.connect( QString(), QString(), dbusInterface, QLatin1String("identitiesChanged"), this, SLOT(slotIdentitiesChanged(QString)) ); mReadOnly = readonly; mConfig = new KConfig( QLatin1String("emailidentities") ); readConfig( mConfig ); if ( mIdentities.isEmpty() ) { kDebug( 5325 ) << "emailidentities is empty -> convert from kmailrc"; // No emailidentities file, or an empty one due to broken conversion // (kconf_update bug in kdelibs <= 3.2.2) // => convert it, i.e. read settings from kmailrc KConfig kmailConf( QLatin1String("kmailrc") ); readConfig( &kmailConf ); } // we need at least a default identity: if ( mIdentities.isEmpty() ) { kDebug( 5325 ) << "IdentityManager: No identity found. Creating default."; createDefaultIdentity(); commit(); } KConfig kmailConf( QLatin1String("kmail2rc") ); if (!mReadOnly) { bool needCommit = false; if (kmailConf.hasGroup(QLatin1String("Composer"))) { KConfigGroup composerGroup = kmailConf.group(QLatin1String("Composer")); if (composerGroup.hasKey(QLatin1String("pgp-auto-sign"))) { const bool pgpAutoSign = composerGroup.readEntry(QLatin1String("pgp-auto-sign"), false); QList::iterator end = mIdentities.end(); for ( QList::iterator it = mIdentities.begin(); it != end; ++it ) { it->setPgpAutoSign(pgpAutoSign); } composerGroup.deleteEntry(QLatin1String("pgp-auto-sign")); composerGroup.sync(); needCommit = true; } } if (kmailConf.hasGroup(QLatin1String("General"))) { KConfigGroup generalGroup = kmailConf.group(QLatin1String("General")); if (generalGroup.hasKey(QLatin1String("Default domain"))) { QString defaultDomain = generalGroup.readEntry(QLatin1String("Default domain")); if (defaultDomain.isEmpty()) { defaultDomain = QHostInfo::localHostName(); } QList::iterator end = mIdentities.end(); for ( QList::iterator it = mIdentities.begin(); it != end; ++it ) { it->setDefaultDomainName(defaultDomain); } generalGroup.deleteEntry(QLatin1String("Default domain")); generalGroup.sync(); needCommit = true; } } if (needCommit) commit(); } // Migration: people without settings in kemailsettings should get some if ( KEMailSettings().getSetting( KEMailSettings::EmailAddress ).isEmpty() ) { writeConfig(); } } IdentityManager::~IdentityManager() { kWarning( hasPendingChanges(), 5325 ) << "IdentityManager: There were uncommitted changes!"; delete mConfig; } QString IdentityManager::makeUnique( const QString &name ) const { int suffix = 1; QString result = name; while ( identities().contains( result ) ) { result = i18nc( "%1: name; %2: number appended to it to make it unique " "among a list of names", "%1 #%2", name, suffix ); suffix++; } return result; } bool IdentityManager::isUnique( const QString &name ) const { return !identities().contains( name ); } void IdentityManager::commit() { // early out: if ( !hasPendingChanges() || mReadOnly ) { return; } QList seenUOIDs; QList::ConstIterator end = mIdentities.constEnd(); for ( QList::ConstIterator it = mIdentities.constBegin(); it != end; ++it ) { seenUOIDs << ( *it ).uoid(); } QList changedUOIDs; // find added and changed identities: for ( QList::ConstIterator it = mShadowIdentities.constBegin(); it != mShadowIdentities.constEnd(); ++it ) { int index = seenUOIDs.indexOf( ( *it ).uoid() ); if ( index != -1 ) { uint uoid = seenUOIDs.at( index ); const Identity &orig = identityForUoid( uoid ); // look up in mIdentities if ( *it != orig ) { // changed identity kDebug( 5325 ) << "emitting changed() for identity" << uoid; emit changed( *it ); changedUOIDs << uoid; } seenUOIDs.removeAll( uoid ); } else { // new identity kDebug( 5325 ) << "emitting added() for identity" << ( *it ).uoid(); emit added( *it ); } } // what's left are deleted identities: for ( QList::ConstIterator it = seenUOIDs.constBegin(); it != seenUOIDs.constEnd(); ++it ) { kDebug( 5325 ) << "emitting deleted() for identity" << ( *it ); emit deleted( *it ); } mIdentities = mShadowIdentities; writeConfig(); // now that mIdentities has all the new info, we can emit the added/changed // signals that ship a uoid. This is because the slots might use // identityForUoid(uoid)... QList::ConstIterator changedEnd( changedUOIDs.constEnd() ); for ( QList::ConstIterator it = changedUOIDs.constBegin(); it != changedEnd; ++it ) { emit changed( *it ); } emit changed(); // normal signal // DBus signal for other IdentityManager instances const QString ourIdentifier = QString::fromLatin1( "%1/%2" ). arg( QDBusConnection::sessionBus().baseService() ). arg( property( "uniqueDBusPath" ).toString() ); emit identitiesChanged( ourIdentifier ); } void IdentityManager::rollback() { mShadowIdentities = mIdentities; } bool IdentityManager::hasPendingChanges() const { return mIdentities != mShadowIdentities; } QStringList IdentityManager::identities() const { QStringList result; ConstIterator end = mIdentities.constEnd(); for ( ConstIterator it = mIdentities.constBegin(); it != end; ++it ) { result << ( *it ).identityName(); } return result; } QStringList IdentityManager::shadowIdentities() const { QStringList result; ConstIterator end = mShadowIdentities.constEnd(); for ( ConstIterator it = mShadowIdentities.constBegin(); it != end; ++it ) { result << ( *it ).identityName(); } return result; } void IdentityManager::sort() { qSort( mShadowIdentities ); } void IdentityManager::writeConfig() const { const QStringList identities = groupList( mConfig ); QStringList::const_iterator groupEnd = identities.constEnd(); for ( QStringList::const_iterator group = identities.constBegin(); group != groupEnd; ++group ) { mConfig->deleteGroup( *group ); } int i = 0; ConstIterator end = mIdentities.constEnd(); for ( ConstIterator it = mIdentities.constBegin(); it != end; ++it, ++i ) { KConfigGroup cg( mConfig, QString::fromLatin1( "Identity #%1" ).arg( i ) ); ( *it ).writeConfig( cg ); if ( ( *it ).isDefault() ) { // remember which one is default: KConfigGroup general( mConfig, "General" ); general.writeEntry( configKeyDefaultIdentity, ( *it ).uoid() ); // Also write the default identity to emailsettings KEMailSettings es; es.setSetting( KEMailSettings::RealName, ( *it ).fullName() ); es.setSetting( KEMailSettings::EmailAddress, ( *it ).primaryEmailAddress() ); es.setSetting( KEMailSettings::Organization, ( *it ).organization() ); es.setSetting( KEMailSettings::ReplyToAddress, ( *it ).replyToAddr() ); } } mConfig->sync(); } void IdentityManager::readConfig( KConfig *config ) { mIdentities.clear(); const QStringList identities = groupList( config ); if ( identities.isEmpty() ) { return; // nothing to be done... } KConfigGroup general( config, "General" ); uint defaultIdentity = general.readEntry( configKeyDefaultIdentity, 0 ); bool haveDefault = false; QStringList::const_iterator groupEnd = identities.constEnd(); for ( QStringList::const_iterator group = identities.constBegin(); group != groupEnd; ++group ) { KConfigGroup configGroup( config, *group ); mIdentities << Identity(); mIdentities.last().readConfig( configGroup ); if ( !haveDefault && mIdentities.last().uoid() == defaultIdentity ) { haveDefault = true; mIdentities.last().setIsDefault( true ); } } if ( !haveDefault ) { kWarning( 5325 ) << "IdentityManager: There was no default identity." << "Marking first one as default."; mIdentities.first().setIsDefault( true ); } qSort( mIdentities ); mShadowIdentities = mIdentities; } QStringList IdentityManager::groupList( KConfig *config ) const { return config->groupList().filter( QRegExp( QLatin1String("^Identity #\\d+$") ) ); } IdentityManager::ConstIterator IdentityManager::begin() const { return mIdentities.begin(); } IdentityManager::ConstIterator IdentityManager::end() const { return mIdentities.end(); } IdentityManager::Iterator IdentityManager::modifyBegin() { return mShadowIdentities.begin(); } IdentityManager::Iterator IdentityManager::modifyEnd() { return mShadowIdentities.end(); } const Identity &IdentityManager::identityForUoid( uint uoid ) const { for ( ConstIterator it = begin(); it != end(); ++it ) { if ( ( *it ).uoid() == uoid ) { return ( *it ); } } return Identity::null(); } const Identity &IdentityManager::identityForUoidOrDefault( uint uoid ) const { const Identity &ident = identityForUoid( uoid ); if ( ident.isNull() ) { return defaultIdentity(); } else { return ident; } } const Identity &IdentityManager::identityForAddress( const QString &addresses ) const { const QStringList addressList = KPIMUtils::splitAddressList( addresses ); foreach ( const QString &fullAddress, addressList ) { const QString addrSpec = KPIMUtils::extractEmailAddress( fullAddress ).toLower(); for ( ConstIterator it = begin(); it != end(); ++it ) { const Identity &identity = *it; if ( identity.matchesEmailAddress( addrSpec ) ) { return identity; } } } return Identity::null(); } bool IdentityManager::thatIsMe( const QString &addressList ) const { return !identityForAddress( addressList ).isNull(); } Identity &IdentityManager::modifyIdentityForName( const QString &name ) { for ( Iterator it = modifyBegin(); it != modifyEnd(); ++it ) { if ( ( *it ).identityName() == name ) { return ( *it ); } } kWarning( 5325 ) << "IdentityManager::modifyIdentityForName() used as" << "newFromScratch() replacement!" << endl << " name == \"" << name << "\""; return newFromScratch( name ); } Identity &IdentityManager::modifyIdentityForUoid( uint uoid ) { for ( Iterator it = modifyBegin(); it != modifyEnd(); ++it ) { if ( ( *it ).uoid() == uoid ) { return ( *it ); } } kWarning( 5325 ) << "IdentityManager::identityForUoid() used as" << "newFromScratch() replacement!" << endl << " uoid == \"" << uoid << "\""; return newFromScratch( i18n( "Unnamed" ) ); } const Identity &IdentityManager::defaultIdentity() const { for ( ConstIterator it = begin(); it != end(); ++it ) { if ( ( *it ).isDefault() ) { return ( *it ); } } if ( mIdentities.isEmpty() ) { kFatal( 5325 ) << "IdentityManager: No default identity found!"; } else { kWarning( 5325 ) << "IdentityManager: No default identity found!"; } return *begin(); } bool IdentityManager::setAsDefault( uint uoid ) { // First, check if the identity actually exists: bool found = false; for ( ConstIterator it = mShadowIdentities.constBegin(); it != mShadowIdentities.constEnd(); ++it ) { if ( ( *it ).uoid() == uoid ) { found = true; break; } } if ( !found ) { return false; } // Then, change the default as requested: for ( Iterator it = modifyBegin(); it != modifyEnd(); ++it ) { ( *it ).setIsDefault( ( *it ).uoid() == uoid ); } // and re-sort: sort(); return true; } bool IdentityManager::removeIdentity( const QString &name ) { if ( mShadowIdentities.size() <= 1 ) { return false; } for ( Iterator it = modifyBegin(); it != modifyEnd(); ++it ) { if ( ( *it ).identityName() == name ) { bool removedWasDefault = ( *it ).isDefault(); mShadowIdentities.erase( it ); if ( removedWasDefault && !mShadowIdentities.isEmpty() ) { mShadowIdentities.first().setIsDefault( true ); } return true; } } return false; } bool IdentityManager::removeIdentityForced( const QString &name ) { for ( Iterator it = modifyBegin(); it != modifyEnd(); ++it ) { if ( ( *it ).identityName() == name ) { bool removedWasDefault = ( *it ).isDefault(); mShadowIdentities.erase( it ); if ( removedWasDefault && !mShadowIdentities.isEmpty() ) { mShadowIdentities.first().setIsDefault( true ); } return true; } } return false; } Identity &IdentityManager::newFromScratch( const QString &name ) { return newFromExisting( Identity( name ) ); } Identity &IdentityManager::newFromControlCenter( const QString &name ) { KEMailSettings es; es.setProfile( es.defaultProfileName() ); return newFromExisting( Identity( name, es.getSetting( KEMailSettings::RealName ), es.getSetting( KEMailSettings::EmailAddress ), es.getSetting( KEMailSettings::Organization ), es.getSetting( KEMailSettings::ReplyToAddress ) ) ); } Identity &IdentityManager::newFromExisting( const Identity &other, const QString &name ) { mShadowIdentities << other; Identity &result = mShadowIdentities.last(); result.setIsDefault( false ); // we don't want two default identities! result.setUoid( newUoid() ); // we don't want two identies w/ same UOID if ( !name.isNull() ) { result.setIdentityName( name ); } return result; } void IdentityManager::createDefaultIdentity() { QString fullName, emailAddress; bool done = false; // Check if the application has any settings createDefaultIdentity( fullName, emailAddress ); // If not, then use the kcontrol settings if ( fullName.isEmpty() && emailAddress.isEmpty() ) { KEMailSettings emailSettings; fullName = emailSettings.getSetting( KEMailSettings::RealName ); emailAddress = emailSettings.getSetting( KEMailSettings::EmailAddress ); if ( !fullName.isEmpty() && !emailAddress.isEmpty() ) { newFromControlCenter( i18nc( "use default address from control center", "Default" ) ); done = true; } else { // If KEmailSettings doesn't have name and address, generate something from KUser KUser user; if ( fullName.isEmpty() ) { fullName = user.property( KUser::FullName ).toString(); } if ( emailAddress.isEmpty() ) { emailAddress = user.loginName(); if ( !emailAddress.isEmpty() ) { KConfigGroup general( mConfig, "General" ); QString defaultdomain = general.readEntry( "Default domain" ); if ( !defaultdomain.isEmpty() ) { emailAddress += QLatin1Char('@') + defaultdomain; } else { emailAddress.clear(); } } } } } if ( !done ) { // Default identity name QString name( i18nc( "Default name for new email accounts/identities.", "Unnamed" ) ); if ( !emailAddress.isEmpty() ) { // If we have an email address, create a default identity name from it QString idName = emailAddress; int pos = idName.indexOf( QLatin1Char('@') ); if ( pos != -1 ) { name = idName.mid( pos + 1, -1 ); } // Make the name a bit more human friendly name.replace( QLatin1Char('.'), QLatin1Char(' ') ); pos = name.indexOf( QLatin1Char(' ') ); if ( pos != 0 ) { name[pos + 1] = name[pos + 1].toUpper(); } name[0] = name[0].toUpper(); } else if ( !fullName.isEmpty() ) { // If we have a full name, create a default identity name from it name = fullName; } mShadowIdentities << Identity( name, fullName, emailAddress ); } mShadowIdentities.last().setIsDefault( true ); mShadowIdentities.last().setUoid( newUoid() ); if ( mReadOnly ) { // commit won't do it in readonly mode mIdentities = mShadowIdentities; } } int IdentityManager::newUoid() { int uoid; // determine the UOIDs of all saved identities QList usedUOIDs; QList::ConstIterator end( mIdentities.constEnd() ); for ( QList::ConstIterator it = mIdentities.constBegin(); it != end; ++it ) { usedUOIDs << ( *it ).uoid(); } if ( hasPendingChanges() ) { // add UOIDs of all shadow identities. Yes, we will add a lot of duplicate // UOIDs, but avoiding duplicate UOIDs isn't worth the effort. QList::ConstIterator endShadow( mShadowIdentities.constEnd() ); for ( QList::ConstIterator it = mShadowIdentities.constBegin(); it != endShadow; ++it ) { usedUOIDs << ( *it ).uoid(); } } usedUOIDs << 0; // no UOID must be 0 because this value always refers to the // default identity do { uoid = KRandom::random(); } while ( usedUOIDs.indexOf( uoid ) != -1 ); return uoid; } QStringList KPIMIdentities::IdentityManager::allEmails() const { QStringList lst; for ( ConstIterator it = begin(); it != end(); ++it ) { lst << ( *it ).primaryEmailAddress(); if ( !( *it ).emailAliases().isEmpty() ) { lst << ( *it ).emailAliases(); } } return lst; } void KPIMIdentities::IdentityManager::slotRollback() { rollback(); } void KPIMIdentities::IdentityManager::slotIdentitiesChanged( const QString &id ) { kDebug( 5325 ) << " KPIMIdentities::IdentityManager::slotIdentitiesChanged :" << id; const QString ourIdentifier = QString::fromLatin1( "%1/%2" ). arg( QDBusConnection::sessionBus().baseService() ). arg( property( "uniqueDBusPath" ).toString() ); if ( id != ourIdentifier ) { mConfig->reparseConfiguration(); Q_ASSERT( !hasPendingChanges() ); readConfig( mConfig ); emit changed(); } }