[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-pim
Subject:    Re: [Kde-pim] Bugs in libkabc
From:       Tobias Koenig <tokoe82 () yahoo ! de>
Date:       2002-10-13 22:14:51
[Download RAW message or body]

On Fri, Oct 11, 2002 at 07:03:35PM +0200, Tobias K?nig wrote:
> Hi,
Hi again,

Since the Telekom was a bit faster this time I've got the internet
connection already today, so here is the patch for the below mentioned bugs.

> I've discovered some bugs in libkabc and have the
> patches already on my hard disc but can't commit it
> until friday next week.
>
> The first bug is the 'save()' call in the destructor
> of StdAddressBook. So the address book is always
> stored
> even when 'Don't save changes' is selected in
> KAddressBook.
> (Fixed in CVS HEAD now)
>
> The second problem is the adding/removing addresses
> to/from
> resources. That fails with the current implementation
> in some cases.
>
> The third bug is an older bug that causes libkabc to
> store the wrong REV timestamp. I found the problem
> in KAddressBook undocmds.cpp and have the fix already
> here.

Can I commit it?

Ciao,
Tobias
--
In a world without walls and fences who
needs Windows and Gates???

["libkabc.diff" (text/plain)]

? libkabc.diff
Index: addressbook.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/addressbook.cpp,v
retrieving revision 1.47
diff -u -b -p -r1.47 addressbook.cpp
--- addressbook.cpp	2002/09/30 15:44:07	1.47
+++ addressbook.cpp	2002/10/13 22:01:31
@@ -222,17 +222,12 @@ bool AddressBook::load()
 
   Resource *r;
   bool ok = true;
-  for( r = d->mResources.first(); r; r = d->mResources.next() )
+  for ( r = d->mResources.first(); r; r = d->mResources.next() )
     if ( !r->load() ) {
       error( i18n("Unable to load resource '%1'").arg( r->name() ) );
       ok = false;
     }
 
-  // mark all addressees as unchanged
-  Addressee::List::Iterator it;
-  for ( it = d->mAddressees.begin(); it != d->mAddressees.end(); ++it )
-    (*it).setChanged( false );
-
   return ok;
 }
 
@@ -240,10 +235,8 @@ bool AddressBook::save( Ticket *ticket )
 {
   kdDebug(5700) << "AddressBook::save()"<< endl;
 
-  if ( ticket->resource() ) {
-    deleteRemovedAddressees();
+  if ( ticket->resource() )
     return ticket->resource()->save( ticket );
-  }
 
   return false;
 }
@@ -307,15 +300,18 @@ void AddressBook::insertAddressee( const
       Addressee addr = a;
       if ( addr != (*it) )
         changed = true;
+
+      (*it) = addr;
 
-      (*it) = a;
       if ( (*it).resource() == 0 )
         (*it).setResource( standardResource() );
 
       if ( changed ) {
         (*it).setRevision( QDateTime::currentDateTime() );
-        (*it).setChanged( true ); 
+        if ( (*it).resource() )
+          (*it).resource()->setAddresseeChanged( &(*it) );
       }
+
       return;
     }
   }
@@ -323,7 +319,9 @@ void AddressBook::insertAddressee( const
   Addressee& addr = d->mAddressees.last();
   if ( addr.resource() == 0 )
     addr.setResource( standardResource() );
-  addr.setChanged( true );
+
+  if ( addr.resource() )
+    addr.resource()->setAddresseeChanged( &addr );
 }
 
 void AddressBook::removeAddressee( const Addressee &a )
