[Licq-devel] New group handling, patch review

"Anders Olofsson" <[email protected]> Mon, 2 Jun 2008 18:08:23 +0200
Newsgroups gmane.network.licq.devel
Message-ID <000001c8c4ca$e2d077d0$0241a8c0@zzz>
I've started working on a better group handling in Licq.
The attached patch is my progress so far. I've implemented new handling in
daemon and it compiles but I haven't tried to run it as I haven't started
adapting any plugins yet.
I'm sending the patch now to give people a chance to review it and find
problems before I put down too much work with the plugins.
The patch for daemon is complete. That is, unless anyone think I've
implemented it the wrong way or I run into problems when working with the
plugins.

So, this patch is meant for viewing, it's not ready for running yet. Let me
know I should change anything.

/Anders
licq_6226_newGroupHandling.patch (application/octet-stream, 53.6 KB)
Index: include/licq_packets.h
===================================================================
--- include/licq_packets.h	(revision 6226)
+++ include/licq_packets.h	(working copy)
@@ -385,7 +385,7 @@
 class CPU_ExportGroupsToServerList : public CPU_CommonFamily
 {
 public:
-  CPU_ExportGroupsToServerList(GroupList &);
+  CPU_ExportGroupsToServerList();
 };
 
 //-----AddPrivacyInfo-----------------------------------------------------------
Index: include/licq_user.h
===================================================================
--- include/licq_user.h	(revision 6226)
+++ include/licq_user.h	(working copy)
@@ -6,6 +6,7 @@
 #include <ctime>
 #include <list>
 #include <map>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -178,10 +179,14 @@
           continue;                      \
         }
 
+class ICQuser;
+class ICQOwner;
+class LicqGroup;
+
 typedef std::list<ICQUser *> UserList;
 typedef std::list<class ICQOwner *> OwnerList;
-typedef std::vector<char *> GroupList;
-typedef std::vector<unsigned short> GroupIDList;
+typedef std::set<unsigned short> UserGroupList;
+typedef std::map<unsigned short, LicqGroup*> GroupMap;
 typedef std::list<unsigned long> UinList;
 typedef std::list<char *> UserStringList;
 typedef std::vector <class CUserEvent *> UserEventList;
@@ -733,13 +738,69 @@
   const char *HistoryName()      { return m_fHistory.Description(); }
   const char *HistoryFile()      { return m_fHistory.FileName(); }
 
-  // Group functions
-  unsigned long GetGroups(GroupType g)         { return(m_nGroups[g]); }
-  void SetGroups(GroupType g, unsigned long s) { m_nGroups[g] = s; SaveLicqInfo(); }
-  bool GetInGroup(GroupType, unsigned short);
-  void SetInGroup(GroupType, unsigned short, bool);
-  void AddToGroup(GroupType, unsigned short);
-  void RemoveFromGroup(GroupType, unsigned short);
+  /**
+   * Get user groups this user is member of
+   *
+   * @return List of groups
+   */
+  const UserGroupList& GetGroups() const { return myGroups; }
+
+  /**
+   * Get system groups this user is member of
+   *
+   * @return Bitmask of server groups
+   */
+  unsigned long GetSystemGroups() const { return mySystemGroups; }
+
+  /**
+   * Set user groups this user is member of
+   *
+   * @param groups List of groups
+   */
+  void SetGroups(const UserGroupList& groups) { myGroups = groups; }
+
+  /**
+   * Set system groups this user is member of
+   *
+   * @param groups Bitmask of server groups
+   */
+  void SetSystemGroups(unsigned long groups)    { mySystemGroups = groups; }
+
+  /**
+   * Check if user is member of a group
+   *
+   * @param gtype Group type (GROUPS_SYSTEM or GROUPS_USER)
+   * @param groupId Id of group to check
+   * @return True if group exists and user is member
+   */
+  bool GetInGroup(GroupType gtype, unsigned short groupId) const;
+
+  /**
+   * Convenience function to set membership of user for a group
+   *
+   * @param gtype Group type (GROUPS_SYSTEM or GROUPS_USER)
+   * @param groupId Id of group
+   * @param member True to add user to group, false to remove user from group
+   */
+  void SetInGroup(GroupType gtype, unsigned short groupId, bool member);
+
+  /**
+   * Add user to a group
+   *
+   * @param gtype Group type (GROUPS_SYSTEM or GROUPS_USER)
+   * @param groupId Id of group to add
+   */
+  void AddToGroup(GroupType gtype, unsigned short groupId);
+
+  /**
+   * Remove user from a group
+   *
+   * @param gtype Group type (GROUPS_SYSTEM or GROUPS_USER)
+   * @pram groupId Id of group to leave
+   * @return True if group was valid and user was a member
+   */
+  bool RemoveFromGroup(GroupType gtype, unsigned short groupId);
+
   // Short cuts to above functions
   bool InvisibleList() { return GetInGroup(GROUPS_SYSTEM, GROUP_INVISIBLE_LIST); }
   bool VisibleList()   { return GetInGroup(GROUPS_SYSTEM, GROUP_VISIBLE_LIST); }
@@ -861,8 +922,9 @@
   unsigned short m_nPort, m_nLocalPort, m_nConnectionVersion;
   unsigned short m_nTyping;
   unsigned long m_nUin,
-                m_nStatus,
-                m_nGroups[2];
+                m_nStatus;
+  UserGroupList myGroups;               /**< List of user groups */
+  unsigned long mySystemGroups;         /**< Bitmask for system groups */
   unsigned short m_nSequence;
   unsigned long m_nPhoneFollowMeStatus, m_nICQphoneStatus, m_nSharedFilesStatus;
   char m_nMode;
@@ -979,7 +1041,6 @@
   unsigned short m_nLockType;
   static pthread_mutex_t mutex_nNumUserEvents;
 
-  friend class CUserGroup;
   friend class CUserManager;
   friend class CICQDaemon;
   friend class CMSN;
@@ -1073,6 +1134,99 @@
   unsigned short m_nLockType;
 };
 
