From cb4a46e7fbe62d788e66ed6121c717a2d22a4d7c Mon Sep 17 00:00:00 2001 From: watcherhd Date: Thu, 21 Apr 2011 14:14:52 +0000 Subject: svn.miranda.im is moving to a new home! git-svn-id: http://miranda-plugins.googlecode.com/svn/trunk@7 e753b5eb-9565-29b2-b5c5-2cc6f99dfbcb --- icqj_mod/icq_servlist.c | 1925 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1925 insertions(+) create mode 100644 icqj_mod/icq_servlist.c (limited to 'icqj_mod/icq_servlist.c') diff --git a/icqj_mod/icq_servlist.c b/icqj_mod/icq_servlist.c new file mode 100644 index 0000000..1c532d1 --- /dev/null +++ b/icqj_mod/icq_servlist.c @@ -0,0 +1,1925 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000,2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001,2002 Jon Keating, Richard Hughes +// Copyright © 2002,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004,2005,2006 Joe Kucera +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// ----------------------------------------------------------------------------- +// +// File name : $Source: /cvsroot/miranda/miranda/protocols/IcqOscarJ/icq_servlist.c,v $ +// Revision : $Revision: 3119 $ +// Last change on : $Date: 2006-06-11 23:24:04 +0200 (Sun, 11 Jun 2006) $ +// Last change by : $Author: jokusoftware $ +// +// DESCRIPTION: +// +// Functions that handles list of used server IDs, sends low-level packets for SSI information +// +// ----------------------------------------------------------------------------- + +#include "icqoscar.h" + + + +extern BOOL bIsSyncingCL; + +static HANDLE hHookSettingChanged = NULL; +static HANDLE hHookContactDeleted = NULL; +static DWORD* pwIDList = NULL; +static int nIDListCount = 0; +static int nIDListSize = 0; + + + +// cookie struct for pending records +typedef struct ssipendingitem_t +{ + HANDLE hContact; + char* szGroupPath; + GROUPADDCALLBACK ofCallback; + servlistcookie* pCookie; +} ssipendingitem; + +static CRITICAL_SECTION servlistMutex; +static int nPendingCount = 0; +static int nPendingSize = 0; +static ssipendingitem** pdwPendingList = NULL; +static int nJustAddedCount = 0; +static int nJustAddedSize = 0; +static HANDLE* pdwJustAddedList = NULL; +static WORD* pwGroupRenameList = NULL; +static int nGroupRenameCount = 0; +static int nGroupRenameSize = 0; + +static DWORD updateServContact(HANDLE hContact); + + +// Add running group rename operation +void AddGroupRename(WORD wGroupID) +{ + EnterCriticalSection(&servlistMutex); + if (nGroupRenameCount >= nGroupRenameSize) + { + nGroupRenameSize += 10; + pwGroupRenameList = (WORD*)realloc(pwGroupRenameList, nGroupRenameSize * sizeof(WORD)); + } + + pwGroupRenameList[nGroupRenameCount] = wGroupID; + nGroupRenameCount++; + LeaveCriticalSection(&servlistMutex); +} + + +// Remove running group rename operation +void RemoveGroupRename(WORD wGroupID) +{ + int i, j; + + EnterCriticalSection(&servlistMutex); + if (pwGroupRenameList) + { + for (i = 0; i= nJustAddedSize) + { + nJustAddedSize += 10; + pdwJustAddedList = (HANDLE*)realloc(pdwJustAddedList, nJustAddedSize * sizeof(HANDLE)); + } + + pdwJustAddedList[nJustAddedCount] = hContact; + nJustAddedCount++; + LeaveCriticalSection(&servlistMutex); +} + + +// was the contact added during this serv-list load +BOOL IsContactJustAdded(HANDLE hContact) +{ + int i; + + EnterCriticalSection(&servlistMutex); + if (pdwJustAddedList) + { + for (i = 0; ihContact == hContact) + { // we need the last item for this contact + pItem = pdwPendingList[i]; + } + } + } + + if (pItem) // we found a pending operation, so link our data + { + pItem->ofCallback = ofEvent; + pItem->pCookie = cookie; + pItem->szGroupPath = null_strdup(szGroup); // we need to duplicate the string + bRes = FALSE; + + NetLog_Server("Operation postponed."); + } + + if (nPendingCount >= nPendingSize) // add new + { + nPendingSize += 10; + pdwPendingList = (ssipendingitem**)realloc(pdwPendingList, nPendingSize * sizeof(ssipendingitem*)); + } + + pdwPendingList[nPendingCount] = (ssipendingitem*)SAFE_MALLOC(sizeof(ssipendingitem)); + pdwPendingList[nPendingCount]->hContact = hContact; + + nPendingCount++; + LeaveCriticalSection(&servlistMutex); + + return bRes; +} + + + +// Check if any pending operation is in progress +// If yes, get its data and remove it from queue +void RemovePendingOperation(HANDLE hContact, int nResult) +{ + int i, j; + ssipendingitem* pItem = NULL; + + EnterCriticalSection(&servlistMutex); + if (pdwPendingList) + { + for (i = 0; ihContact == hContact) + { + pItem = pdwPendingList[i]; + for (j = i+1; jofCallback) + { + NetLog_Server("Resuming postponed operation."); + + makeGroupId(pItem->szGroupPath, pItem->ofCallback, pItem->pCookie); + } + else if ((int)pItem->pCookie == 1) + { + NetLog_Server("Resuming postponed update."); + + updateServContact(hContact); + } + + SAFE_FREE(&pItem->szGroupPath); // free the string + SAFE_FREE(&pItem); + return; + } // else remove all pending operations for this contact + NetLog_Server("Purging postponed operation."); + if ((pItem->pCookie) && ((int)pItem->pCookie != 1)) + SAFE_FREE(&pItem->pCookie->szGroupName); // do not leak nick name on error + SAFE_FREE(&pItem->szGroupPath); + SAFE_FREE(&pItem); + } + } + } + LeaveCriticalSection(&servlistMutex); + return; +} + + + +// Remove All pending operations +void FlushPendingOperations() +{ + int i; + + EnterCriticalSection(&servlistMutex); + + for (i = 0; i= nIDListSize) + { + nIDListSize += 100; + pwIDList = (DWORD*)realloc(pwIDList, nIDListSize * sizeof(DWORD)); + } + + pwIDList[nIDListCount] = wID | bGroupId << 0x18; + nIDListCount++; + LeaveCriticalSection(&servlistMutex); +} + + + +// Remove a server ID from the list of reserved IDs. +// Used for deleting contacts and other modifications. +void FreeServerID(WORD wID, int bGroupId) +{ + int i, j; + DWORD dwId = wID | bGroupId << 0x18; + + EnterCriticalSection(&servlistMutex); + if (pwIDList) + { + for (i = 0; i= wID) && ((pwIDList[i] & 0xFFFF) <= wID + wCount)) + { + LeaveCriticalSection(&servlistMutex); + return TRUE; + } + } + } + LeaveCriticalSection(&servlistMutex); + + return FALSE; +} + + + +void FlushServerIDs() +{ + EnterCriticalSection(&servlistMutex); + SAFE_FREE(&pwIDList); + nIDListCount = 0; + nIDListSize = 0; + LeaveCriticalSection(&servlistMutex); +} + + + +static int GroupReserveIdsEnumProc(const char *szSetting,LPARAM lParam) +{ + if (szSetting && strlennull(szSetting)<5) + { // it is probably server group + char val[MAX_PATH+2]; // dummy + DBVARIANT dbv; + DBCONTACTGETSETTING cgs; + + dbv.type = DBVT_ASCIIZ; + dbv.pszVal = val; + dbv.cchVal = MAX_PATH; + + cgs.szModule=(char*)lParam; + cgs.szSetting=szSetting; + cgs.pValue=&dbv; + if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC,(WPARAM)NULL,(LPARAM)&cgs)) + { // we failed to read setting, try also utf8 - DB bug + dbv.type = DBVT_UTF8; + dbv.pszVal = val; + dbv.cchVal = MAX_PATH; + if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC,(WPARAM)NULL,(LPARAM)&cgs)) + return 0; // we failed also, invalid setting + } + if (dbv.type!=DBVT_ASCIIZ) + { // it is not a cached server-group name + return 0; + } + ReserveServerID((WORD)strtoul(szSetting, NULL, 0x10), SSIT_GROUP); +#ifdef _DEBUG + NetLog_Server("Loaded group %u:'%s'", strtoul(szSetting, NULL, 0x10), val); +#endif + } + return 0; +} + + + +int ReserveServerGroups() +{ + DBCONTACTENUMSETTINGS dbces; + int nStart = nIDListCount; + + char szModule[MAX_PATH+9]; + + strcpy(szModule, gpszICQProtoName); + strcat(szModule, "SrvGroups"); + + dbces.pfnEnumProc = &GroupReserveIdsEnumProc; + dbces.szModule = szModule; + dbces.lParam = (LPARAM)szModule; + + CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)NULL, (LPARAM)&dbces); + + return nIDListCount - nStart; +} + + + +// Load all known server IDs from DB to list +void LoadServerIDs() +{ + HANDLE hContact; + WORD wSrvID; + int nGroups = 0, nContacts = 0, nPermits = 0, nDenys = 0, nIgnores = 0; + + EnterCriticalSection(&servlistMutex); + if (wSrvID = ICQGetContactSettingWord(NULL, "SrvAvatarID", 0)) + ReserveServerID(wSrvID, SSIT_ITEM); + if (wSrvID = ICQGetContactSettingWord(NULL, "SrvVisibilityID", 0)) + ReserveServerID(wSrvID, SSIT_ITEM); + + nGroups = ReserveServerGroups(); + + hContact = ICQFindFirstContact(); + + while (hContact) + { // search all our contacts, reserve their server IDs + if (wSrvID = ICQGetContactSettingWord(hContact, "ServerId", 0)) + { + ReserveServerID(wSrvID, SSIT_ITEM); + nContacts++; + } + if (wSrvID = ICQGetContactSettingWord(hContact, "SrvDenyId", 0)) + { + ReserveServerID(wSrvID, SSIT_ITEM); + nDenys++; + } + if (wSrvID = ICQGetContactSettingWord(hContact, "SrvPermitId", 0)) + { + ReserveServerID(wSrvID, SSIT_ITEM); + nPermits++; + } + if (wSrvID = ICQGetContactSettingWord(hContact, "SrvIgnoreId", 0)) + { + ReserveServerID(wSrvID, SSIT_ITEM); + nIgnores++; + } + + hContact = ICQFindNextContact(hContact); + } + LeaveCriticalSection(&servlistMutex); + + NetLog_Server("Loaded SSI: %d contacts, %d groups, %d permit, %d deny, %d ignore items.", nContacts, nGroups, nPermits, nDenys, nIgnores); + + return; +} + + + +WORD GenerateServerId(int bGroupId) +{ + WORD wId; + + while (TRUE) + { + // Randomize a new ID + // Max value is probably 0x7FFF, lowest value is probably 0x0001 (generated by Icq2Go) + // We use range 0x1000-0x7FFF. + wId = (WORD)RandRange(0x1000, 0x7FFF); + + if (!CheckServerID(wId, 0)) + break; + } + + ReserveServerID(wId, bGroupId); + + return wId; +} + + + +// Generate server ID with wCount IDs free after it, for sub-groups. +WORD GenerateServerIdPair(int bGroupId, int wCount) +{ + WORD wId; + + while (TRUE) + { + // Randomize a new ID + // Max value is probably 0x7FFF, lowest value is probably 0x0001 (generated by Icq2Go) + // We use range 0x1000-0x7FFF. + wId = (WORD)RandRange(0x1000, 0x7FFF); + + if (!CheckServerID(wId, wCount)) + break; + } + + ReserveServerID(wId, bGroupId); + + return wId; +} + + +/*********************************************** + * + * --- Low-level packet sending functions --- + * + */ + + +static DWORD icq_sendServerItem(DWORD dwCookie, WORD wAction, WORD wGroupId, WORD wItemId, const char *szName, BYTE *pTLVs, int nTlvLength, WORD wItemType) +{ // generic packet + icq_packet packet; + int nNameLen; + WORD wTLVlen = (WORD)nTlvLength; + + // Prepare item name length + nNameLen = strlennull(szName); + + // Build the packet + serverPacketInit(&packet, (WORD)(nNameLen + 20 + wTLVlen)); + packFNACHeaderFull(&packet, ICQ_LISTS_FAMILY, wAction, 0, dwCookie); + packWord(&packet, (WORD)nNameLen); + if (nNameLen) + packBuffer(&packet, szName, (WORD)nNameLen); + packWord(&packet, wGroupId); + packWord(&packet, wItemId); + packWord(&packet, wItemType); + packWord(&packet, wTLVlen); + if (wTLVlen) + packBuffer(&packet, pTLVs, wTLVlen); + + // Send the packet and return the cookie + sendServPacket(&packet); + + return dwCookie; +} + + + +DWORD icq_sendServerContact(HANDLE hContact, DWORD dwCookie, WORD wAction, WORD wGroupId, WORD wContactId) +{ + DWORD dwUin; + uid_str szUid; + icq_packet pBuffer; + char *szNick = NULL, *szNote = NULL; + BYTE *pData = NULL; + int nNickLen, nNoteLen, nDataLen; + WORD wTLVlen; + BYTE bAuth; + + // Prepare UID + if (ICQGetContactSettingUID(hContact, &dwUin, &szUid)) + { + NetLog_Server("Buddy upload failed (UID missing)."); + return 0; + } + + bAuth = ICQGetContactSettingByte(hContact, "Auth", 0); + szNick = UniGetContactSettingUtf(hContact, "CList", "MyHandle", NULL); + szNote = UniGetContactSettingUtf(hContact, "UserInfo", "MyNotes", NULL); + + { + DBVARIANT dbv; + + if (!DBGetContactSetting(hContact, gpszICQProtoName, "ServerData", &dbv)) + { // read additional server item data + nDataLen = dbv.cpbVal; + pData = (BYTE*)_alloca(nDataLen); + memcpy(pData, dbv.pbVal, nDataLen); + + ICQFreeVariant(&dbv); + } + else + { + pData = NULL; + nDataLen = 0; + } + } + + nNickLen = strlennull(szNick); + nNoteLen = strlennull(szNote); + + // Build the packet + wTLVlen = (nNickLen?4+nNickLen:0) + (nNoteLen?4+nNoteLen:0) + (bAuth?4:0) + nDataLen; + + // Initialize our handy data buffer + pBuffer.wPlace = 0; + pBuffer.pData = (BYTE *)_alloca(wTLVlen); + pBuffer.wLen = wTLVlen; + + if (nNickLen) + packTLV(&pBuffer, SSI_TLV_NAME, (WORD)nNickLen, szNick); // Nickname TLV + + if (nNoteLen) + packTLV(&pBuffer, SSI_TLV_COMMENT, (WORD)nNoteLen, szNote); // Comment TLV + + if (pData) + packBuffer(&pBuffer, pData, (WORD)nDataLen); + + if (bAuth) // icq5 gives this as last TLV + packDWord(&pBuffer, 0x00660000); // "Still waiting for auth" TLV + + return icq_sendServerItem(dwCookie, wAction, wGroupId, wContactId, strUID(dwUin, szUid), pBuffer.pData, wTLVlen, SSI_ITEM_BUDDY); +} + + + +DWORD icq_sendSimpleItem(DWORD dwCookie, WORD wAction, DWORD dwUin, char* szUID, WORD wGroupId, WORD wItemId, WORD wItemType) +{ // for privacy items + return icq_sendServerItem(dwCookie, wAction, wGroupId, wItemId, strUID(dwUin, szUID), NULL, 0, wItemType); +} + + + +DWORD icq_sendGroupUtf(DWORD dwCookie, WORD wAction, WORD wGroupId, const char *szName, void *pContent, int cbContent) +{ + WORD wTLVlen; + icq_packet pBuffer; // I reuse the ICQ packet type as a generic buffer + // I should be ashamed! ;) + + if (strlennull(szName) == 0 && wGroupId != 0) + { + NetLog_Server("Group upload failed (GroupName missing)."); + return 0; // without name we could not change the group + } + + // Calculate buffer size + wTLVlen = (cbContent?4+cbContent:0); + + // Initialize our handy data buffer + pBuffer.wPlace = 0; + pBuffer.pData = (BYTE *)_alloca(wTLVlen); + pBuffer.wLen = wTLVlen; + + if (wTLVlen) + packTLV(&pBuffer, SSI_TLV_SUBITEMS, (WORD)cbContent, pContent); // Groups TLV + + return icq_sendServerItem(dwCookie, wAction, wGroupId, 0, szName, pBuffer.pData, wTLVlen, SSI_ITEM_GROUP); +} + + + +DWORD icq_modifyServerPrivacyItem(HANDLE hContact, DWORD dwUin, char* szUid, WORD wAction, DWORD dwOperation, WORD wItemId, WORD wType) +{ + servlistcookie* ack; + DWORD dwCookie; + + if (!(ack = (servlistcookie*)SAFE_MALLOC(sizeof(servlistcookie)))) + { // cookie failed, use old fake + dwCookie = GenerateCookie(wAction); + } + else + { + ack->dwAction = dwOperation; // remove privacy item + ack->dwUin = dwUin; + ack->hContact = hContact; + ack->wContactId = wItemId; + + dwCookie = AllocateCookie(CKT_SERVERLIST, wAction, dwUin, ack); + } + return icq_sendSimpleItem(dwCookie, wAction, dwUin, szUid, 0, wItemId, wType); +} + + + +DWORD icq_removeServerPrivacyItem(HANDLE hContact, DWORD dwUin, char* szUid, WORD wItemId, WORD wType) +{ + return icq_modifyServerPrivacyItem(hContact, dwUin, szUid, ICQ_LISTS_REMOVEFROMLIST, SSA_PRIVACY_REMOVE, wItemId, wType); +} + + + +DWORD icq_addServerPrivacyItem(HANDLE hContact, DWORD dwUin, char* szUid, WORD wItemId, WORD wType) +{ + return icq_modifyServerPrivacyItem(hContact, dwUin, szUid, ICQ_LISTS_ADDTOLIST, SSA_PRIVACY_ADD, wItemId, wType); +} + + +/***************************************** + * + * --- Contact DB Utilities --- + * + */ + +static int GroupNamesEnumProc(const char *szSetting,LPARAM lParam) +{ // if we got pointer, store setting name, return zero + if (lParam) + { + char** block = (char**)SAFE_MALLOC(2*sizeof(char*)); + block[1] = null_strdup(szSetting); + block[0] = ((char**)lParam)[0]; + ((char**)lParam)[0] = (char*)block; + } + return 0; +} + + + +void DeleteModuleEntries(const char* szModule) +{ + DBCONTACTENUMSETTINGS dbces; + char** list = NULL; + + dbces.pfnEnumProc = &GroupNamesEnumProc; + dbces.szModule = szModule; + dbces.lParam = (LPARAM)&list; + CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)NULL, (LPARAM)&dbces); + while (list) + { + void* bet; + + DBDeleteContactSetting(NULL, szModule, list[1]); + SAFE_FREE(&list[1]); + bet = list; + list = (char**)list[0]; + SAFE_FREE(&bet); + } +} + + + +int IsServerGroupsDefined() +{ + int iRes = 1; + + if (ICQGetContactSettingDword(NULL, "Version", 0) < 0x00030608) + { // group cache & linking data too old, flush, reload from server + char szModule[MAX_PATH+9]; + + strcpy(szModule, gpszICQProtoName); + strcat(szModule, "Groups"); // flush obsolete linking data + DeleteModuleEntries(szModule); + + iRes = 0; // no groups defined, or older version + } + // store our current version + ICQWriteContactSettingDword(NULL, "Version", ICQ_PLUG_VERSION & 0x00FFFFFF); + + return iRes; +} + + + +void FlushSrvGroupsCache() +{ + char szModule[MAX_PATH+9]; + + strcpy(szModule, gpszICQProtoName); + strcat(szModule, "SrvGroups"); + DeleteModuleEntries(szModule); +} + + + +// Look thru DB and collect all ContactIDs from a group +void* collectBuddyGroup(WORD wGroupID, int *count) +{ + WORD* buf = NULL; + int cnt = 0; + HANDLE hContact; + WORD wItemID; + + hContact = ICQFindFirstContact(); + + while (hContact) + { // search all contacts + if (wGroupID == ICQGetContactSettingWord(hContact, "SrvGroupId", 0)) + { // add only buddys from specified group + wItemID = ICQGetContactSettingWord(hContact, "ServerId", 0); + + if (wItemID) + { // valid ID, add + cnt++; + buf = (WORD*)realloc(buf, cnt*sizeof(WORD)); + buf[cnt-1] = wItemID; + } + } + + hContact = ICQFindNextContact(hContact); + } + + *count = cnt<<1; // we return size in bytes + return buf; +} + + + +// Look thru DB and collect all GroupIDs +void* collectGroups(int *count) +{ + WORD* buf = NULL; + int cnt = 0; + int i; + HANDLE hContact; + WORD wGroupID; + + hContact = ICQFindFirstContact(); + + while (hContact) + { // search all contacts + if (wGroupID = ICQGetContactSettingWord(hContact, "SrvGroupId", 0)) + { // add only valid IDs + for (i = 0; i on start of a server group name +static int countGroupNameLevel(const char *szGroupName) +{ + int nNameLen = strlennull(szGroupName); + int i = 0; + + while (iversion >= 1) + { // we've got unicode interface, use it + wchar_t* usTmp = make_unicode_string(szGroupName); + + clint->pfnRenameGroup(hGroup, (TCHAR*)usTmp); + SAFE_FREE(&usTmp); + } + else + { // old ansi version - no other way + int size = strlennull(szGroupName) + 2; + char* szTmp = (char*)_alloca(size); + + utf8_decode_static(szGroupName, szTmp, size); + CallService(MS_CLIST_GROUPRENAME, hGroup, (LPARAM)szTmp); + } + + return hGroup; +} + + + +// demangle group path +char* makeGroupPathUtf(WORD wGroupId) +{ + char* szGroup = NULL; + + if (szGroup = getServerGroupNameUtf(wGroupId)) + { // this groupid is not valid + while (strstr(szGroup, "\\")!=NULL) *strstr(szGroup, "\\") = '_'; // remove invalid char + if (getServerGroupIDUtf(szGroup) == wGroupId) + { // this grouppath is known and is for this group, set user group + return szGroup; + } + else + { + if (strlennull(szGroup) && (szGroup[0] == '>')) + { // it is probably a sub-group + WORD wId = wGroupId-1; + int level = countGroupLevel(wGroupId); + int levnew = countGroupLevel(wId); + char* szTempGroup; + + if (level == -1) + { // this is just an ordinary group + int hGroup; + + if (!GroupNameExistsUtf(szGroup, -1)) + { // if the group does not exist, create it + hGroup = CreateCListGroup(szGroup); + } + setServerGroupIDUtf(szGroup, wGroupId); // set grouppath id + return szGroup; + } + while ((levnew >= level) && (levnew != -1)) + { // we look for parent group + wId--; + levnew = countGroupLevel(wId); + } + if (levnew == -1) + { // that was not a sub-group, it was just a group starting with > + if (!GroupNameExistsUtf(szGroup, -1)) + { // if the group does not exist, create it + int hGroup = CreateCListGroup(szGroup); + } + setServerGroupIDUtf(szGroup, wGroupId); // set grouppath id + return szGroup; + } + + szTempGroup = makeGroupPathUtf(wId); + + szTempGroup = realloc(szTempGroup, strlennull(szGroup)+strlennull(szTempGroup)+2); + strcat(szTempGroup, "\\"); + strcat(szTempGroup, szGroup+level); + SAFE_FREE(&szGroup); + szGroup = szTempGroup; + + if (getServerGroupIDUtf(szGroup) == wGroupId) + { // known path, give + return szGroup; + } + else + { // unknown path, create + if (!GroupNameExistsUtf(szGroup, -1)) + { // if the group does not exist, create it + int hGroup = CreateCListGroup(szGroup); + } + setServerGroupIDUtf(szGroup, wGroupId); // set grouppath id + return szGroup; + } + } + else + { // create that group + int hGroup; + + if (!GroupNameExistsUtf(szGroup, -1)) + { // if the group does not exist, create it + hGroup = CreateCListGroup(szGroup); + } + setServerGroupIDUtf(szGroup, wGroupId); // set grouppath id + return szGroup; + } + } + } + return NULL; +} + + + +// this is the second pard of recursive event-driven procedure +void madeMasterGroupId(WORD wGroupID, LPARAM lParam) +{ + servlistcookie* clue = (servlistcookie*)lParam; + char* szGroup = clue->szGroupName; + GROUPADDCALLBACK ofCallback = clue->ofCallback; + servlistcookie* param = (servlistcookie*)clue->lParam; + int level; + + if (wGroupID) // if we got an id count level + level = countGroupLevel(wGroupID); + else + level = -1; + + SAFE_FREE(&clue); + + if (level == -1) + { // something went wrong, give the id and go away + if (ofCallback) ofCallback(wGroupID, (LPARAM)param); + + SAFE_FREE(&szGroup); + return; + } + level++; // we are a sub + + // check if on that id is not group of the same or greater level, if yes, try next + while (CheckServerID((WORD)(wGroupID+1),0) && (countGroupLevel((WORD)(wGroupID+1)) >= level)) + { + wGroupID++; + } + + if (!CheckServerID((WORD)(wGroupID+1), 0)) + { // the next id is free, so create our group with that id + servlistcookie* ack; + DWORD dwCookie; + char* szSubGroup = (char*)SAFE_MALLOC(strlennull(szGroup)+level+1); + + if (szSubGroup) + { + int i; + + for (i=0; iwGroupId = wGroupID+1; + ack->szGroupName = szSubGroup; // we need that name + ack->dwAction = SSA_GROUP_ADD; + ack->ofCallback = ofCallback; + ack->lParam = (LPARAM)param; + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, 0, ack); + + sendAddStart(0); + icq_sendGroupUtf(dwCookie, ICQ_LISTS_ADDTOLIST, ack->wGroupId, szSubGroup, NULL, 0); + + SAFE_FREE(&szGroup); + return; + } + SAFE_FREE(&szSubGroup); + } + } + // we failed to create sub-group give parent groupid + if (ofCallback) ofCallback(wGroupID, (LPARAM)param); + + SAFE_FREE(&szGroup); + return; +} + + + +// create group with this path, a bit complex task +// this supposes that all server groups are known +WORD makeGroupId(const char* szGroupPath, GROUPADDCALLBACK ofCallback, servlistcookie* lParam) +{ + WORD wGroupID = 0; + char* szGroup = (char*)szGroupPath; + + if (!szGroup || szGroup[0]=='\0') szGroup = DEFAULT_SS_GROUP; + + if (wGroupID = getServerGroupIDUtf(szGroup)) + { + if (ofCallback) ofCallback(wGroupID, (LPARAM)lParam); + return wGroupID; // if the path is known give the id + } + + if (!strstr(szGroup, "\\")) + { // a root group can be simply created without problems + servlistcookie* ack; + DWORD dwCookie; + + if (ack = (servlistcookie*)SAFE_MALLOC(sizeof(servlistcookie))) + { // we have cookie good, go on + ack->wGroupId = GenerateServerId(SSIT_GROUP); + ack->szGroupName = null_strdup(szGroup); // we need that name + ack->dwAction = SSA_GROUP_ADD; + ack->ofCallback = ofCallback; + ack->lParam = (LPARAM)lParam; + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, 0, ack); + + sendAddStart(0); + icq_sendGroupUtf(dwCookie, ICQ_LISTS_ADDTOLIST, ack->wGroupId, szGroup, NULL, 0); + + return 0; + } + } + else + { // this is a sub-group + char* szSub = null_strdup(szGroup); // create subgroup, recursive, event-driven, possibly relocate + servlistcookie* ack; + char *szLast; + + if (strstr(szSub, "\\") != NULL) + { // determine parent group + szLast = strstr(szSub, "\\")+1; + + while (strstr(szLast, "\\") != NULL) + szLast = strstr(szLast, "\\")+1; // look for last backslash + szLast[-1] = '\0'; + } + // make parent group id + ack = (servlistcookie*)SAFE_MALLOC(sizeof(servlistcookie)); + if (ack) + { + WORD wRes; + ack->lParam = (LPARAM)lParam; + ack->ofCallback = ofCallback; + ack->szGroupName = null_strdup(szLast); // groupname + wRes = makeGroupId(szSub, madeMasterGroupId, ack); + SAFE_FREE(&szSub); + + return wRes; + } + + SAFE_FREE(&szSub); + } + + if (strstr(szGroup, "\\") != NULL) + { // we failed to get grouppath, trim it by one group + WORD wRes; + char *szLast = null_strdup(szGroup); + char *szLess = szLast; + + while (strstr(szLast, "\\") != NULL) + szLast = strstr(szLast, "\\")+1; // look for last backslash + szLast[-1] = '\0'; + wRes = makeGroupId(szLess, ofCallback, lParam); + SAFE_FREE(&szLess); + + return wRes; + } + + wGroupID = 0; // everything failed, let callback handle error + if (ofCallback) ofCallback(wGroupID, (LPARAM)lParam); + + return wGroupID; +} + + +/***************************************** + * + * --- Server-List Operations --- + * + */ + +void addServContactReady(WORD wGroupID, LPARAM lParam) +{ + WORD wItemID; + DWORD dwUin; + uid_str szUid; + servlistcookie* ack; + DWORD dwCookie; + + ack = (servlistcookie*)lParam; + + if (!ack || !wGroupID) // something went wrong + { + if (ack) RemovePendingOperation(ack->hContact, 0); + return; + } + + wItemID = ICQGetContactSettingWord(ack->hContact, "ServerId", 0); + + if (wItemID) + { // Only add the contact if it doesnt already have an ID + RemovePendingOperation(ack->hContact, 0); + NetLog_Server("Failed to add contact to server side list (%s)", "already there"); + return; + } + + // Get UID + if (ICQGetContactSettingUID(ack->hContact, &dwUin, &szUid)) + { // Could not do anything without uid + RemovePendingOperation(ack->hContact, 0); + NetLog_Server("Failed to add contact to server side list (%s)", "no UID"); + return; + } + + wItemID = GenerateServerId(SSIT_ITEM); + + ack->dwAction = SSA_CONTACT_ADD; + ack->dwUin = dwUin; + ack->wGroupId = wGroupID; + ack->wContactId = wItemID; + + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, dwUin, ack); + + sendAddStart(0); + icq_sendServerContact(ack->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, wGroupID, wItemID); +} + + + +// Called when contact should be added to server list, if group does not exist, create one +DWORD addServContact(HANDLE hContact, const char *pszNick, const char *pszGroup) +{ + servlistcookie* ack; + + if (!(ack = (servlistcookie*)SAFE_MALLOC(sizeof(servlistcookie)))) + { // Could not do anything without cookie + NetLog_Server("Failed to add contact to server side list (%s)", "malloc failed"); + return 0; + } + else + { + ack->hContact = hContact; + + if (AddPendingOperation(hContact, pszGroup, ack, addServContactReady)) + makeGroupId(pszGroup, addServContactReady, ack); + + return 1; + } +} + + + +// Called when contact should be removed from server list, remove group if it remain empty +DWORD removeServContact(HANDLE hContact) +{ + WORD wGroupID; + WORD wItemID; + DWORD dwUin; + uid_str szUid; + servlistcookie* ack; + DWORD dwCookie; + + // Get the contact's group ID + if (!(wGroupID = ICQGetContactSettingWord(hContact, "SrvGroupId", 0))) + { + // Could not find a usable group ID + NetLog_Server("Failed to remove contact from server side list (%s)", "no group ID"); + return 0; + } + + // Get the contact's item ID + if (!(wItemID = ICQGetContactSettingWord(hContact, "ServerId", 0))) + { + // Could not find usable item ID + NetLog_Server("Failed to remove contact from server side list (%s)", "no item ID"); + return 0; + } + + // Get UID + if (ICQGetContactSettingUID(hContact, &dwUin, &szUid)) + { + // Could not do anything without uid + NetLog_Server("Failed to remove contact from server side list (%s)", "no UID"); + return 0; + } + + if (!(ack = (servlistcookie*)SAFE_MALLOC(sizeof(servlistcookie)))) + { // Could not do anything without cookie + NetLog_Server("Failed to remove contact from server side list (%s)", "malloc failed"); + return 0; + } + else + { + ack->dwAction = SSA_CONTACT_REMOVE; + ack->dwUin = dwUin; + ack->hContact = hContact; + ack->wGroupId = wGroupID; + ack->wContactId = wItemID; + + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, dwUin, ack); + } + + sendAddStart(0); + icq_sendServerContact(hContact, dwCookie, ICQ_LISTS_REMOVEFROMLIST, wGroupID, wItemID); + + return 0; +} + + + +void moveServContactReady(WORD wNewGroupID, LPARAM lParam) +{ + WORD wItemID; + WORD wGroupID; + DWORD dwUin; + uid_str szUid; + servlistcookie* ack; + DWORD dwCookie, dwCookie2; + + ack = (servlistcookie*)lParam; + + if (!ack || !wNewGroupID) // something went wrong + { + if (ack) RemovePendingOperation(ack->hContact, 0); + return; + } + + if (!ack->hContact) return; // we do not move us, caused our uin was wrongly added to list + + wItemID = ICQGetContactSettingWord(ack->hContact, "ServerId", 0); + wGroupID = ICQGetContactSettingWord(ack->hContact, "SrvGroupId", 0); + + if (!wItemID) + { // We have no ID, so try to simply add the contact to serv-list + NetLog_Server("Unable to move contact (no ItemID) -> trying to add"); + // we know the GroupID, so directly call add + addServContactReady(wNewGroupID, lParam); + return; + } + + if (!wGroupID) + { // Only move the contact if it had an GroupID + RemovePendingOperation(ack->hContact, 0); + NetLog_Server("Failed to move contact to group on server side list (%s)", "no Group"); + return; + } + + if (wGroupID == wNewGroupID) + { // Only move the contact if it had different GroupID + RemovePendingOperation(ack->hContact, 1); + NetLog_Server("Contact not moved to group on server side list (same Group)"); + return; + } + + // Get UID + if (ICQGetContactSettingUID(ack->hContact, &dwUin, &szUid)) + { // Could not do anything without uin + RemovePendingOperation(ack->hContact, 0); + NetLog_Server("Failed to move contact to group on server side list (%s)", "no UID"); + return; + } + + ack->szGroupName = NULL; + ack->dwAction = SSA_CONTACT_SET_GROUP; + ack->dwUin = dwUin; + ack->wGroupId = wGroupID; + ack->wContactId = wItemID; + ack->wNewContactId = GenerateServerId(SSIT_ITEM); // icq5 recreates also this, imitate + ack->wNewGroupId = wNewGroupID; + ack->lParam = 0; // we use this as a sign + + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, dwUin, ack); + dwCookie2 = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, dwUin, ack); + + sendAddStart(0); + // imitate icq5, previously here was different order, but AOL changed and it ceased to work + icq_sendServerContact(ack->hContact, dwCookie, ICQ_LISTS_REMOVEFROMLIST, wGroupID, wItemID); + icq_sendServerContact(ack->hContact, dwCookie2, ICQ_LISTS_ADDTOLIST, wNewGroupID, ack->wNewContactId); +} + + + +// Called when contact should be moved from one group to another, create new, remove empty +DWORD moveServContactGroup(HANDLE hContact, const char *pszNewGroup) +{ + servlistcookie* ack; + + if (!GroupNameExistsUtf(pszNewGroup, -1) && (pszNewGroup != NULL) && (pszNewGroup[0]!='\0')) + { // the contact moved to non existing group, do not do anything: MetaContact hack + NetLog_Server("Contact not moved - probably hiding by MetaContacts."); + return 0; + } + + if (!ICQGetContactSettingWord(hContact, "ServerId", 0)) + { // the contact is not stored on the server, check if we should try to add + if (!ICQGetContactSettingByte(NULL, "ServerAddRemove", DEFAULT_SS_ADDSERVER) || + DBGetContactSettingByte(hContact, "CList", "Hidden", 0)) + return 0; + } + + if (!(ack = (servlistcookie*)SAFE_MALLOC(sizeof(servlistcookie)))) + { // Could not do anything without cookie + NetLog_Server("Failed to add contact to server side list (%s)", "malloc failed"); + return 0; + } + else + { + ack->hContact = hContact; + + if (AddPendingOperation(hContact, pszNewGroup, ack, moveServContactReady)) + makeGroupId(pszNewGroup, moveServContactReady, ack); + return 1; + } +} + + + +// Is called when a contact' details has been changed locally to update +// the server side details. +static DWORD updateServContact(HANDLE hContact) +{ + WORD wGroupID; + WORD wItemID; + DWORD dwUin; + uid_str szUid; + servlistcookie* ack; + DWORD dwCookie; + + // Get the contact's group ID + if (!(wGroupID = ICQGetContactSettingWord(hContact, "SrvGroupId", 0))) + { + // Could not find a usable group ID + NetLog_Server("Failed to update contact's details on server side list (%s)", "no group ID"); + RemovePendingOperation(hContact, 0); + return 0; + } + + // Get the contact's item ID + if (!(wItemID = ICQGetContactSettingWord(hContact, "ServerId", 0))) + { + // Could not find usable item ID + NetLog_Server("Failed to update contact's details on server side list (%s)", "no item ID"); + RemovePendingOperation(hContact, 0); + return 0; + } + + // Get UID + if (ICQGetContactSettingUID(hContact, &dwUin, &szUid)) + { + // Could not set nickname on server without uid + NetLog_Server("Failed to update contact's details on server side list (%s)", "no UID"); + RemovePendingOperation(hContact, 0); + return 0; + } + + if (!(ack = (servlistcookie*)SAFE_MALLOC(sizeof(servlistcookie)))) + { + // Could not allocate cookie - use old fake + NetLog_Server("Failed to allocate cookie"); + dwCookie = GenerateCookie(ICQ_LISTS_UPDATEGROUP); + } + else + { + ack->dwAction = SSA_CONTACT_UPDATE; + ack->wContactId = wItemID; + ack->wGroupId = wGroupID; + ack->dwUin = dwUin; + ack->hContact = hContact; + + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, dwUin, ack); + } + + // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or + // ICQ_LISTS_CLI_MODIFYEND when just changing nick name + icq_sendServerContact(hContact, dwCookie, ICQ_LISTS_UPDATEGROUP, wGroupID, wItemID); + + return dwCookie; +} + + + +void renameServGroup(WORD wGroupId, char* szGroupName) +{ + servlistcookie* ack; + DWORD dwCookie; + char* szGroup, *szLast; + int level = countGroupLevel(wGroupId); + int i; + void* groupData; + DWORD groupSize; + + if (IsGroupRenamed(wGroupId)) return; // the group was already renamed + + if (level == -1) return; // we failed to prepare group + + szLast = szGroupName; + i = level; + while (i) + { // find correct part of grouppath + szLast = strstr(szLast, "\\")+1; + i--; + } + szGroup = (char*)SAFE_MALLOC(strlennull(szLast)+1+level); + if (!szGroup) return; + + for (i=0;idwAction = SSA_GROUP_RENAME; + ack->wGroupId = wGroupId; + ack->szGroupName = szGroup; // we need this name + + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); + + AddGroupRename(wGroupId); + + icq_sendGroupUtf(dwCookie, ICQ_LISTS_UPDATEGROUP, wGroupId, szGroup, groupData, groupSize); + SAFE_FREE(&groupData); + } +} + + +/***************************************** + * + * --- Miranda Contactlist Hooks --- + * + */ + + + +static int ServListDbSettingChanged(WPARAM wParam, LPARAM lParam) +{ + DBCONTACTWRITESETTING* cws = (DBCONTACTWRITESETTING*)lParam; + + // We can't upload changes to NULL contact + if ((HANDLE)wParam == NULL) + return 0; + + if (!strcmpnull(cws->szModule, "ContactPhoto")) + { // contact photo changed ? + ContactPhotoSettingChanged((HANDLE)wParam); + } + + // TODO: Queue changes that occur while offline + if (!icqOnline || !gbSsiEnabled || bIsSyncingCL) + return 0; + + { // only our contacts will be handled + if (IsICQContact((HANDLE)wParam)) + ;// our contact, fine; otherwise return + else + return 0; + } + + if (!strcmpnull(cws->szModule, "CList")) + { + // Has a temporary contact just been added permanently? + if (!strcmpnull(cws->szSetting, "NotOnList") && + (cws->value.type == DBVT_DELETED || (cws->value.type == DBVT_BYTE && cws->value.bVal == 0)) && + ICQGetContactSettingByte(NULL, "ServerAddRemove", DEFAULT_SS_ADDSERVER) && + !DBGetContactSettingByte((HANDLE)wParam, "CList", "Hidden", 0)) + { + DWORD dwUin; + uid_str szUid; + + // Does this contact have a UID? + if (!ICQGetContactSettingUID((HANDLE)wParam, &dwUin, &szUid)) + { + char *pszNick; + char *pszGroup; +// sendVisContactServ(dwUin, 1); + // Read nick name from DB + pszNick = UniGetContactSettingUtf((HANDLE)wParam, "CList", "MyHandle", NULL); + + // Read group from DB + pszGroup = UniGetContactSettingUtf((HANDLE)wParam, "CList", "Group", NULL); + + addServContact((HANDLE)wParam, pszNick, pszGroup); + + SAFE_FREE(&pszNick); + SAFE_FREE(&pszGroup); + } + } + + // Has contact been renamed? + if (!strcmpnull(cws->szSetting, "MyHandle") && + ICQGetContactSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE)) + { + if (AddPendingOperation((HANDLE)wParam, NULL, (servlistcookie*)1, NULL)) + updateServContact((HANDLE)wParam); + } + + // Has contact been moved to another group? + if (!strcmpnull(cws->szSetting, "Group") && + ICQGetContactSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE)) + { // Test if group was not renamed... + WORD wGroupId = ICQGetContactSettingWord((HANDLE)wParam, "SrvGroupId", 0); + char* szGroup = makeGroupPathUtf(wGroupId); + char* szNewGroup; + int bRenamed = 0; + int bMoved = 1; + + // Read group from DB + szNewGroup = UniGetContactSettingUtf((HANDLE)wParam, "CList", "Group", NULL); + + if (szNewGroup && wGroupId && !GroupNameExistsUtf(szGroup, -1)) + { // if we moved from non-existing group, it can be rename + if (!getServerGroupIDUtf(szNewGroup)) + { // the target group is not known - it is probably rename + if (getServerGroupIDUtf(szGroup)) + { // source group not known -> already renamed + if (countClistGroupLevel(szNewGroup) == countGroupLevel(wGroupId)) + { // renamed groups can be only in the same level, if not it is move + if (!IsGroupRenamed(wGroupId)) + { // is rename in progress ? + bRenamed = 1; // TODO: we should really check if group was not moved to sub-group + NetLog_Server("Group %x renamed to ""%s"".", wGroupId, szNewGroup); + } + else // if rename in progress do not move contacts + bMoved = 0; + } + } + } + } + SAFE_FREE(&szGroup); + + if (bRenamed) + renameServGroup(wGroupId, szNewGroup); + else if (bMoved) // TODO: this is bad, we badly need rate management + moveServContactGroup((HANDLE)wParam, szNewGroup); + + SAFE_FREE(&szNewGroup); + } + } + + if (!strcmpnull(cws->szModule, "UserInfo")) + { + if (!strcmpnull(cws->szSetting, "MyNotes") && + ICQGetContactSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE)) + { + if (AddPendingOperation((HANDLE)wParam, NULL, (servlistcookie*)1, NULL)) + updateServContact((HANDLE)wParam); + } + } + + return 0; +} + + + +static int ServListDbContactDeleted(WPARAM wParam, LPARAM lParam) +{ + DeleteFromCache((HANDLE)wParam); + + if (!icqOnline || !gbSsiEnabled) + return 0; + + { // we need all server contacts on local buddy list + WORD wContactID; + WORD wGroupID; + WORD wVisibleID; + WORD wInvisibleID; + WORD wIgnoreID; + DWORD dwUIN; + uid_str szUID; +// sendVisContactServ(dwUIN, 0); + wContactID = ICQGetContactSettingWord((HANDLE)wParam, "ServerId", 0); + wGroupID = ICQGetContactSettingWord((HANDLE)wParam, "SrvGroupId", 0); + wVisibleID = ICQGetContactSettingWord((HANDLE)wParam, "SrvPermitId", 0); + wInvisibleID = ICQGetContactSettingWord((HANDLE)wParam, "SrvDenyId", 0); + wIgnoreID = ICQGetContactSettingWord((HANDLE)wParam, "SrvIgnoreId", 0); + if (ICQGetContactSettingUID((HANDLE)wParam, &dwUIN, &szUID)) + return 0; + + // Close all opened peer connections + CloseContactDirectConns((HANDLE)wParam); + + if ((wGroupID && wContactID) || wVisibleID || wInvisibleID || wIgnoreID) + { + if (wContactID) + { // delete contact from server + removeServContact((HANDLE)wParam); + } + + if (wVisibleID) + { // detete permit record + icq_removeServerPrivacyItem((HANDLE)wParam, dwUIN, szUID, wVisibleID, SSI_ITEM_PERMIT); + } + + if (wInvisibleID) + { // delete deny record + icq_removeServerPrivacyItem((HANDLE)wParam, dwUIN, szUID, wInvisibleID, SSI_ITEM_DENY); + } + + if (wIgnoreID) + { // delete ignore record + icq_removeServerPrivacyItem((HANDLE)wParam, dwUIN, szUID, wIgnoreID, SSI_ITEM_IGNORE); + } + } + } + + return 0; +} + + + +void InitServerLists(void) +{ + InitializeCriticalSection(&servlistMutex); + + hHookSettingChanged = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, ServListDbSettingChanged); + hHookContactDeleted = HookEvent(ME_DB_CONTACT_DELETED, ServListDbContactDeleted); +} + + + +void UninitServerLists(void) +{ + if (hHookSettingChanged) + UnhookEvent(hHookSettingChanged); + + if (hHookContactDeleted) + UnhookEvent(hHookContactDeleted); + + FlushServerIDs(); + FlushPendingOperations(); + + DeleteCriticalSection(&servlistMutex); +} -- cgit v1.2.3