@@ -339,7 +337,9 @@ void AddressBook::removeAddressee( const
 
 void AddressBook::removeAddressee( const Iterator &it )
 {
-  d->mRemovedAddressees.append( (*it) );
+  if ( (*it).resource() )
+    (*it).resource()->setAddresseeRemoved( &(*it) );
+
   d->mAddressees.remove( it.d->mIt );
 }
 
@@ -526,18 +526,6 @@ void AddressBook::error( const QString& 
     d->mErrorHandler->error( msg );
   else
     kdError(5700) << "no error handler defined" << endl;
-}
-
-void AddressBook::deleteRemovedAddressees()
-{
-  Addressee::List::Iterator it;
-  for ( it = d->mRemovedAddressees.begin(); it != d->mRemovedAddressees.end(); ++it ) {
-    Resource *resource = (*it).resource();
-    if ( resource && !resource->readOnly() )
-      resource->removeAddressee( *it );
-  }
-
-  d->mRemovedAddressees.clear();
 }
 
 void AddressBook::setStandardResource( Resource *resource )
Index: addressee.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/addressee.cpp,v
retrieving revision 1.49
diff -u -b -p -r1.49 addressee.cpp
--- addressee.cpp	2002/09/05 23:05:43	1.49
+++ addressee.cpp	2002/10/13 22:01:33
@@ -1465,7 +1465,12 @@ void Addressee::parseEmailAddress( const
 void Addressee::setResource( Resource *resource )
 {
     detach();
+
+  if ( mData->resource )
+    mData->resource->setAddresseeRemoved( this );
+
     mData->resource = resource;
+  mData->resource->setAddresseeAdded( this );  
 } 
 
 Resource *Addressee::resource() const
@@ -1475,13 +1480,10 @@ Resource *Addressee::resource() const
 
 void Addressee::setChanged( bool value )
 {
-    detach();
-    mData->changed = value;
-}
+  if ( !mData->resource )
+    return;
 
-bool Addressee::changed() const
-{
-    return mData->changed;
+  mData->resource->setAddresseeChanged( this, value );
 }
 
 QDataStream &KABC::operator<<( QDataStream &s, const Addressee &a )
Index: addressee.h
===================================================================
RCS file: /home/kde/kdelibs/kabc/addressee.h,v
retrieving revision 1.45
diff -u -b -p -r1.45 addressee.h
--- addressee.h	2002/09/29 09:43:41	1.45
+++ addressee.h	2002/10/13 22:01:34
@@ -772,14 +772,10 @@ class Addressee
     Resource *resource() const;
   
     /**
-	    Mark addressee as changed.
+      Marks the addressee as changed. Normally you needn't use
+      this method.
 	   */
-    void setChanged( bool value );
-
-  	/**
-	    Return whether the addressee is changed.
-	   */
-	  bool changed() const;
+    void setChanged( bool changed );
 
   private:
     Addressee copy();
Index: addresslineedit.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/addresslineedit.cpp,v
retrieving revision 1.5
diff -u -b -p -r1.5 addresslineedit.cpp
--- addresslineedit.cpp	2002/09/24 15:13:12	1.5
+++ addresslineedit.cpp	2002/10/13 22:01:35
@@ -308,7 +308,7 @@ void AddressLineEdit::doCompletion(bool 
           match = s_completion->makeCompletion( "$$" + s );
     }
 
-    kdDebug() << "** completion for: " << s << " : " << match << endl;
+    kdDebug(5700) << "** completion for: " << s << " : " << match << endl;
 
     if ( ctrlT )
     {
Index: formatplugin.h
===================================================================
RCS file: /home/kde/kdelibs/kabc/formatplugin.h,v
retrieving revision 1.1
diff -u -b -p -r1.1 formatplugin.h
--- formatplugin.h	2002/08/05 21:25:39	1.1
+++ formatplugin.h	2002/10/13 22:01:35
@@ -41,6 +41,8 @@ class Addressee;
  */
 class FormatPlugin : public Plugin
 {
+  friend class Resource;
+
 public:
 
   /**
Index: resource.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/resource.cpp,v
retrieving revision 1.16
diff -u -b -p -r1.16 resource.cpp
--- resource.cpp	2002/09/26 13:18:49	1.16
+++ resource.cpp	2002/10/13 22:01:35
@@ -51,11 +51,16 @@ Ticket *Resource::requestSaveTicket()
 
 bool Resource::load()
 {
+  clearChangedUIDs();
+
   return true;
 }
 
 bool Resource::save( Ticket * )
 {
+  clearRemovedUIDs();
+  clearChangedUIDs();
+
   return false;
 }
 
@@ -69,11 +74,6 @@ QString Resource::identifier() const
   return "NoIdentifier";
 }
 
-void Resource::removeAddressee( const Addressee& )
-{
-  // do nothing
-}
-
 void Resource::cleanUp()
 {
   // do nothing
@@ -117,4 +117,51 @@ QString Resource::cryptStr( const QStrin
         QChar( 0x1001F - str[ i ].unicode() );
                 
   return result;
+}
+
+void Resource::setAddresseeAdded( const Addressee *addr )
+{
+  /*
+   * Since this addressee could be removed previous from this
+   * resource we have to remove it from the mRemovedList.
+   */
+
+  Q_ASSERT( addr );
+  mRemovedList.remove( addr->uid() );
+}
+
+void Resource::setAddresseeRemoved( const Addressee *addr )
+{
+  Q_ASSERT( addr );
+  mRemovedList.append( addr->uid() );
+}
+
+void Resource::setAddresseeChanged( const Addressee *addr, bool value )
+{
+  Q_ASSERT( addr );
+
+  if ( value )
+    mChangedList.append( addr->uid() );
+  else
+    mChangedList.remove( addr->uid() );
+}
+
+QStringList Resource::removedUIDs() const
+{
+  return mRemovedList;
+}
+
+QStringList Resource::changedUIDs() const
+{
+  return mChangedList;
+}
+
+void Resource::clearRemovedUIDs()
+{
+  mRemovedList.clear();
+}
+
+void Resource::clearChangedUIDs()
+{
+  mChangedList.clear();
 }
Index: resource.h
===================================================================
RCS file: /home/kde/kdelibs/kabc/resource.h,v
retrieving revision 1.16
diff -u -b -p -r1.16 resource.h
--- resource.h	2002/09/05 23:05:43	1.16
+++ resource.h	2002/10/13 22:01:35
@@ -49,6 +49,9 @@ class Ticket
  */
 class Resource : public Plugin
 {
+  friend class AddressBook;
+  friend class Addressee;
+
 public:
   /**
    * Constructor
@@ -99,12 +102,6 @@ public:
   virtual QString identifier() const;
 
   /**
-   * Removes a addressee from resource. This method is mainly
-   * used by record-based resources like LDAP or SQL.
-   */
-  virtual void removeAddressee( const Addressee& addr );
-
-  /**
    * This method is called by an error handler if the application
    * crashed
    */
@@ -150,11 +147,24 @@ public:
 protected:
   Ticket *createTicket( Resource * );
 
+  void setAddresseeAdded( const Addressee* );
+  void setAddresseeRemoved( const Addressee* );
+  void setAddresseeChanged( const Addressee*, bool value = true );
+
+  QStringList removedUIDs() const;
+  QStringList changedUIDs() const;
+
+  void clearRemovedUIDs();
+  void clearChangedUIDs();
+
 private:
   AddressBook *mAddressBook;
   bool mReadOnly;
   bool mFastResource;
   QString mName;
+
+  QStringList mRemovedList;
+  QStringList mChangedList;
 };
 
 }
Index: resourcefile.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/resourcefile.cpp,v
retrieving revision 1.19
diff -u -b -p -r1.19 resourcefile.cpp
--- resourcefile.cpp	2002/09/05 23:05:43	1.19
+++ resourcefile.cpp	2002/10/13 22:01:36
@@ -112,7 +112,11 @@ bool ResourceFile::load()
     return false;
   }
 
-  return mFormat->loadAll( addressBook(), this, &file );
+  bool ok = mFormat->loadAll( addressBook(), this, &file );
+
+  Resource::load();
+
+  return ok;
 }
 
 bool ResourceFile::save( Ticket *ticket )