+/**
+ * Class holding data for a user group in the contact list.
+ * System groups only exists as a bitmask in ICQUser.
+ *
+ * Note: LicqGroup objects should only be created, deleted or modified from the
+ * user manager. If set functions are called directly, plugins will not receive
+ * any signal notifying them of the change.
+ */
+class LicqGroup
+{
+public:
+  /**
+   * Constructor, creates a new user group
+   *
+   * @param id Group id, must be unique
+   * @param name Group name
+   */
+  LicqGroup(unsigned short id, const std::string& name);
+
+  /**
+   * Get id for group. This is an id used locally by Licq and is persistant for
+   * each group.
+   *
+   * @return Group id
+   */
+  unsigned short id() const { return myId; }
+
+  /**
+   * Get name of group as should be displayed in the user interface
+   *
+   * @return Group name
+   */
+  const std::string& name() const { return myName; }
+
+  /**
+   * Get sorting index for the group. This is used by user interface plugins to
+   * determine sorting order for the groups. Lower numbers should be displayed
+   * higher in the list.
+   *
+   * @return Sorting index for this group
+   */
+  unsigned short sortIndex() const { return mySortIndex; }
+
+  /**
+   * Group id for this group in the ICQ server side list
+   *
+   * @return ICQ server group id or 0 if not set or not known
+   */
+  unsigned short icqGroupId() const { return myIcqGroupId; }
+
+  /**
+   * Set group name
+   *
+   * @param name New group name
+   */
+  void setName(const std::string& name) { myName = name; }
+
+  /**
+   * Set sorting index for group
+   *
+   * @param sortIndex Group sorting index
+   */
+  void setSortIndex(unsigned short sortIndex) { mySortIndex = sortIndex; }
+
+  /**
+   * Set group id in ICQ server side list
+   *
+   * @param icqGroupId ICQ server group id
+   */
+  void setIcqGroupId(unsigned short icqGroupId) { myIcqGroupId = icqGroupId; }
+
+  /**
+   * Lock group for access
+   *
+   * @param lockType Type of lock (LOCK_R or LOCK_W)
+   */
+  void Lock(unsigned short lockType);
+
+  /**
+   * Release current lock for group
+   */
+  void Unlock();
+
+private:
+  unsigned short myId;
+  std::string myName;
+  unsigned short mySortIndex;
+  unsigned short myIcqGroupId;
+
+  pthread_rdwr_t myMutex;
+  unsigned short myLockType;
+};
+
 class CUserManager
 {
 public:
@@ -1104,27 +1258,138 @@
 
   UserList *LockUserList(unsigned short);
   void UnlockUserList();
-  GroupList *LockGroupList(unsigned short);
+
+  /**
+   * Lock group list for access
+   * Call UnlockGroupList when lock is no longer needed
+   *
+   * @param lockType Type of lock (LOCK_R or LOCK_W)
+   * @return Map of all user groups indexed by group ids
+   */
+  GroupMap* LockGroupList(unsigned short lockType);
+
+  /**
+   * Release group list lock
+   */
   void UnlockGroupList();
-  GroupIDList *LockGroupIDList(unsigned short);
-  void UnlockGroupIDList();
+
   OwnerList *LockOwnerList(unsigned short);
   void UnlockOwnerList();
 
-  bool AddGroup(char *, unsigned short = 0);
-  void RemoveGroup(unsigned short);
-  void RenameGroup(unsigned short, const char *, bool = true);
+  /**
+   * Find and lock a group
+   * After use, the lock must be released by calling DropGroup()
+   *
+   * @param groupId Id of group to fetch
+   * @param lockType Type of lock to get
+   * @return The group if found no NULL if groupId was invalid
+   */
+  LicqGroup* FetchGroup(unsigned short groupId, unsigned short lockType);
+
+  /**
+   * Release the lock for a group preivously returned by FetchGroup()
+   *
+   * @param group The group to unlock
+   */
+  void DropGroup(LicqGroup* group);
+
+  /**
+   * Add a user group
+   *
+   * @param name Group name, must be unique
+   * @param icqGroupId ICQ server group id
+   * @return True if group was successfully added
+   */
+  bool AddGroup(const std::string& name, unsigned short icqGroupId = 0);
+
+  /**
+   * Remove a user group
+   *
+   * @param groupId Id of group to remove
+   */
+  void RemoveGroup(unsigned short groupId);
+
+  /**
+   * Rename a user group
+   *
+   * @param groupId Id of group to rename
+   * @param name New group name, must be unique
+   * @param sendUpdate True if server group should be updated
+   * @return True if group was successfully renamed
+   */
+  bool RenameGroup(unsigned short groupId, const std::string& name, bool sendUpdate = true);
+
+  /**
+   * Get number of user groups
+   *
+   * @return Number of user groups
+   */
   unsigned short NumGroups();
+
+  /**
+   * Save user group list to configuration file
+   * Note: This function assumes that user group list is already locked.
+   */
   void SaveGroups();
-  void SwapGroups(unsigned short g1, unsigned short g2);
 
-  void AddGroupID(unsigned short);
-  void RemoveGroupID(unsigned short);
-  void ModifyGroupID(char *, unsigned short);
-  void SaveGroupIDs();
-  unsigned short GetIDFromGroup(const char *);
-  unsigned short GetGroupFromID(unsigned short);
+  /**
+   * Move sorting position for a group
+   * Sorting position for other groups may also be changed to make sure all
+   * groups have unique sorting indexes.
+   *
+   * @param groupId Id of group to move
+   * @param newIndex New sorting index where 0 is the top position
+   */
+  void ModifyGroupSorting(unsigned short groupId, unsigned short newIndex);
 
+  /**
+   * Change ICQ server group id for a user group
+   *
+   * @param name Name of group to change
+   * @param icqGroupId ICQ server group id to set
+   */
+  void ModifyGroupID(const std::string& name, unsigned short icqGroupId);
+
+  /**
+   * Change ICQ server group id for a user group
+   *
+   * @param groupId Id of group to change
+   * @param icqGroupId ICQ server group id to set
+   */
+  void ModifyGroupID(unsigned short groupId, unsigned short icqGroupId);
+
+  /**
+   * Get ICQ group id from group name
+   *
+   * @param name Group name
+   * @return Id for ICQ server group or 0 if not found
+   */
+  unsigned short GetIDFromGroup(const std::string& name);
+
+  /**
+   * Get ICQ group id from group
+   *
+   * @param groupId Group
+   * @return Id for iCQ server group or 0 if groupId was invalid
+   */
+  unsigned short GetIDFromGroup(unsigned short groupId);
+
+  /**
+   * Get group id from ICQ server group id
+   *
+   * @param icqGroupId ICQ server group id
+   * @return Id for group or 0 if not found
+   */
+  unsigned short GetGroupFromID(unsigned short icqGroupId);
+
+  /**
+   * Find id for group with a given name
+   *
+   * @param name Name of the group
+   * @return Id for the group or 0 if there is no group with that name
+   */
+  unsigned short GetGroupFromName(const std::string& name);
+
   unsigned short GenerateSID();
 
   void AddUserToGroup(unsigned long _nUin, unsigned short _nGroup);
@@ -1145,16 +1410,15 @@
 protected:
   pthread_rdwr_t mutex_grouplist, mutex_userlist, mutex_groupidlist, mutex_ownerlist;
 
-  GroupList m_vszGroups;
+  GroupMap myGroups;
   UserList m_vpcUsers;
   OwnerList m_vpcOwners;
-  GroupIDList m_vnGroupsID;
   CUserHashTable m_hUsers;
   ICQOwner *m_xOwner;
   unsigned long m_nOwnerUin;
   unsigned short m_nDefaultGroup, m_nNewUserGroup,
-                 m_nUserListLockType, m_nGroupListLockType,
-                 m_nGroupIDListLockType, m_nOwnerListLockType;
+                 m_nUserListLockType, myGroupListLockType,
+                 m_nOwnerListLockType;
   bool m_bAllowSave;
 
   friend class CICQDaemon;
Index: include/licq_events.h
===================================================================
--- include/licq_events.h	(revision 6226)
+++ include/licq_events.h	(working copy)
@@ -405,7 +405,7 @@
  * Sub signals for SIGNAL_UPDATExLIST
  *
  * For contact updates, ppid and id are valid. For group updates, argument
- * holds the group id.
+ * holds the group id except for reordered which applies to entire list.
  */
 enum SubSignals_UPDATExLIST
 {
@@ -414,7 +414,8 @@
   LIST_INVALIDATE = 3,          /**< List must be reloaded */
   LIST_GROUP_ADDED = 4,         /**< A group was added to the list */
   LIST_GROUP_REMOVED = 5,       /**< A group was removed from the list */
-  LIST_GROUP_CHANGED = 6        /**< Data for a group has changed */
+  LIST_GROUP_CHANGED = 6,       /**< Data for a group has changed */
+  LIST_GROUP_REORDERED = 7,     /**< Group sorting has changed */
 };
 
 // Deprecated SIGNAL_UPDATExLIST sub signal names, do not use
Index: include/licq_icqd.h
===================================================================
--- include/licq_icqd.h	(revision 6226)
+++ include/licq_icqd.h	(working copy)
@@ -468,7 +468,7 @@
   void icqRenameUser(unsigned long _nUin);
   void icqRenameUser(const char *_szId);
   void icqExportUsers(UserStringList &, unsigned short);
-  void icqExportGroups(GroupList &);
+  void icqExportGroups();
   void icqUpdateServerGroups();
   void icqUpdatePhoneBookTimestamp();
   void icqUpdatePictureTimestamp();
Index: src/icqpacket.cpp
===================================================================
--- src/icqpacket.cpp	(revision 6226)
+++ src/icqpacket.cpp	(working copy)
@@ -2724,31 +2724,38 @@
     if (_nType == ICQ_ROSTxNORMAL)
     {
       // Use the first group that the user is in as the server stored group
-      GroupIDList *pID = gUserManager.LockGroupIDList(LOCK_R);
-      for (unsigned short j = 1; j < pID->size() + 1; j++)
+      const UserGroupList& userGroups = u->GetGroups();
+
+      for (UserGroupList::const_iterator j = userGroups.begin(); j != userGroups.end(); ++j)
       {
-        if (u->GetInGroup(GROUPS_USER, j))
-        {
-          m_nGSID = (*pID)[j-1];
-          if (m_nGSID)
-            break;
-        }
+        m_nGSID = gUserManager.GetIDFromGroup(*j);
+        if (m_nGSID != 0)
+          break;
       }
 
       // No group yet?  Use default.  No default? Use ID of 1 (general)
       if (m_nGSID == 0)
       {
         unsigned short nNewGroup = gUserManager.NewUserGroup();
-        if (nNewGroup && nNewGroup <= pID->size())
-          m_nGSID = (*pID)[nNewGroup-1];
+        m_nGSID = gUserManager.GetIDFromGroup(nNewGroup);
 
-        if (m_nGSID == 0 && pID->size())
-          m_nGSID = (*pID)[0]; // first group if none was specified
+        if (m_nGSID == 0)
+        {
+          // First group if none was specified
+          GroupMap* groups = gUserManager.LockGroupList(LOCK_R);
+          if (groups->size() > 0)
+          {
+            LicqGroup* g = groups->begin()->second;
+            g->Lock(LOCK_R);
+            m_nGSID = g->icqGroupId();
+            g->Unlock();
+          }
+          gUserManager.UnlockGroupList();
+        }
 
         if (m_nGSID == 0)
           m_nGSID = 1; // General (unless user renamed group or wasnt created yet)
       }
-      gUserManager.UnlockGroupIDList();
 
       u->SetGSID(m_nGSID);
       szUnicodeName = strdup(u->GetAlias());
@@ -2778,40 +2785,44 @@
 
     if (szUnicodeName)
       free(szUnicodeName);
-      
+
     free(*i);
   }
 }
 
 //-----ExportGroupsToServerList-------------------------------------------------
-CPU_ExportGroupsToServerList::CPU_ExportGroupsToServerList(GroupList &groups)
+CPU_ExportGroupsToServerList::CPU_ExportGroupsToServerList()
   : CPU_CommonFamily(ICQ_SNACxFAM_LIST, ICQ_SNACxLIST_ROSTxADD)
 {
   int nSize = 0;
   int nGSID = 0;
 
-  GroupList::iterator g;
-  for (g = groups.begin(); g != groups.end(); ++g)
+  const GroupMap* groups = gUserManager.LockGroupList(LOCK_R);
+  GroupMap::const_iterator g;
+  for (g = groups->begin(); g != groups->end(); ++g)
   {
-    char *szUnicode = gTranslator.ToUnicode(*g);
+    g->second->Lock(LOCK_R);
+    const char* szUnicode = gTranslator.ToUnicode(g->second->name().c_str());
     nSize += strlen(szUnicode);
+    g->second->Unlock();
     nSize += 10;
     delete [] szUnicode;
   }
-  
+
   m_nSize += nSize;
   InitBuffer();
 
   // Not necessary, but just to make it explicit that it occurs
   SetExtraInfo(0); // top level group (contains all the groups)
 
-  for (g = groups.begin(); g != groups.end(); g++)
+  for (g = groups->begin(); g != groups->end(); g++)
   {
     nGSID = gUserManager.GenerateSID();
 
-    gUserManager.ModifyGroupID(*g, nGSID);
+    gUserManager.ModifyGroupID(g->first, nGSID);
 
-    char *szUnicodeName = gTranslator.ToUnicode(*g);
+    g->second->Lock(LOCK_R);
+    const char* szUnicodeName = gTranslator.ToUnicode(g->second->name().c_str());
 
     buffer->PackUnsignedShortBE(strlen(szUnicodeName));
     buffer->Pack(szUnicodeName, strlen(szUnicodeName));
@@ -2819,9 +2830,11 @@
     buffer->PackUnsignedShortBE(0);
     buffer->PackUnsignedShortBE(ICQ_ROSTxGROUP);
     buffer->PackUnsignedShortBE(0);
+    g->second->Unlock();
 
     delete [] szUnicodeName;
   }
+  gUserManager.UnlockGroupList();
 }
 
 //-----AddPrivacyInfo-----------------------------------------------------------
@@ -2874,11 +2887,11 @@
       u->SetAwaitingAuth(_bAuthReq);
 
       // Check for a group id
-      GroupIDList *pID = gUserManager.LockGroupIDList(LOCK_R);
+      const UserGroupList& userGroups = u->GetGroups();
       if (_nGroup)
       {
         // Use the passed in group
-        m_nGSID = (*pID)[_nGroup-1];
+        m_nGSID = gUserManager.GetIDFromGroup(_nGroup);
       }
       else if (u->GetGSID() && _bAuthReq)
       {
@@ -2887,14 +2900,11 @@
       else
       {
         // Use the first group that the user is in as the server stored group
-        for (unsigned short i = 1; i < pID->size() + 1; i++)
+        for (UserGroupList::iterator i = userGroups.begin(); i != userGroups.end(); ++i)
         {
-          if (u->GetInGroup(GROUPS_USER, i))
-          {
-            m_nGSID = (*pID)[i-1];
-            if (m_nGSID)
-              break;
-          }
+          m_nGSID = gUserManager.GetIDFromGroup(*i);
+          if (m_nGSID != 0)
+            break;
         }
       }
 
@@ -2918,14 +2928,23 @@
       if (m_nGSID == 0)
       {
         unsigned short nNewGroup = gUserManager.NewUserGroup();
-        if (nNewGroup && nNewGroup <= pID->size())
-          m_nGSID = (*pID)[nNewGroup-1];
+        m_nGSID = gUserManager.GetIDFromGroup(nNewGroup);
 
-        if (m_nGSID == 0 && pID->size())
-          m_nGSID = (*pID)[0];
-        
         if (m_nGSID == 0)
-          m_nGSID = 1; // General (unless user renamed group)
+        {
+          GroupMap* groups = gUserManager.LockGroupList(LOCK_R);
+          if (groups->size() > 0)
+          {
+            LicqGroup* g = groups->begin()->second;
+            g->Lock(LOCK_R);
+            m_nGSID = g->icqGroupId();
+            g->Unlock();
+          }
+          gUserManager.UnlockGroupList();
+        }
+
+        if (m_nGSID == 0)
+          m_nGSID = 1; // General (unless user renamed group or wasnt created yet)
       }
 
       szUnicodeAlias = 0;//strdup(u->GetAlias());
@@ -2933,7 +2952,6 @@
 
       SetExtraInfo(m_nGSID);
       u->SetGSID(m_nGSID);
-      gUserManager.UnlockGroupIDList();
       gUserManager.DropUser(u);
 
       break;
@@ -2998,16 +3016,21 @@
   {
     if (_bTopLevel)
     {
-      GroupIDList *pID = gUserManager.LockGroupIDList(LOCK_R);
+      const GroupMap* groups = gUserManager.LockGroupList(LOCK_R);
 
       // We are creating our top level group, so attach all the group ids now
       buffer->PackUnsignedShortBE(0x00C8);
       buffer->PackUnsignedShortBE(gUserManager.NumGroups() * 2);
-      
-      for (unsigned short i = 0; i < pID->size(); i++)
-        buffer->PackUnsignedShortBE((*pID)[i]);
-     
-      gUserManager.UnlockGroupIDList();
+
+      GroupMap::const_iterator i;
+      for (i = groups->begin(); i != groups->end(); ++i)
+      {
+        i->second->Lock(LOCK_R);
+        buffer->PackUnsignedShortBE(i->second->icqGroupId());
+        i->second->Unlock();
+      }
+
+      gUserManager.UnlockGroupList();
     }
     else
     {
@@ -3182,7 +3205,7 @@
   unsigned short nExtraLen = 0;
   unsigned short nNameLen = strlen(_szName);
   char *szUnicodeName = 0;
-  GroupIDList *gID = 0;
+  const GroupMap* groups = 0;
   CBuffer tlvBuffer;
 
   switch (_nType)
@@ -3228,10 +3251,10 @@
 
       if (nGSID == 0)
       {
-        gID = gUserManager.LockGroupIDList(LOCK_R);
-        nExtraLen += (gID->size() * 2);
+        groups = gUserManager.LockGroupList(LOCK_R);
+        nExtraLen += (groups->size() * 2);
         if (nExtraLen == 0)
-          gUserManager.UnlockGroupIDList();
+          gUserManager.UnlockGroupList();
       }
       else
       {
@@ -3273,14 +3296,17 @@
     {
       buffer->PackUnsignedShortBE(0x00C8);
       buffer->PackUnsignedShortBE(nExtraLen-4);
-      
+
       if (nGSID == 0)
       {
-        for (unsigned int i = 0; i != gID->size(); i++)
+        GroupMap::const_iterator i;
+        for (i = groups->begin(); i != groups->end(); ++i)
         {
-          buffer->PackUnsignedShortBE((*gID)[i]);
+          i->second->Lock(LOCK_R);
+          buffer->PackUnsignedShortBE(i->second->icqGroupId());
+          i->second->Unlock();
         }
-        gUserManager.UnlockGroupIDList();
+        gUserManager.UnlockGroupList();
       }
       else
       {
Index: src/user.cpp
===================================================================
--- src/user.cpp	(revision 6226)
+++ src/user.cpp	(working copy)
@@ -483,8 +483,6 @@
   }
 }
 
-
-
 //=====CUserManager=============================================================
 CUserManager::CUserManager() : m_hUsers(USER_HASH_SIZE)
 {
@@ -502,7 +500,8 @@
   pthread_rdwr_set_name(&mutex_ownerlist, "ownerlist");
 
   m_nOwnerListLockType = LOCK_N;
-  m_nUserListLockType = m_nGroupListLockType = m_nGroupIDListLockType = LOCK_N;
+  m_nUserListLockType = LOCK_N;
+  myGroupListLockType = LOCK_N;
 
   m_xOwner = NULL;
   m_nOwnerUin = 0;
@@ -517,9 +516,9 @@
     delete *iter;
   }
 
-  GroupList::iterator g_iter;
-  for (g_iter = m_vszGroups.begin(); g_iter != m_vszGroups.end(); ++g_iter)
-    free(*g_iter);
+  GroupMap::iterator g_iter;
+  for (g_iter = myGroups.begin(); g_iter != myGroups.end(); ++g_iter)
+    delete g_iter->second;
 
   OwnerList::iterator o_iter;
   for (o_iter = m_vpcOwners.begin(); o_iter != m_vpcOwners.end(); ++o_iter)
@@ -590,23 +589,44 @@
   licqConf.SetSection("groups");
   licqConf.ReadNum("NumOfGroups", nGroups);
 
+  GroupMap* groups = LockGroupList(LOCK_W);
   m_bAllowSave = false;
-  char sGroupKey[MAX_KEYxNAME_LEN], sGroupIDKey[MAX_KEYxNAME_LEN],
-       sGroupName[MAX_LINE_LEN];
-  unsigned short nID;
+  char key[MAX_KEYxNAME_LEN], groupName[MAX_LINE_LEN];
+  unsigned short icqGroupId, groupId, sortIndex;
   for (unsigned short i = 1; i <= nGroups; i++)
   {
-     sprintf(sGroupKey, "Group%d.name", i);
-     licqConf.ReadStr(sGroupKey, sGroupName);
+    sprintf(key, "Group%d.name", i);
+    licqConf.ReadStr(key, groupName);
+
      licqConf.ClearFlag( INI_FxFATAL );
-     sprintf(sGroupIDKey, "Group%d.id", i);
-     licqConf.ReadNum(sGroupIDKey, nID, 0);
+
+    sprintf(key, "Group%d.id", i);
+    licqConf.ReadNum(key, groupId, 0);
+
+    sprintf(key, "Group%d.Sorting", i);
+    bool newConfig = licqConf.ReadNum(key, sortIndex, i-1);
+
+    sprintf(key, "Group%d.IcqServerId", i);
+    licqConf.ReadNum(key, icqGroupId, 0);
+
+    // Sorting and IcqServerId did not exist in older versions.
+    // If they are missing, assume that we are reading an old configuration
+    // where id parameter is ICQ server side group id.
+    if (!newConfig)
+    {
+      icqGroupId = groupId;
+      groupId = i;
+    }
+
      licqConf.SetFlag( INI_FxFATAL );
-     char *szTempGroup = strdup(sGroupName);
-     AddGroup(szTempGroup, nID);
-     free(szTempGroup);
+
+    LicqGroup* newGroup = new LicqGroup(groupId, groupName);
+    newGroup->setIcqGroupId(icqGroupId);
+    newGroup->setSortIndex(sortIndex);
+    (*groups)[groupId] = newGroup;
   }
   m_bAllowSave = true;
+  UnlockGroupList();
 
   licqConf.ReadNum("DefaultGroup", m_nDefaultGroup, 0);
   if(m_nDefaultGroup >=  1024)
@@ -830,188 +850,199 @@
   RemoveUser(szId, nPPID);
 }
 
+LicqGroup* CUserManager::FetchGroup(unsigned short group, unsigned short lockType)
+{
+  GroupMap* groups = LockGroupList(LOCK_R);
+  GroupMap::const_iterator iter = groups->find(group);
+  LicqGroup* g = NULL;
+  if (iter != groups->end())
+  {
+    g = iter->second;
+    g->Lock(lockType);
+  }
+  UnlockGroupList();
+  return g;
+}
 
+void CUserManager::DropGroup(LicqGroup* group)
+{
+  if (group != NULL)
+    group->Unlock();
+}
+
 /*---------------------------------------------------------------------------
  * CUserManager::AddGroup
  *-------------------------------------------------------------------------*/
-bool CUserManager::AddGroup(char *_szName, unsigned short nID)
+bool CUserManager::AddGroup(const string& name, unsigned short icqGroupId)
 {
-  bool bNewGroup = true;
+  if (name.empty())
+    return false;
 
-  if(_szName)
+  if (GetGroupFromName(name) != 0)
   {
-    // Check to make sure it isn't a group name already
-    LockGroupList(LOCK_W);
+    // Don't allow a duplicate name
+    gLog.Warn(tr("%sGroup %s is already in list.\n"), L_WARNxSTR, name.c_str());
+    return false;
+  }
 
-    GroupList::iterator iter;
-    for (iter = m_vszGroups.begin(); iter != m_vszGroups.end(); ++iter)
-    {
-      if (strcasecmp(*iter, _szName) == 0)
-      {
-        bNewGroup = false;
-        break;
-      }
-    }
+  GroupMap* groups = LockGroupList(LOCK_W);
 
-    // Don't allow a duplicate name
-    if (!bNewGroup)
-    {
-      gLog.Warn(tr("%sGroup %s already on list.\n"), L_WARNxSTR, _szName);
-    }
-    else
-    {
-      AddGroupID(nID);
+  // Find first free group id
+  unsigned short gid;
+  for (gid = 1; groups->count(gid) != 0 ; ++gid)
+    ;
 
-      m_vszGroups.push_back(strdup(_szName));
-      SaveGroups();
-    }
+  LicqGroup* newGroup = new LicqGroup(gid, name);
+  newGroup->setIcqGroupId(icqGroupId);
+  newGroup->setSortIndex(groups->size());
+  (*groups)[gid] = newGroup;
 
-    UnlockGroupList();
-  }
+  SaveGroups();
+  UnlockGroupList();
 
-  if (bNewGroup && gLicqDaemon)
+  if (gLicqDaemon != NULL)
   {
-    if (nID != 0)
-      gLicqDaemon->icqAddGroup(_szName);
+    if (icqGroupId != 0)
+      gLicqDaemon->icqAddGroup(name.c_str());
 
-    // New group is last so it has highest group id
-    unsigned short gid = NumGroups();
-
     // Send signal to let plugins know of the new group
     gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExLIST, LIST_GROUP_ADDED, NULL, 0, gid, 0));
   }
 
-  return bNewGroup;
+  return true;
 }
 
-
-
 /*---------------------------------------------------------------------------
  * CUserManager::RemoveGroup
- *
- * Removes a group and it's id (note groups are numbered 1 to NumGroups() )
  *-------------------------------------------------------------------------*/
-void CUserManager::RemoveGroup(unsigned short n)
+void CUserManager::RemoveGroup(unsigned short groupId)
 {
-  // Don't delete the all users group
-  if(n < 1 || n > NumGroups())
-  {
+  LicqGroup* group = FetchGroup(groupId, LOCK_R);
+  if (group == NULL)
     return;
-  }
 
-  GroupList *g = LockGroupList(LOCK_R);	
+  string name = group->name();
+  unsigned short sortIndex = group->sortIndex();
+  DropGroup(group);
 
   // Must be called when there are no locks on GroupID and Group lists
-  char szName[128];
-  strncpy(szName, m_vszGroups[n-1], sizeof(szName));
-  szName[sizeof(szName) - 1] = '\0';
-  UnlockGroupList();
-  gLicqDaemon->icqRemoveGroup(szName);
+  gLicqDaemon->icqRemoveGroup(name.c_str());
 
   // Lock it back up
-  g = LockGroupList(LOCK_W);
+  GroupMap* g = LockGroupList(LOCK_W);
+  group->Lock(LOCK_W);
 
-  // Erase the group from the vector
-  m_vszGroups.erase(m_vszGroups.begin()+n-1);
+  // Erase the group
+  g->erase(groupId);
+  group->Unlock();
+  delete group;
 
-  unsigned short j;
+  if (m_nDefaultGroup == groupId)
+    m_nDefaultGroup = 0;
+  if (m_nNewUserGroup == groupId)
+    m_nNewUserGroup = 0;
+
+  // Decrease sorting index for higher groups so we don't leave a gap
+  GroupMap::const_iterator iter;
+  for (iter = g->begin(); iter != g->end(); ++iter)
+  {
+    iter->second->Lock(LOCK_W);
+    unsigned short si = iter->second->sortIndex();
+    if (si > sortIndex)
+      iter->second->setSortIndex(si - 1);
+    iter->second->Unlock();
+  }
+
+  // Remove group from users
   FOR_EACH_USER_START(LOCK_W)
   {
-    for (j = n; j < g->size() + 1; j++)
-      pUser->SetInGroup(GROUPS_USER, j, pUser->GetInGroup(GROUPS_USER, j + 1));
+    if (pUser->RemoveFromGroup(GROUPS_USER, groupId))
+      gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExUSER, USER_GENERAL,
+          pUser->IdString(), pUser->PPID()));
   }
-  pUser->SetInGroup(GROUPS_USER, j, false);
   FOR_EACH_USER_END;
-  if (m_nDefaultGroup >= n) m_nDefaultGroup--;
-  if (m_nNewUserGroup >= n) m_nNewUserGroup--;
 
-  RemoveGroupID(n);
-
   SaveGroups();
   UnlockGroupList();
 
   // Send signal to let plugins know of the removed group
-  // FIXME: Since removing a group causes other groups to chande id, we can't
-  // just send the LIST_GROUP_REMOVED signal.
-  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExLIST, LIST_INVALIDATE, NULL, 0, 0, 0));
+  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExLIST, LIST_GROUP_REMOVED, NULL, 0, groupId, 0));
+
+  // Send signal to let plugins know that sorting indexes may have changed
+  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExLIST, LIST_GROUP_REORDERED, NULL, 0, 0, 0));
 }
 
-
-/*---------------------------------------------------------------------------
- * CUserManager::SwapGroups
- *-------------------------------------------------------------------------*/
-void CUserManager::SwapGroups(unsigned short g1, unsigned short g2)
+void CUserManager::ModifyGroupSorting(unsigned short groupId, unsigned short newIndex)
 {
-  LockGroupList(LOCK_W);
+  LicqGroup* group = FetchGroup(groupId, LOCK_R);
+  if (group == NULL)
+    return;
 
-  // validate the group numbers
-  if (g1 > m_vszGroups.size() || g1 < 1 || g2 > m_vszGroups.size() || g2 < 1)
+  if (newIndex >= NumGroups())
+    newIndex = NumGroups() - 1;
+
+  unsigned short oldIndex = group->sortIndex();
+  DropGroup(group);
+
+  GroupMap* g = LockGroupList(LOCK_R);
+
+  // Move all groups between new and old position one step
+  for (GroupMap::iterator i = g->begin(); i != g->end(); ++i)
   {
-    UnlockGroupList();
-    return;
+    i->second->Lock(LOCK_W);
+    unsigned short si = i->second->sortIndex();
+    if (newIndex < oldIndex && si >= newIndex && si < oldIndex)
+      i->second->setSortIndex(si + 1);
+    else if (newIndex > oldIndex && si > oldIndex && si <= newIndex)
+      i->second->setSortIndex(si - 1);
+    i->second->Unlock();
   }
 
-  // move the actual group
-  char *g = m_vszGroups[g1 - 1];
-  m_vszGroups[g1 - 1] = m_vszGroups[g2 - 1];
-  m_vszGroups[g2 - 1] = g;
-  if (m_nDefaultGroup == g1) m_nDefaultGroup = g2;
-  else if (m_nDefaultGroup == g2) m_nDefaultGroup = g1;
-  if (m_nNewUserGroup == g1) m_nNewUserGroup = g2;
-  else if (m_nNewUserGroup == g2) m_nNewUserGroup = g1;
+  group->Lock(LOCK_W);
+  group->setSortIndex(newIndex);
+  group->Unlock();
+
   SaveGroups();
   UnlockGroupList();
 
-  // adjust all the users
-  bool bInG1;
-  FOR_EACH_USER_START(LOCK_W)
-  {
-    bInG1 = pUser->GetInGroup(GROUPS_USER, g1);
-    pUser->SetInGroup(GROUPS_USER, g1, pUser->GetInGroup(GROUPS_USER, g2));
-    pUser->SetInGroup(GROUPS_USER, g2, bInG1);
-  }
-  FOR_EACH_USER_END
-
-  // swap the group ids
-  LockGroupIDList(LOCK_W);
-  unsigned short nTmp = m_vnGroupsID[g1 - 1];
-  m_vnGroupsID[g1 - 1] = m_vnGroupsID[g2 - 1];
-  m_vnGroupsID[g2 - 1] = nTmp;
-  SaveGroupIDs();
-  UnlockGroupIDList();
-
-  // Send signals to let plugins know that the groups sorting has changed
-  // FIXME: Until sorting is separated from group id, we'll have plugins reload
-  // the entire list to avoid problems.
-  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExLIST, LIST_INVALIDATE, NULL, 0, 0, 0));
+  // Send signal to let plugins know that sorting indexes have changed
+  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExLIST, LIST_GROUP_REORDERED, NULL, 0, 0, 0));
 }
 
-
 /*---------------------------------------------------------------------------
  * CUserManager::RenameGroup
  *-------------------------------------------------------------------------*/
-void CUserManager::RenameGroup(unsigned short n, const char *_sz, bool _bUpdate)
+bool CUserManager::RenameGroup(unsigned short groupId, const string& name, bool sendUpdate)
 {
-  if (n < 1 || n > NumGroups()) return;
-  GroupList *g = LockGroupList(LOCK_W);
-  free((*g)[n - 1]);
-  (*g)[n - 1] = strdup(_sz);
+  if (GetGroupFromName(name) != 0)
+  {
+    // Don't allow a duplicate name
+    gLog.Warn(tr("%sGroup name %s is already in list.\n"), L_WARNxSTR, name.c_str());
+    return false;
+  }
+
+  LicqGroup* group = FetchGroup(groupId, LOCK_W);
+  if (group == NULL)
+    return false;
+
+  group->setName(name);
+  unsigned short icqGroupId = group->icqGroupId();
+  DropGroup(group);
+
+  LockGroupList(LOCK_R);
   SaveGroups();
   UnlockGroupList();
 
-  LockGroupIDList(LOCK_R);
-  unsigned short nGSID = m_vnGroupsID[n-1];
-  UnlockGroupIDList();
-
   // If we rename a group on logon, don't send the rename packet
-  if (gLicqDaemon && _bUpdate)
-    gLicqDaemon->icqRenameGroup(_sz, nGSID);
+  if (gLicqDaemon != NULL && sendUpdate)
+    gLicqDaemon->icqRenameGroup(name.c_str(), icqGroupId);
 
   // Send signal to let plugins know the group has changed
-  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExLIST, LIST_GROUP_CHANGED, NULL, 0, n, 0));
+  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExLIST, LIST_GROUP_CHANGED, NULL, 0, groupId, 0));
+
+  return true;
 }
 
-
 /*---------------------------------------------------------------------------
  * CUserManager::SaveGroups
  *
@@ -1029,21 +1060,30 @@
   licqConf.LoadFile(filename);
 
   licqConf.SetSection("groups");
-  licqConf.WriteNum("NumOfGroups", NumGroups());
+  GroupMap::size_type count = myGroups.size();
+  licqConf.WriteNum("NumOfGroups", count);
 
-  char sGroupKey[MAX_KEYxNAME_LEN], sGroupIDKey[MAX_KEYxNAME_LEN];
-  //LockGroupList(LOCK_R);
-  LockGroupIDList(LOCK_R);
-  for (unsigned short i = 0; i < m_vszGroups.size(); i++)
+  char key[MAX_KEYxNAME_LEN];
+  int i = 1;
+  for (GroupMap::iterator group = myGroups.begin(); group != myGroups.end(); ++group)
   {
-     sprintf(sGroupKey, "Group%d.name", i + 1);
-     licqConf.WriteStr(sGroupKey, m_vszGroups[i]);
+    group->second->Lock(LOCK_R);
 
-     sprintf(sGroupIDKey, "Group%d.id", i + 1);
-     licqConf.WriteNum(sGroupIDKey, m_vnGroupsID[i]);
+    sprintf(key, "Group%d.name", i);
+    licqConf.WriteStr(key, group->second->name().c_str());
+
+    sprintf(key, "Group%d.id", i);
+    licqConf.WriteNum(key, group->second->id());
+
+    sprintf(key, "Group%d.IcqServerId", i);
+    licqConf.WriteNum(key, group->second->icqGroupId());
+
+    sprintf(key, "Group%d.Sorting", i);
+    licqConf.WriteNum(key, group->second->sortIndex());
+
+    group->second->Unlock();
+    ++i;
   }
-  UnlockGroupIDList();
-  //UnlockGroupList();
 
   licqConf.WriteNum("DefaultGroup", m_nDefaultGroup);
   licqConf.WriteNum("NewUserGroup", m_nNewUserGroup);
@@ -1052,129 +1092,91 @@
 }
 
 /*---------------------------------------------------------------------------
- * CUserManager::AddGroupID
+ * CUserManager::GetIDFromGroup
  *-------------------------------------------------------------------------*/
-void CUserManager::AddGroupID(unsigned short nID)
+unsigned short CUserManager::GetIDFromGroup(const string& name)
 {
-  LockGroupIDList(LOCK_W);
-  m_vnGroupsID.push_back(nID);
-  UnlockGroupIDList();
+  unsigned short groupId = GetGroupFromName(name);
+  if (groupId == 0)
+    return 0;
+
+  return GetIDFromGroup(groupId);
 }
 
-/*---------------------------------------------------------------------------
- * CUserManager::RemoveGroupID
- *-------------------------------------------------------------------------*/
-void CUserManager::RemoveGroupID(unsigned short n)
+unsigned short CUserManager::GetIDFromGroup(unsigned short groupId)
 {
-  LockGroupIDList(LOCK_W);
-  m_vnGroupsID.erase(m_vnGroupsID.begin()+n-1);
-  SaveGroupIDs();
-  UnlockGroupIDList();
+  LicqGroup* group = gUserManager.FetchGroup(groupId, LOCK_R);
+  if (group == NULL)
+    return 0;
+
+  unsigned short icqGroupId = group->icqGroupId();
+  DropGroup(group);
+
+  return icqGroupId;
 }
 
 /*---------------------------------------------------------------------------
- * CUserManager::GetIDFromGroup
+ * CUserManager::GetGroupFromID
  *-------------------------------------------------------------------------*/
-unsigned short CUserManager::GetIDFromGroup(const char *_szName)
+unsigned short CUserManager::GetGroupFromID(unsigned short icqGroupId)
 {
-  unsigned short nID = 0;
-  unsigned short nGroup = 0;
-
-  LockGroupList(LOCK_R);
-  LockGroupIDList(LOCK_R);
-  for (GroupList::iterator i = m_vszGroups.begin(); i != m_vszGroups.end();
-    ++i)
+  const GroupMap* groups = LockGroupList(LOCK_R);
+  GroupMap::const_iterator iter;
+  unsigned short groupId = 0;
+  for (iter = groups->begin(); iter != groups->end(); ++iter)
   {
-    if (strcmp(_szName, *i) == 0)
-    {			
-      nID = m_vnGroupsID[nGroup];
-      break;
-    }
-    nGroup++;
+    iter->second->Lock(LOCK_R);
+    if (iter->second->icqGroupId() == icqGroupId)
+      groupId = iter->first;
+    iter->second->Unlock();
   }
-  UnlockGroupIDList();
   UnlockGroupList();
 
-  return nID;
+  return groupId;
 }
 
-/*---------------------------------------------------------------------------
- * CUserManager::GetGroupFromID
- *-------------------------------------------------------------------------*/
-unsigned short CUserManager::GetGroupFromID(unsigned short nID)
+unsigned short CUserManager::GetGroupFromName(const string& name)
 {
-  unsigned short nGroup = 0;
-  
-  LockGroupIDList(LOCK_R);
-  for (GroupIDList::iterator i = m_vnGroupsID.begin(); i != m_vnGroupsID.end();
-      ++i)
+  const GroupMap* groups = LockGroupList(LOCK_R);
+  GroupMap::const_iterator iter;
+  unsigned short id = 0;
+  for (iter = groups->begin(); iter != groups->end(); ++iter)
   {
-    nGroup++;
-    if (*i == nID)  break;
+    iter->second->Lock(LOCK_R);
+    if (iter->second->name() == name)
+      id = iter->first;
+    iter->second->Unlock();
   }
-  UnlockGroupIDList();
+  UnlockGroupList();
 
-  return nGroup;
+  return id;
 }
 
 /*---------------------------------------------------------------------------
  * CUserManager::ModifyGroupID
  *-------------------------------------------------------------------------*/
-void CUserManager::ModifyGroupID(char *szGroup, unsigned short nNewID)
+void CUserManager::ModifyGroupID(const string& name, unsigned short icqGroupId)
 {
-  unsigned short nGroup = 0;
+  unsigned short id = GetGroupFromName(name);
+  if (id != 0)
+    ModifyGroupID(id, icqGroupId);
+}
 
-  LockGroupList(LOCK_R);
-  LockGroupIDList(LOCK_W);
+void CUserManager::ModifyGroupID(unsigned short groupId, unsigned short icqGroupId)
+{
+  LicqGroup* group = FetchGroup(groupId, LOCK_W);
+  if (group == NULL)
+    return;
 
-  for (GroupList::iterator i = m_vszGroups.begin(); i != m_vszGroups.end();
-      ++i)
-  {
-    if (strcmp(*i, szGroup) == 0)
-    {
-      m_vnGroupsID[nGroup] = nNewID;
-      break;
-    }
-    else
-      nGroup++;
-  }
+  group->setIcqGroupId(icqGroupId);
+  DropGroup(group);
 
-  UnlockGroupIDList();
-
+  LockGroupList(LOCK_R);
   SaveGroups();
   UnlockGroupList();
 }
 
 /*---------------------------------------------------------------------------
- * CUserManager::SaveGroupIDs
- *
- * Assumes a lock on the group id list
- *-------------------------------------------------------------------------*/
-void CUserManager::SaveGroupIDs()
-{
-  if (!m_bAllowSave) return;
-
-  // Load the group info from licq.conf
-  char filename[MAX_FILENAME_LEN];
-  snprintf(filename, MAX_FILENAME_LEN, "%s/licq.conf", BASE_DIR);
-  filename[MAX_FILENAME_LEN - 1] = '\0';
-  CIniFile licqConf(INI_FxWARN);
-  licqConf.LoadFile(filename);
-
-  licqConf.SetSection("groups");
-
-  char sGroupKey[MAX_KEYxNAME_LEN];
-  for (unsigned short i = 0; i < m_vnGroupsID.size(); i++)
-  {
-     sprintf(sGroupKey, "Group%d.id", i + 1);
-     licqConf.WriteNum(sGroupKey, m_vnGroupsID[i]);
-  }
-
-  licqConf.FlushFile();
-  licqConf.CloseFile();
-}
-
-/*---------------------------------------------------------------------------
  * CUserManager::GenerateSID
  *
  * Generate a random number.  Make sure no user has that random number.  If
@@ -1226,10 +1228,14 @@
     if (bCheckGroup)
     {
       // Check our groups too!
-      GroupIDList *gID = gUserManager.LockGroupIDList(LOCK_R);
-      for (unsigned short j = 0; j < gID->size(); j++)
+      const GroupMap* groups = LockGroupList(LOCK_R);
+      GroupMap::const_iterator j;
+      for (j = groups->begin(); j != groups->end(); ++j)
       {
-        if ((*gID)[j] == nSID)
+        j->second->Lock(LOCK_R);
+        unsigned short icqGroupId = j->second->icqGroupId();
+        j->second->Unlock();
+        if (icqGroupId == nSID)
         {
           if (nSID == 0x7FFF)
             nSID = 1;
@@ -1239,7 +1245,7 @@
           break;
         }
       }
-      gUserManager.UnlockGroupIDList();
+      UnlockGroupList();
     }
 
   } while (!bDone);
@@ -1409,7 +1415,7 @@
 unsigned short CUserManager::NumGroups()
 {
   //LockGroupList(LOCK_R);
-  unsigned short n = m_vszGroups.size();
+  unsigned short n = myGroups.size();
   //UnlockGroupList();
   return n;
 }
@@ -1464,9 +1470,9 @@
  *
  * Locks the entire group list for iterating through...
  *-------------------------------------------------------------------------*/
-GroupList *CUserManager::LockGroupList(unsigned short _nLockType)
+GroupMap* CUserManager::LockGroupList(unsigned short lockType)
 {
-  switch (_nLockType)
+  switch (lockType)
   {
   case LOCK_R:
     pthread_rdwr_rlock_np (&mutex_grouplist);
@@ -1478,20 +1484,18 @@
     assert(false);
     return NULL;
   }
-  m_nGroupListLockType = _nLockType;
-  return &m_vszGroups;
+  myGroupListLockType = lockType;
+  return &myGroups;
 }
 
-
-
 /*---------------------------------------------------------------------------
  * CUserManager::UnlockGroupList
  *-------------------------------------------------------------------------*/
 void CUserManager::UnlockGroupList()
 {
-  unsigned short nLockType = m_nGroupListLockType;
-  m_nGroupListLockType = LOCK_R;
-  switch (nLockType)
+  unsigned short lockType = myGroupListLockType;
+  myGroupListLockType = LOCK_R;
+  switch (lockType)
   {
   case LOCK_R:
     pthread_rdwr_runlock_np(&mutex_grouplist);
@@ -1505,53 +1509,6 @@
   }
 }
 
-
-/*---------------------------------------------------------------------------
- * LockGroupIDList
- *
- * Locks the entire group id list for iterating through...
- *-------------------------------------------------------------------------*/
-GroupIDList *CUserManager::LockGroupIDList(unsigned short _nLockType)
-{
-  switch (_nLockType)
-  {
-  case LOCK_R:
-    pthread_rdwr_rlock_np (&mutex_groupidlist);
-    break;
-  case LOCK_W:
-    pthread_rdwr_wlock_np(&mutex_groupidlist);
-    break;
-  default:
-    assert(false);
-    return NULL;
-  }
-  m_nGroupIDListLockType = _nLockType;
-  return &m_vnGroupsID;
-}
-
-
-
-/*---------------------------------------------------------------------------
- * CUserManager::UnlockGroupIDList
- *-------------------------------------------------------------------------*/
-void CUserManager::UnlockGroupIDList()
-{
-  unsigned short nLockType = m_nGroupIDListLockType;
-  m_nGroupIDListLockType = LOCK_R;
-  switch (nLockType)
-  {
-  case LOCK_R:
-    pthread_rdwr_runlock_np(&mutex_groupidlist);
-    break;
-  case LOCK_W:
-    pthread_rdwr_wunlock_np(&mutex_groupidlist);
-    break;
-  default:
-    assert(false);
-    break;
-  }
-}
-
 OwnerList *CUserManager::LockOwnerList(unsigned short _nLockType)
 {
   switch (_nLockType)
@@ -1603,6 +1560,8 @@
   if (gLicqDaemon && nPPID == LICQ_PPID)
     gLicqDaemon->icqChangeGroup(szId, nPPID, _nGroup,
       nGSID, ICQ_ROSTxNORMAL, ICQ_ROSTxNORMAL);
+
+  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExUSER, USER_GENERAL, szId, nPPID));
 }
 
 void CUserManager::AddUserToGroup(unsigned long _nUin, unsigned short _nGroup)
@@ -1615,9 +1574,10 @@
   if (gLicqDaemon)
     gLicqDaemon->icqChangeGroup(_nUin, _nGroup, nGSID, ICQ_ROSTxNORMAL,
       ICQ_ROSTxNORMAL);
+
+  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExUSER, USER_GENERAL, _nUin));
 }
 
-
 /*---------------------------------------------------------------------------
  * CUserManager::RemoveUserFromGroup
  *-------------------------------------------------------------------------*/
@@ -1628,18 +1588,20 @@
   if (u == NULL) return;
   u->RemoveFromGroup(GROUPS_USER, _nGroup);
   DropUser(u);
+
+  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExUSER, USER_GENERAL, szId, nPPID));
 }
 
-
 void CUserManager::RemoveUserFromGroup(unsigned long _nUin, unsigned short _nGroup)
 {
   ICQUser *u = FetchUser(_nUin, LOCK_W);
   if (u == NULL) return;
   u->RemoveFromGroup(GROUPS_USER, _nGroup);
   DropUser(u);
+
+  gLicqDaemon->PushPluginSignal(new CICQSignal(SIGNAL_UPDATExUSER, USER_GENERAL, _nUin));
 }
 
-
 //=====CUserHashTable===========================================================
 CUserHashTable::CUserHashTable(unsigned short _nSize) : m_vlTable(_nSize)
 {
@@ -1797,23 +1759,50 @@
 
 
 
+LicqGroup::LicqGroup(unsigned short id, const string& name)
+  : myId(id),
+    myName(name),
+    mySortIndex(0),
+    myIcqGroupId(0),
+    myLockType(LOCK_R)
+{
+}
 
+void LicqGroup::Lock(unsigned short lockType)
+{
+  switch (lockType)
+  {
+    case LOCK_R:
+      pthread_rdwr_rlock_np(&myMutex);
+      break;
+    case LOCK_W:
+      pthread_rdwr_wlock_np(&myMutex);
+      break;
+    default:
+      assert(false);
+      return;
+  }
+  myLockType = lockType;
+}
 
+void LicqGroup::Unlock()
+{
+  unsigned short lockType = myLockType;
+  myLockType = LOCK_R;
+  switch (lockType)
+  {
+    case LOCK_R:
+      pthread_rdwr_runlock_np(&myMutex);
+      break;
+    case LOCK_W:
+      pthread_rdwr_wunlock_np(&myMutex);
+      break;
+    default:
+      assert(false);
+      break;
+  }
+}
 
-
-
-
-
-
-
-
-
-
-
-
-
-
-
 //=====CUser====================================================================
 
 unsigned short ICQUser::s_nNumUserEvents = 0;
@@ -2053,8 +2042,7 @@
   unsigned long nLast;
   unsigned short nPPFieldCount;
   m_fConf.SetSection("user");
-  m_fConf.ReadNum("Groups.System", m_nGroups[GROUPS_SYSTEM], 0);
-  m_fConf.ReadNum("Groups.User", m_nGroups[GROUPS_USER], 0);
+  m_fConf.ReadNum("Groups.System", mySystemGroups, 0);
   m_fConf.ReadStr("Ip", szTemp, "0.0.0.0");
   struct in_addr in;
   m_nIp = inet_aton(szTemp, &in);
@@ -2126,6 +2114,28 @@
     }
   }
 
+  unsigned short userGroupCount;
+  if (m_fConf.ReadNum("GroupCount", userGroupCount, 0))
+  {
+    for (unsigned short i = 1; i <= userGroupCount; ++i)
+    {
+      sprintf(szTemp, "Group%u", i);
+      unsigned short groupId;
+      m_fConf.ReadNum(szTemp, groupId, 0);
+      if (groupId > 0)
+        AddToGroup(GROUPS_USER, groupId);
+    }
+  }
+  else
+  {
+    // Groupcount is missing in user config, try and read old group configuration
+    unsigned long oldGroups;
+    m_fConf.ReadNum("Groups.User", oldGroups, 0);
+    for (unsigned short i = 0; i <= 31; ++i)
+      if (oldGroups & (1L << i))
+        AddToGroup(GROUPS_USER, i+1);
+  }
+
   m_bSupportsUTF8 = false;
   
   if (nNewMessages > 0)
@@ -2474,11 +2484,10 @@
   char szTemp[12];
   SetAlias(IdString());
   SetHistoryFile("default");
-  SetGroups(GROUPS_SYSTEM, 0);
+  SetSystemGroups(0);
+  myGroups.clear();
   if (gUserManager.NewUserGroup())
-    SetGroups(GROUPS_USER, (unsigned long)(1 << (gUserManager.NewUserGroup()-1)));
-  else
-    SetGroups(GROUPS_USER, 0);
+    myGroups.insert(gUserManager.NewUserGroup());
   SetNewUser(true);
   SetAuthorization(false);
   SetNewUser(true);
@@ -3588,8 +3597,7 @@
    char buf[64];
    m_fConf.SetSection("user");
    m_fConf.WriteStr("History", HistoryName());
-   m_fConf.WriteNum("Groups.System", GetGroups(GROUPS_SYSTEM));
-   m_fConf.WriteNum("Groups.User", GetGroups(GROUPS_USER));
+   m_fConf.WriteNum("Groups.System", GetSystemGroups());
    m_fConf.WriteStr("Ip", ip_ntoa(m_nIp, buf));
    m_fConf.WriteStr("IntIp", ip_ntoa(m_nIntIp, buf));
    m_fConf.WriteNum("Port", Port());
@@ -3641,6 +3649,15 @@
      return;
    }
 
+  m_fConf.WriteNum("GroupCount", myGroups.size());
+  i = 1;
+  for (UserGroupList::iterator g = myGroups.begin(); g != myGroups.end(); ++g)
+  {
+    sprintf(buf, "Group%u", i);
+    m_fConf.WriteNum(buf, *g);
+    ++i;
+  }
+
    m_fConf.CloseFile();
 }
 
@@ -3817,10 +3834,15 @@
 }
 
 
-bool ICQUser::GetInGroup(GroupType g, unsigned short _nGroup)
+bool ICQUser::GetInGroup(GroupType gtype, unsigned short groupId) const
 {
-  if (_nGroup == 0) return true;
-  return (GetGroups(g) & (unsigned long)(1 << (_nGroup - 1)));
+  if (groupId == 0)
+      return true;
+
+  if (gtype == GROUPS_SYSTEM)
+    return mySystemGroups & (1L << (groupId -1)) != 0;
+  else
+    return myGroups.count(groupId) > 0;
 }
 
 void ICQUser::SetInGroup(GroupType g, unsigned short _nGroup, bool _bIn)
@@ -3831,20 +3853,32 @@
     RemoveFromGroup(g, _nGroup);
 }
 
-void ICQUser::AddToGroup(GroupType g, unsigned short _nGroup)
+void ICQUser::AddToGroup(GroupType gtype, unsigned short groupId)
 {
-  if (_nGroup == 0) return;
-  SetGroups(g, GetGroups(g) | (unsigned long)(1 << (_nGroup - 1)));
+  if (groupId == 0) return;
+
+  if (gtype == GROUPS_SYSTEM)
+    mySystemGroups |= (1L << (groupId - 1));
+  else
+    myGroups.insert(groupId);
 }
 
+bool ICQUser::RemoveFromGroup(GroupType gtype, unsigned short groupId)
+{
+  if (groupId == 0)
+    return false;
 
-void ICQUser::RemoveFromGroup(GroupType g, unsigned short _nGroup)
-{
-  if (_nGroup == 0) return;
-  SetGroups(g, GetGroups(g) & (0xFFFFFFFF - (unsigned long)(1 << (_nGroup - 1))));
+  if (gtype == GROUPS_SYSTEM)
+  {
+    unsigned long mask = 1L << (groupId - 1);
+    bool inGroup = mySystemGroups & mask;
+    mySystemGroups &= ~mask;
+    return inGroup;
+  }
+  else
+    return myGroups.erase(groupId);
 }
 
-
 unsigned short ICQUser::getNumUserEvents()
 {
   pthread_mutex_lock(&mutex_nNumUserEvents);
Index: src/icqd-srv.cpp
===================================================================
--- src/icqd-srv.cpp	(revision 6226)
+++ src/icqd-srv.cpp	(working copy)
@@ -117,24 +117,9 @@
 void CICQDaemon::CheckExport()
 {
   // Export groups
-  GroupList groups;
-  GroupList *g = gUserManager.LockGroupList(LOCK_R);
-  GroupIDList *gID = gUserManager.LockGroupIDList(LOCK_R);
+  if (gUserManager.NumGroups() > 0)
+    icqExportGroups();
 
-  for (unsigned int i = 0; i < gID->size(); i++)
-  {
-    if ((*gID)[i] == 0)
-    {
-      groups.push_back((*g)[i]);
-    }
-  }
-
-  gUserManager.UnlockGroupList();
-  gUserManager.UnlockGroupIDList();
-
-  if (groups.size())
-    icqExportGroups(groups);
-
   // Just upload all of the users now
   UserStringList doneUsers;
   UserStringList users;
@@ -223,29 +208,31 @@
 {
   if (!UseServerContactList())  return;
   CSrvPacketTcp *pReply;
-  
+
   pReply = new CPU_UpdateToServerList("", ICQ_ROSTxGROUP, 0);
   addToModifyUsers(pReply->SubSequence(), "");
   gLog.Info(tr("%sUpdating top level group.\n"), L_SRVxSTR);
   SendExpectEvent_Server(0, pReply, NULL);
- 
-  GroupList *g = gUserManager.LockGroupList(LOCK_R);
-  GroupIDList *gID = gUserManager.LockGroupIDList(LOCK_R);
 
-  for (unsigned int i = 0; i < gID->size(); i++)
+  const GroupMap* groups = gUserManager.LockGroupList(LOCK_R);
+
+  GroupMap::const_iterator i;
+  for (i = groups->begin(); i != groups->end(); ++i)
   {
-    if ((*gID)[i])
+    unsigned int gid = i->second->icqGroupId();
+    if (gid != 0)
     {
-      pReply = new CPU_UpdateToServerList((*g)[i], ICQ_ROSTxGROUP,
-        (*gID)[i]);
-      gLog.Info(tr("%sUpdating group %s.\n"), L_SRVxSTR, (*g)[i]);   
+      i->second->Lock(LOCK_R);
+      const char* gname = i->second->name().c_str();
+      pReply = new CPU_UpdateToServerList(gname, ICQ_ROSTxGROUP, gid);
+      gLog.Info(tr("%sUpdating group %s.\n"), L_SRVxSTR, gname);
+      i->second->Unlock();
       addToModifyUsers(pReply->SubSequence(), "");
       SendExpectEvent_Server(0, pReply, NULL);
     }
   }
 
   gUserManager.UnlockGroupList();
-  gUserManager.UnlockGroupIDList();       
 }
 
 //-----icqAddGroup--------------------------------------------------------------
@@ -309,14 +296,14 @@
 }
 
 //-----icqExportGroups----------------------------------------------------------
-void CICQDaemon::icqExportGroups(GroupList &groups)
+void CICQDaemon::icqExportGroups()
 {
   if (!UseServerContactList()) return;
 
   CSrvPacketTcp *pStart = new CPU_ExportContactStart();
   SendEvent_Server(pStart);
 
-  CSrvPacketTcp *pExport = new CPU_ExportGroupsToServerList(groups);
+  CSrvPacketTcp *pExport = new CPU_ExportGroupsToServerList();
   gLog.Info(tr("%sExporting groups to server contact list...\n"), L_SRVxSTR);
   // We lump all the groups into one packet, so the success/failure result will
   // be based on all of them. So a generic name should be fine, but then we need
@@ -4504,13 +4491,14 @@
                 break;
 
 
-              GroupList *g = gUserManager.LockGroupList(LOCK_R);
+              LicqGroup* group = gUserManager.FetchGroup(n, LOCK_R);
               std::string groupName;
-              if (e->ExtraInfo() == 0)
+              if (e->ExtraInfo() == 0 || group == NULL)
                 groupName = ""; // top level
               else
-                groupName = (*g)[n-1];
-              gUserManager.UnlockGroupList();
+                groupName = group->name();
+              if (group != NULL)
+                gUserManager.DropGroup(group);
 
               // Start editing server list
               CSrvPacketTcp *pStart = new CPU_GenericFamily(ICQ_SNACxFAM_LIST,