@@ -127,7 +131,9 @@ bool ResourceFile::save( Ticket *ticket 
   
   mFormat->saveAll( addressBook(), this, &file );
 
+  Resource::save( ticket );
   delete ticket;
+
   unlock( mFileName );
 
   return true;
Index: stdaddressbook.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/stdaddressbook.cpp,v
retrieving revision 1.32
diff -u -b -p -r1.32 stdaddressbook.cpp
--- stdaddressbook.cpp	2002/10/11 16:59:22	1.32
+++ stdaddressbook.cpp	2002/10/13 22:01:36
@@ -110,8 +110,6 @@ bool StdAddressBook::save()
 
   AddressBook *ab = self();
 
-  ab->deleteRemovedAddressees();
-
   QPtrList<Resource> list = ab->resources();
   for ( uint i = 0; i < list.count(); ++i ) {
     resource = list.at( i );
Index: vcard21parser.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/vcard21parser.cpp,v
retrieving revision 1.8
diff -u -b -p -r1.8 vcard21parser.cpp
--- vcard21parser.cpp	2002/09/03 17:58:06	1.8
+++ vcard21parser.cpp	2002/10/13 22:01:37
@@ -233,7 +233,7 @@ KABC::Addressee VCard21Parser::readFromS
   // Check if parsing failed
   if (mVCard == 0)
   {
-     kdDebug() << "Parsing failed" << endl;
+     kdDebug(5710) << "Parsing failed" << endl;
      return addressee;
   }         
   //set the addressees name and formated name
Index: vcardformatimpl.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/vcardformatimpl.cpp,v
retrieving revision 1.33
diff -u -b -p -r1.33 vcardformatimpl.cpp
--- vcardformatimpl.cpp	2002/08/08 14:27:52	1.33
+++ vcardformatimpl.cpp	2002/10/13 22:01:39
@@ -109,7 +109,6 @@ void VCardFormatImpl::saveAll( AddressBo
     if ( (*it).resource() == resource ) {
       VCard *v = new VCard;
       saveAddressee( (*it), v, false );
-      (*it).setChanged( false );
       vcardlist.append( v );
     }
   }
@@ -814,7 +813,6 @@ Agent VCardFormatImpl::readAgentValue( V
     vstr.replace( QRegExp("\\\\:"), ":" );
     vstr.replace( QRegExp("\\\\,"), "," );
     vstr.replace( QRegExp("\\\\;"), ";" );
-    kdDebug() << "oldAgent=" << vstr << endl;
     Addressee *addr = new Addressee;
     readFromString( vstr, *addr );
     agent.setAddressee( addr );
Index: formats/binaryformat.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/formats/binaryformat.cpp,v
retrieving revision 1.4
diff -u -b -p -r1.4 binaryformat.cpp
--- formats/binaryformat.cpp	2002/08/05 21:25:39	1.4
+++ formats/binaryformat.cpp	2002/10/13 22:01:39
@@ -109,7 +109,6 @@ void BinaryFormat::saveAll( AddressBook 
     if ( (*it).resource() == resource ) {
       saveAddressee( (*it), stream );
       counter++;
-      (*it).setChanged( false );
     }
   }
 
Index: plugins/dir/resourcedir.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/plugins/dir/resourcedir.cpp,v
retrieving revision 1.5
diff -u -b -p -r1.5 resourcedir.cpp
--- plugins/dir/resourcedir.cpp	2002/09/09 16:32:23	1.5
+++ plugins/dir/resourcedir.cpp	2002/10/13 22:01:40
@@ -147,6 +147,8 @@ bool ResourceDir::load()
     file.close();
   }
 
+  Resource::load();
+
   return ok;
 }
 
@@ -157,10 +159,18 @@ bool ResourceDir::save( Ticket *ticket )
   AddressBook::Iterator it;
   bool ok = true;
 
+  QStringList uids = removedUIDs();
+  QStringList::Iterator uidIt;
+  for ( uidIt = uids.begin(); uidIt != uids.end(); ++uidIt )
+    QFile::remove( mPath + "/" + (*uidIt) );
+
   for ( it = addressBook()->begin(); it != addressBook()->end(); ++it ) {
-    if ( (*it).resource() != this || !(*it).changed() )
+    if ( (*it).resource() != this )
       continue;
 
+    if ( !changedUIDs().contains( (*it).uid() ) )
+      continue;
+
     QFile file( mPath + "/" + (*it).uid() );
     if ( !file.open( IO_WriteOnly ) ) {
       addressBook()->error( i18n( "Unable to open file '%1' for writing" ).arg( file.name() ) );
@@ -169,13 +179,12 @@ bool ResourceDir::save( Ticket *ticket )
 
     mFormat->save( *it, &file );
 
-    // mark as unchanged
-    (*it).setChanged( false );
-
     file.close();
   }
 
+  Resource::save( ticket );
   delete ticket;
+
   unlock( mPath );
 
   return ok;
@@ -252,11 +261,6 @@ void ResourceDir::pathChanged()
 QString ResourceDir::identifier() const
 {
     return path();
-}
-
-void ResourceDir::removeAddressee( const Addressee& addr )
-{
-    QFile::remove( mPath + "/" + addr.uid() );
 }
 
 void ResourceDir::cleanUp()
Index: plugins/dir/resourcedir.h
===================================================================
RCS file: /home/kde/kdelibs/kabc/plugins/dir/resourcedir.h,v
retrieving revision 1.3
diff -u -b -p -r1.3 resourcedir.h
--- plugins/dir/resourcedir.h	2002/09/05 23:05:43	1.3
+++ plugins/dir/resourcedir.h	2002/10/13 22:01:40
@@ -50,12 +50,6 @@ public:
   virtual QString identifier() const;
 
   /**
-   * Remove a addressee from its source.
-   * This method is mainly called by KABC::AddressBook.
-   */
-  void removeAddressee( const Addressee& addr );
-
-  /**
    * This method is called by an error handler if the application
    * crashed
    */
Index: plugins/ldap/resourceldap.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/plugins/ldap/resourceldap.cpp,v
retrieving revision 1.11
diff -u -b -p -r1.11 resourceldap.cpp
--- plugins/ldap/resourceldap.cpp	2002/09/14 15:03:33	1.11
+++ plugins/ldap/resourceldap.cpp	2002/10/13 22:01:40
@@ -214,14 +214,26 @@ bool ResourceLDAP::load()
 
   ldap_msgfree( res );
 
+  Resource::load();
+
   return true;
 }
 
-bool ResourceLDAP::save( Ticket * )
+bool ResourceLDAP::save( Ticket *ticket )
 {
+  QStringList uids = removedUIDs();
+  QStringList::Iterator uidIt;
+  for ( uidIt = uids.begin(); uidIt != uids.end(); ++uidIt )
+    removeAddressee( *uidIt );
+
   AddressBook::Iterator it;
   for ( it = addressBook()->begin(); it != addressBook()->end(); ++it ) {
-    if ( (*it).resource() == this && (*it).changed() ) {
+    if ( (*it).resource() != this )
+      continue;
+
+    if ( !changedUIDs().contains( (*it).uid() ) )
+      continue;
+
       LDAPMod **mods = NULL;
 	
       addModOp( &mods, "objectClass", "organizationalPerson" );
@@ -254,21 +266,19 @@ bool ResourceLDAP::save( Ticket * )
          addressBook()->error( i18n( "Unable to modify '%1' on server '%2'" ).arg( (*it).uid() ).arg( mHost ) );
 
       ldap_mods_free( mods, 1 );
-
-      // mark as unchanged
-      (*it).setChanged( false );
-    }
   }
 
+  Resource::save( ticket );
+
   return true;
 }
 
-void ResourceLDAP::removeAddressee( const Addressee &addr )
+void ResourceLDAP::removeAddressee( const QString &uid )
 {
   LDAPMessage *res;
   LDAPMessage *msg;
 
-  QString filter = QString( "(&(uid=%1)(%2))" ).arg( addr.uid() ).arg( mFilter );
+  QString filter = QString( "(&(uid=%1)(%2))" ).arg( uid ).arg( mFilter );
 
   kdDebug(5700) << "ldap:removeAddressee" << filter << endl;
 
Index: plugins/ldap/resourceldap.h
===================================================================
RCS file: /home/kde/kdelibs/kabc/plugins/ldap/resourceldap.h,v
retrieving revision 1.5
diff -u -b -p -r1.5 resourceldap.h
--- plugins/ldap/resourceldap.h	2002/07/28 16:19:19	1.5
+++ plugins/ldap/resourceldap.h	2002/10/13 22:01:40
@@ -48,9 +48,10 @@ public:
   bool load();
   bool save( Ticket * );
 
-  void removeAddressee( const Addressee& addr );
-
   QString identifier() const;
+
+protected:
+  void removeAddressee( const QString &uid );
 
 private:
   QString mUser;
Index: plugins/net/resourcenet.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/plugins/net/resourcenet.cpp,v
retrieving revision 1.3
diff -u -b -p -r1.3 resourcenet.cpp
--- plugins/net/resourcenet.cpp	2002/09/19 12:27:48	1.3
+++ plugins/net/resourcenet.cpp	2002/10/13 22:01:41
@@ -123,6 +123,8 @@ bool ResourceNet::load()
     }
   }
 
+  Resource::load();
+
   return ok;
 }
 
@@ -142,9 +144,6 @@ bool ResourceNet::save( Ticket *ticket )
 
     mFormat->save( *it, file );
 
-    // mark as unchanged
-    (*it).setChanged( false );
-
     tmpFile.close();
 
     if ( !KIO::NetAccess::upload( tmpFile.name(), mUrl.url() + "/" + (*it).uid() ) ) {
@@ -155,6 +154,7 @@ bool ResourceNet::save( Ticket *ticket )
     tmpFile.unlink();
   }
 
+  Resource::save( ticket );
   delete ticket;
 
   return ok;
Index: scripts/addressee.src.cpp
===================================================================
RCS file: /home/kde/kdelibs/kabc/scripts/addressee.src.cpp,v
retrieving revision 1.35
diff -u -b -p -r1.35 addressee.src.cpp
--- scripts/addressee.src.cpp	2002/09/05 23:05:43	1.35
+++ scripts/addressee.src.cpp	2002/10/13 22:01:42
@@ -771,7 +771,12 @@ void Addressee::parseEmailAddress( const
 void Addressee::setResource( Resource *resource )
 {
     detach();
+
+  if ( mData->resource )
+    mData->resource->setAddresseeRemoved( this );
+
     mData->resource = resource;
+  mData->resource->setAddresseeAdded( this );  
 } 
 
 Resource *Addressee::resource() const
@@ -781,13 +786,10 @@ Resource *Addressee::resource() const
 
 void Addressee::setChanged( bool value )
 {
-    detach();
-    mData->changed = value;
-}
+  if ( !mData->resource )
+    return;
 
-bool Addressee::changed() const
-{
-    return mData->changed;
+  mData->resource->setAddresseeChanged( this, value );
 }
 
 QDataStream &KABC::operator<<( QDataStream &s, const Addressee &a )
Index: scripts/addressee.src.h
===================================================================
RCS file: /home/kde/kdelibs/kabc/scripts/addressee.src.h,v
retrieving revision 1.32
diff -u -b -p -r1.32 addressee.src.h
--- scripts/addressee.src.h	2002/09/29 09:43:38	1.32
+++ scripts/addressee.src.h	2002/10/13 22:01:43
@@ -329,14 +329,10 @@ class Addressee
     Resource *resource() const;
   
     /**
-	    Mark addressee as changed.
+      Marks the addressee as changed. Normally you needn't use
+      this method.
 	   */
-    void setChanged( bool value );
-
-  	/**
-	    Return whether the addressee is changed.
-	   */
-	  bool changed() const;
+    void setChanged( bool changed );
 
   private:
     Addressee copy();

["kaddressbook.diff" (text/plain)]

Index: undocmds.cpp
===================================================================
RCS file: /home/kde/kdepim/kaddressbook/undocmds.cpp,v
retrieving revision 1.9
diff -u -b -p -r1.9 undocmds.cpp
--- undocmds.cpp	2002/08/25 15:58:00	1.9
+++ undocmds.cpp	2002/10/12 18:34:06
@@ -151,13 +151,11 @@ QString PwEditCommand::name()
 
 void PwEditCommand::undo()
 {
-  mDocument->removeAddressee(mNewA);
   mDocument->insertAddressee(mOldA);
 }
 
 void PwEditCommand::redo()
 {
-  mDocument->removeAddressee(mOldA);
   mDocument->insertAddressee(mNewA);
 }
 

_______________________________________________
kde-pim mailing list
kde-pim@mail.kde.org
http://mail.kde.org/mailman/listinfo/kde-pim
kde-pim home page at http://pim.kde.org/

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic