diff --git a/lib/native/windows-64/jmsoutlookaddrbook.dll b/lib/native/windows-64/jmsoutlookaddrbook.dll index 6374fed7d..39c9f3635 100644 Binary files a/lib/native/windows-64/jmsoutlookaddrbook.dll and b/lib/native/windows-64/jmsoutlookaddrbook.dll differ diff --git a/lib/native/windows/jmsoutlookaddrbook.dll b/lib/native/windows/jmsoutlookaddrbook.dll index 1359440b5..045e61389 100644 Binary files a/lib/native/windows/jmsoutlookaddrbook.dll and b/lib/native/windows/jmsoutlookaddrbook.dll differ diff --git a/src/native/addrbook/msoutlook/lib/MAPINotification.cxx b/src/native/addrbook/msoutlook/lib/MAPINotification.cxx new file mode 100644 index 000000000..18d8de4e4 --- /dev/null +++ b/src/native/addrbook/msoutlook/lib/MAPINotification.cxx @@ -0,0 +1,173 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +#include "MAPINotification.h" + +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include + +#include +#include + +#include "../net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.h" +#include "../net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.h" +/** + * Manages notification for the message data base (used to get the list of + * contact). + * + * @author Vincent Lucas + */ + +/** + * The List of events we want to retrieve. + */ +static ULONG EVENT_MASK + = fnevObjectCreated + | fnevObjectDeleted + | fnevObjectModified + | fnevObjectMoved; + +/** + * Functions called when an event is fired from the message data base. + * + * @param lpvContext A pointer to the message data base. + * @param cNotifications The number of event in this call. + * @param lpNotifications The list of notifications. + */ +LONG STDAPICALLTYPE onNotify( + LPVOID lpvContext, + ULONG cNotifications, + LPNOTIFICATION lpNotifications) +{ + for(unsigned int i = 0; i < cNotifications; ++i) + { + LPUNKNOWN iUnknown = NULL; + if(lpvContext != NULL) + { + iUnknown = openEntry( + lpNotifications[i].info.obj.cbEntryID, + lpNotifications[i].info.obj.lpEntryID, + lpvContext); + } + + // A contact has been created + if(lpNotifications[i].ulEventType == fnevObjectCreated) + { + if(lpNotifications[i].info.obj.ulObjType == MAPI_MESSAGE) + { + callInsertedCallbackMethod(iUnknown); + } + } + // A contact has been Modified + else if(lpNotifications[i].ulEventType == fnevObjectModified) + { + if(lpNotifications[i].info.obj.ulObjType == MAPI_MESSAGE) + { + callUpdatedCallbackMethod(iUnknown); + } + } + // A contact has been deleted. + else if(lpNotifications[i].ulEventType == fnevObjectDeleted) + { + if(lpvContext != NULL) + { + char entryIdStr[lpNotifications[i].info.obj.cbEntryID * 2 + 1]; + + HexFromBin( + (LPBYTE) lpNotifications[i].info.obj.lpEntryID, + lpNotifications[i].info.obj.cbEntryID, + entryIdStr); + + if(lpNotifications[i].info.obj.ulObjType == MAPI_MESSAGE) + { + callDeletedCallbackMethod(entryIdStr); + } + } + } + // A contact has been deleted (moved to trash). + else if(lpNotifications[i].ulEventType == fnevObjectMoved) + { + if(lpvContext != NULL) + { + char entryIdStr[lpNotifications[i].info.obj.cbEntryID * 2 + 1]; + HexFromBin( + (LPBYTE) lpNotifications[i].info.obj.lpEntryID, + lpNotifications[i].info.obj.cbEntryID, + entryIdStr); + char parentEntryIdStr[ + lpNotifications[i].info.obj.cbParentID * 2 + 1]; + HexFromBin( + (LPBYTE) lpNotifications[i].info.obj.lpParentID, + lpNotifications[i].info.obj.cbParentID, + parentEntryIdStr); + ULONG wasteBasketTags[] = {1, PR_IPM_WASTEBASKET_ENTRYID}; + ULONG wasteBasketNbValues = 0; + LPSPropValue wasteBasketProps = NULL; + ((LPMDB)lpvContext)->GetProps( + (LPSPropTagArray) wasteBasketTags, + MAPI_UNICODE, + &wasteBasketNbValues, + &wasteBasketProps); + char wasteBasketEntryIdStr[ + wasteBasketProps[0].Value.bin.cb * 2 + 1]; + HexFromBin( + (LPBYTE) wasteBasketProps[0].Value.bin.lpb, + wasteBasketProps[0].Value.bin.cb, + wasteBasketEntryIdStr); + + openEntry( + lpNotifications[i].info.obj.cbParentID, + lpNotifications[i].info.obj.lpParentID, + lpvContext); + + + if(lpNotifications[i].info.obj.ulObjType == MAPI_MESSAGE + && strcmp(parentEntryIdStr, wasteBasketEntryIdStr) == 0) + { + callDeletedCallbackMethod(entryIdStr); + } + } + } + + if(iUnknown != NULL) + { + iUnknown->Release(); + } + } + + // A client must always return a S_OK. + return S_OK; +} + +/** + * Registers to notification for the given message data base. + * + * @param iUnknown The data base to register to in order to receive events. + * + * @return A unsigned long which is a token wich must be used to call the + * unadvise function for the same message data base. + */ +ULONG registerNotifyMessageDataBase( + LPMDB iUnknown) +{ + LPMAPIADVISESINK adviseSink; + HrAllocAdviseSink( + &onNotify, + iUnknown, + &adviseSink); + ULONG nbConnection = 0; + iUnknown->Advise( + 0, + NULL, + EVENT_MASK, + adviseSink, + &nbConnection); + + return nbConnection; +} diff --git a/src/native/addrbook/msoutlook/lib/MAPINotification.h b/src/native/addrbook/msoutlook/lib/MAPINotification.h new file mode 100644 index 000000000..2cf4eea95 --- /dev/null +++ b/src/native/addrbook/msoutlook/lib/MAPINotification.h @@ -0,0 +1,30 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +#ifndef _mapi_notification_h +#define _mapi_notification_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +LONG STDAPICALLTYPE onNotify( + LPVOID lpvContext, + ULONG cNotifications, + LPNOTIFICATION lpNotifications); + +ULONG registerNotifyMessageDataBase( + LPMDB iUnknown); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.cxx b/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.cxx index 861d485db..6b6adb298 100644 --- a/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.cxx +++ b/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.cxx @@ -7,15 +7,31 @@ #include "net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.h" -#include "AddrBookContactQuery.h" +#include "net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.h" + +#include "../AddrBookContactQuery.h" #include "MsOutlookMAPI.h" #include "MsOutlookMAPIHResultException.h" +#include "lib/MAPINotification.h" #include #include +#include #define PR_ATTACHMENT_CONTACTPHOTO PROP_TAG(PT_BOOLEAN, 0x7FFF) +static ULONG openEntryUlFlags = MAPI_BEST_ACCESS; + +LPUNKNOWN openEntryId(const char* entryId); + +ULONG registerNotifyTable( + LPMAPITABLE iUnknown); + +LONG STDAPICALLTYPE tableChanged( + LPVOID lpvContext, + ULONG cNotifications, + LPNOTIFICATION lpNotifications); + typedef jboolean (*MsOutlookAddrBookContactQuery_ForeachRowInTableCallback) (LPUNKNOWN iUnknown, @@ -24,7 +40,7 @@ typedef jstring query, jobject callback, jmethodID callbackMethodID); -static HRESULT HrGetOneProp +static HRESULT MsOutlookAddrBookContactQuery_HrGetOneProp (LPMAPIPROP mapiProp, ULONG propTag, LPSPropValue *prop); static jboolean MsOutlookAddrBookContactQuery_foreachContactInMsgStoresTable @@ -76,26 +92,23 @@ static jbyteArray MsOutlookAddrBookContactQuery_readAttachment (LPMESSAGE message, LONG method, ULONG num, JNIEnv *jniEnv, ULONG cond); JNIEXPORT void JNICALL -Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery_foreachMailUser - (JNIEnv *jniEnv, jclass clazz, jstring query, jobject callback) +Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery_foreachMailUser( + JNIEnv *jniEnv, + jclass clazz, + jstring query, + jobject callback) { jmethodID callbackMethodID; - HRESULT hResult; - LPMAPISESSION mapiSession = NULL; + LPMAPISESSION mapiSession + = MsOutlookAddrBookContactSourceService_getMapiSession(); callbackMethodID = AddrBookContactQuery_getPtrCallbackMethodID(jniEnv, callback); if (!callbackMethodID || jniEnv->ExceptionCheck()) return; - hResult - = MAPILogonEx( - 0, - NULL, NULL, - MAPI_EXTENDED | MAPI_NO_MAIL | MAPI_USE_DEFAULT, - &mapiSession); - if (HR_SUCCEEDED(hResult) && mapiSession) + if (mapiSession) { jboolean proceed = MsOutlookAddrBookContactQuery_foreachContactInMsgStoresTable( @@ -113,28 +126,16 @@ Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContac callback, callbackMethodID); } - /* - * XXX MAPILogonEx has been redefined to return a shared mapiSession - * which is logged off and released upon uninitializing MAPI. The reason - * for the redefinition is that logging on, off and releasing multiple - * times leads to a crash eventually. - */ -// mapiSession->Logoff(0, 0, 0); -// mapiSession->Release(); - } - else - { - MsOutlookMAPIHResultException_throwNew( - jniEnv, - hResult, - __FILE__, __LINE__); } } JNIEXPORT jobjectArray JNICALL -Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery_IMAPIProp_1GetProps - (JNIEnv *jniEnv, jclass clazz, - jlong mapiProp, jlongArray propIds, jlong flags) +Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery_IMAPIProp_1GetProps( + JNIEnv *jniEnv, + jclass clazz, + jlong mapiProp, + jlongArray propIds, + jlong flags) { jsize propIdCount; LPSPropTagArray propTagArray; @@ -309,6 +310,28 @@ Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContac } break; } + + case PT_BINARY: + { + char entryIdStr[prop->Value.bin.cb * 2 + 1]; + + HexFromBin( + prop->Value.bin.lpb, + prop->Value.bin.cb, + entryIdStr); + + jstring value; + value = jniEnv->NewStringUTF(entryIdStr); + if(value) + { + jniEnv->SetObjectArrayElement( + props, + j, value); + if (jniEnv->ExceptionCheck()) + props = NULL; + } + break; + } } } propArray++; @@ -336,7 +359,10 @@ Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContac } static HRESULT -HrGetOneProp(LPMAPIPROP mapiProp, ULONG propTag, LPSPropValue *prop) +MsOutlookAddrBookContactQuery_HrGetOneProp( + LPMAPIPROP mapiProp, + ULONG propTag, + LPSPropValue *prop) { SPropTagArray propTagArray; HRESULT hResult; @@ -490,7 +516,14 @@ MsOutlookAddrBookContactQuery_foreachMailUserInAddressBook ULONG objType; LPUNKNOWN iUnknown; - hResult = adrBook->OpenEntry(0, NULL, NULL, 0, &objType, &iUnknown); + hResult = adrBook->OpenEntry( + 0, + NULL, + NULL, + openEntryUlFlags, + &objType, + &iUnknown); + if (HR_SUCCEEDED(hResult)) { proceed @@ -754,18 +787,20 @@ MsOutlookAddrBookContactQuery_getContactsFolderEntryID ULONG objType; LPUNKNOWN folder; - hResult - = msgStore->OpenEntry( - folderEntryIDByteCount, folderEntryID, - NULL, - 0, - &objType, &folder); + hResult = msgStore->OpenEntry( + folderEntryIDByteCount, + folderEntryID, + NULL, + openEntryUlFlags, + &objType, + &folder); + if (HR_SUCCEEDED(hResult)) { LPSPropValue prop; hResult - = HrGetOneProp( + = MsOutlookAddrBookContactQuery_HrGetOneProp( (LPMAPIPROP) folder, 0x36D10102 /* PR_IPM_CONTACT_ENTRYID */, &prop); @@ -845,7 +880,7 @@ MsOutlookAddrBookContactQuery_onForeachContactInMsgStoresTableRow 0, entryIDByteCount, entryID, NULL, - MDB_NO_MAIL, + MDB_NO_MAIL | openEntryUlFlags, &msgStore); if (HR_SUCCEEDED(hResult)) { @@ -883,10 +918,10 @@ MsOutlookAddrBookContactQuery_onForeachContactInMsgStoresTableRow hResult = msgStore->OpenEntry( - contactsFolderEntryIDByteCount, contactsFolderEntryID, - NULL, - 0, - &contactsFolderObjType, &contactsFolder); + contactsFolderEntryIDByteCount, contactsFolderEntryID, + NULL, + openEntryUlFlags, + &contactsFolderObjType, &contactsFolder); if (HR_SUCCEEDED(hResult)) { proceed @@ -932,11 +967,12 @@ MsOutlookAddrBookContactQuery_onForeachMailUserInContainerTableRow LPUNKNOWN iUnknown; jboolean proceed; + // Make write failed and image load. hResult = ((LPMAPICONTAINER) mapiContainer)->OpenEntry( entryIDByteCount, entryID, NULL, - 0, + openEntryUlFlags, &objType, &iUnknown); if (HR_SUCCEEDED(hResult)) { @@ -974,7 +1010,10 @@ MsOutlookAddrBookContactQuery_readAttachment { LPSPropValue condValue; - hResult = HrGetOneProp((LPMAPIPROP) attach, cond, &condValue); + hResult = MsOutlookAddrBookContactQuery_HrGetOneProp( + (LPMAPIPROP) attach, + cond, + &condValue); if (HR_SUCCEEDED(hResult)) { if ((PT_BOOLEAN != PROP_TYPE(condValue->ulPropTag)) @@ -1035,3 +1074,489 @@ MsOutlookAddrBookContactQuery_readAttachment } return attachment; } + +/** + * Saves one contact property. + */ +JNIEXPORT jboolean JNICALL +Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery_IMAPIProp_1SetPropString + (JNIEnv *jniEnv, jclass clazz, jlong propId, jstring value, + jstring entryId) +{ + HRESULT hResult; + + const char *nativeEntryId = jniEnv->GetStringUTFChars(entryId, NULL); + LPUNKNOWN mapiProp; + if((mapiProp = openEntryId(nativeEntryId)) == NULL) + { + return JNI_FALSE; + } + jniEnv->ReleaseStringUTFChars(entryId, nativeEntryId); + + const char *nativeValue = jniEnv->GetStringUTFChars(value, NULL); + size_t valueLength = strlen(nativeValue); + wchar_t wCharValue[valueLength + 1]; + if(mbstowcs(wCharValue, nativeValue, valueLength + 1) + != valueLength) + { + fprintf(stderr, + "setPropUnicode (addrbook/MsOutlookAddrBookContactQuery.c): \ + \n\tmbstowcs\n"); + fflush(stderr); + jniEnv->ReleaseStringUTFChars(value, nativeValue); + return JNI_FALSE; + } + jniEnv->ReleaseStringUTFChars(value, nativeValue); + + ULONG baseGroupEntryIdProp = 0; + switch(propId) + { + case 0x00008083: // dispidEmail1EmailAddress + baseGroupEntryIdProp = 0x00008080; + break; + case 0x00008093: // dispidEmail2EmailAddress + baseGroupEntryIdProp = 0x00008090; + break; + case 0x000080A3: // dispidEmail3EmailAddress + baseGroupEntryIdProp = 0x000080A0; + break; + } + // If this is a special entry (for email only), then updates all the + // corresponding properties to make it work. + if(baseGroupEntryIdProp != 0) + { + ULONG nbProps = 7; + ULONG propIds[] = + { + 0x8028, // PidLidAddressBookProviderEmailList + 0x8029, // PidLidAddressBookProviderArrayType + (baseGroupEntryIdProp + 0), //0x8080 PidLidEmail1DisplayName + (baseGroupEntryIdProp + 2), // 0x8082 PidLidEmail1AddressType + (baseGroupEntryIdProp + 3), // 0x8083 PidLidEmail1EmailAddress + (baseGroupEntryIdProp + 4), // 0x8084 PidLidEmail1OriginalDisplayName + (baseGroupEntryIdProp + 5) // 0x8085 PidLidEmail1OriginalEntryID + }; + ULONG propTag; + ULONG propCount; + LPSPropValue propArray; + LPSPropTagArray propTagArray; + MAPIAllocateBuffer( + CbNewSPropTagArray(nbProps), + (void **) &propTagArray); + propTagArray->cValues = nbProps; + for(unsigned int i = 0; i < nbProps; ++i) + { + propTag = MsOutlookAddrBookContactQuery_getPropTagFromLid( + (LPMAPIPROP) mapiProp, + propIds[i]); + *(propTagArray->aulPropTag + i) = propTag; + } + hResult = ((LPMAPIPROP) mapiProp)->GetProps( + propTagArray, + MAPI_UNICODE, + &propCount, + &propArray); + + if(SUCCEEDED(hResult)) + { + propArray[2].Value.lpszW = wCharValue; + propArray[4].Value.lpszW = wCharValue; + propArray[5].Value.lpszW = wCharValue; + + if(SUCCEEDED(hResult)) + { + hResult = ((LPMAPIPROP) mapiProp)->SetProps( + nbProps, + propArray, + NULL); + if(SUCCEEDED(hResult)) + { + hResult = ((LPMAPIPROP) mapiProp)->SaveChanges( + FORCE_SAVE | KEEP_OPEN_READWRITE); + if (HR_SUCCEEDED(hResult)) + { + MAPIFreeBuffer(propTagArray); + ((LPMAPIPROP) mapiProp)->Release(); + return JNI_TRUE; + } + } + } + } + MAPIFreeBuffer(propTagArray); + ((LPMAPIPROP) mapiProp)->Release(); + return JNI_FALSE; + } + + SPropValue updateValue; + updateValue.ulPropTag = PROP_TAG(PT_UNICODE, propId); + updateValue.Value.lpszW = wCharValue; + + hResult = ((LPMAPIPROP) mapiProp)->SetProps( + 1, + (LPSPropValue) &updateValue, + NULL); + + if (HR_SUCCEEDED(hResult)) + { + HRESULT hResult + = ((LPMAPIPROP) mapiProp)->SaveChanges( + FORCE_SAVE | KEEP_OPEN_READWRITE); + + if (HR_SUCCEEDED(hResult)) + { + ((LPMAPIPROP) mapiProp)->Release(); + return JNI_TRUE; + } + } + + ((LPMAPIPROP) mapiProp)->Release(); + return JNI_FALSE; +} + +/** + * Deletes one property from a contact. + */ +JNIEXPORT jboolean JNICALL +Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery_IMAPIProp_1DeleteProp + (JNIEnv *jniEnv, jclass clazz, jlong propId, jstring entryId) +{ + const char *nativeEntryId = jniEnv->GetStringUTFChars(entryId, NULL); + LPUNKNOWN mapiProp; + if((mapiProp = openEntryId(nativeEntryId)) == NULL) + { + return JNI_FALSE; + } + jniEnv->ReleaseStringUTFChars(entryId, nativeEntryId); + + ULONG baseGroupEntryIdProp = 0; + switch(propId) + { + case 0x00008083: // dispidEmail1EmailAddress + baseGroupEntryIdProp = 0x00008080; + break; + case 0x00008093: // dispidEmail2EmailAddress + baseGroupEntryIdProp = 0x00008090; + break; + case 0x000080A3: // dispidEmail3EmailAddress + baseGroupEntryIdProp = 0x000080A0; + break; + } + // If this is a special entry (for email only), then deletes all the + // corresponding properties to make it work. + if(baseGroupEntryIdProp != 0) + { + ULONG nbProps = 5; + ULONG propIds[] = + { + (baseGroupEntryIdProp + 0), //0x8080 PidLidEmail1DisplayName + (baseGroupEntryIdProp + 2), // 0x8082 PidLidEmail1AddressType + (baseGroupEntryIdProp + 3), // 0x8083 PidLidEmail1EmailAddress + (baseGroupEntryIdProp + 4), // 0x8084 PidLidEmail1OriginalDisplayName + (baseGroupEntryIdProp + 5) // 0x8085 PidLidEmail1OriginalEntryID + }; + ULONG propTag; + ULONG propCount; + LPSPropValue propArray; + LPSPropTagArray propTagArray; + MAPIAllocateBuffer( + CbNewSPropTagArray(nbProps), + (void **) &propTagArray); + propTagArray->cValues = nbProps; + for(unsigned int i = 0; i < nbProps; ++i) + { + propTag = MsOutlookAddrBookContactQuery_getPropTagFromLid( + (LPMAPIPROP) mapiProp, + propIds[i]); + *(propTagArray->aulPropTag + i) = propTag; + } + + HRESULT hResult + = ((LPMAPIPROP) mapiProp)->DeleteProps( + propTagArray, + NULL); + + if (HR_SUCCEEDED(hResult)) + { + hResult + = ((LPMAPIPROP) mapiProp)->SaveChanges( + FORCE_SAVE | KEEP_OPEN_READWRITE); + + if (HR_SUCCEEDED(hResult)) + { + MAPIFreeBuffer(propTagArray); + ((LPMAPIPROP) mapiProp)->Release(); + return JNI_TRUE; + } + } + MAPIFreeBuffer(propTagArray); + ((LPMAPIPROP) mapiProp)->Release(); + return JNI_FALSE; + } + + SPropTagArray propToDelete; + propToDelete.cValues = 1; + propToDelete.aulPropTag[0] = PROP_TAG(PT_UNICODE, propId); + + HRESULT hResult + = ((LPMAPIPROP) mapiProp)->DeleteProps( + (LPSPropTagArray) &propToDelete, + NULL); + + if (HR_SUCCEEDED(hResult)) + { + hResult + = ((LPMAPIPROP) mapiProp)->SaveChanges( + FORCE_SAVE | KEEP_OPEN_READWRITE); + + if (HR_SUCCEEDED(hResult)) + { + ((LPMAPIPROP) mapiProp)->Release(); + return JNI_TRUE; + } + } + ((LPMAPIPROP) mapiProp)->Release(); + return JNI_FALSE; +} + +/** + * Opens an object from its entry id. + */ +LPUNKNOWN openEntry( + ULONG cbEntryID, + LPENTRYID lpEntryID, + LPVOID lpvContext) +{ + if(lpvContext != NULL) + { + LPUNKNOWN iUnknown; + ULONG objType; + + HRESULT hResult = + ((LPMDB) lpvContext)->OpenEntry( + cbEntryID, + lpEntryID, + NULL, + openEntryUlFlags, + &objType, + &iUnknown); + if (HR_SUCCEEDED(hResult)) + { + return iUnknown; + } + } + return NULL; +} + +static LPMAPITABLE MsOutlookAddrBookContactQuery_msgStoresTable = NULL; +static ULONG MsOutlookAddrBookContactQuery_msgStoresTableConnection = 0; +static ULONG MsOutlookAddrBookContactQuery_nbMsgStores = 0; +static LPMDB * MsOutlookAddrBookContactQuery_msgStores = NULL; +static ULONG * MsOutlookAddrBookContactQuery_msgStoresConnection = NULL; + +/** + * Opens all the message store and register to notifications. + */ +void openAllMsgStores( + LPMAPISESSION mapiSession) +{ + HRESULT hResult; + + hResult = mapiSession->GetMsgStoresTable( + 0, + &MsOutlookAddrBookContactQuery_msgStoresTable); + if(HR_SUCCEEDED(hResult) && MsOutlookAddrBookContactQuery_msgStoresTable) + { + MsOutlookAddrBookContactQuery_msgStoresTableConnection + = registerNotifyTable(MsOutlookAddrBookContactQuery_msgStoresTable); + hResult = MsOutlookAddrBookContactQuery_msgStoresTable->SeekRow( + BOOKMARK_BEGINNING, + 0, + NULL); + if (HR_SUCCEEDED(hResult)) + { + LPSRowSet rows; + hResult = HrQueryAllRows( + MsOutlookAddrBookContactQuery_msgStoresTable, + NULL, + NULL, + NULL, + 0, + &rows); + if (HR_SUCCEEDED(hResult)) + { + MsOutlookAddrBookContactQuery_nbMsgStores = rows->cRows; + MsOutlookAddrBookContactQuery_msgStores + = (LPMDB*) malloc(rows->cRows * sizeof(LPMDB)); + memset( + MsOutlookAddrBookContactQuery_msgStores, + 0, + rows->cRows * sizeof(LPMDB)); + MsOutlookAddrBookContactQuery_msgStoresConnection + = (ULONG*) malloc(rows->cRows * sizeof(ULONG)); + memset( + MsOutlookAddrBookContactQuery_msgStoresConnection, + 0, + rows->cRows * sizeof(ULONG)); + + if(MsOutlookAddrBookContactQuery_msgStores != NULL + && MsOutlookAddrBookContactQuery_msgStoresConnection + != NULL) + { + for(unsigned int r = 0; r < rows->cRows; ++r) + { + SRow row = rows->aRow[r]; + ULONG i; + ULONG objType = 0; + SBinary entryIDBinary = { 0, NULL }; + + for(i = 0; i < row.cValues; ++i) + { + LPSPropValue prop = (row.lpProps) + i; + + switch (prop->ulPropTag) + { + case PR_OBJECT_TYPE: + objType = prop->Value.ul; + break; + case PR_ENTRYID: + entryIDBinary = prop->Value.bin; + break; + } + } + + if(objType && entryIDBinary.cb && entryIDBinary.lpb) + { + hResult = mapiSession->OpenMsgStore( + 0, + entryIDBinary.cb, + (LPENTRYID) entryIDBinary.lpb, + NULL, + MDB_NO_MAIL | openEntryUlFlags, + &MsOutlookAddrBookContactQuery_msgStores[r] + ); + if (HR_SUCCEEDED(hResult)) + { + MsOutlookAddrBookContactQuery_msgStoresConnection[r] + = registerNotifyMessageDataBase( + MsOutlookAddrBookContactQuery_msgStores[r]); + } + } + } + } + FreeProws(rows); + } + } + } +} + +/** + * Frees all memory used to keep in mind the list of the message store and + * unregister each of them from the notifications. + */ +void freeAllMsgStores(void) +{ + if(MsOutlookAddrBookContactQuery_msgStoresConnection != NULL) + { + for(unsigned int i = 0; + i < MsOutlookAddrBookContactQuery_nbMsgStores; + ++i) + { + if(MsOutlookAddrBookContactQuery_msgStoresConnection[i] != 0) + { + MsOutlookAddrBookContactQuery_msgStores[i]->Unadvise( + MsOutlookAddrBookContactQuery_msgStoresConnection[i]); + } + } + free(MsOutlookAddrBookContactQuery_msgStoresConnection); + } + if(MsOutlookAddrBookContactQuery_msgStores != NULL) + { + for(unsigned int i = 0; + i < MsOutlookAddrBookContactQuery_nbMsgStores; + ++i) + { + if(MsOutlookAddrBookContactQuery_msgStores[i] != NULL) + { + MsOutlookAddrBookContactQuery_msgStores[i]->Release(); + } + } + free(MsOutlookAddrBookContactQuery_msgStores); + } + if(MsOutlookAddrBookContactQuery_msgStoresTable != NULL) + { + MsOutlookAddrBookContactQuery_msgStoresTable->Unadvise( + MsOutlookAddrBookContactQuery_msgStoresTableConnection); + MsOutlookAddrBookContactQuery_msgStoresTable->Release(); + } +} + +/** + * Opens an object based on the string representation of its entry id. + */ +LPUNKNOWN openEntryId(const char* entryId) +{ + ULONG tmpEntryIdSize = strlen(entryId) / 2; + LPENTRYID tmpEntryId = (LPENTRYID) malloc(tmpEntryIdSize * sizeof(char)); + if(FBinFromHex((LPSTR) entryId, (LPBYTE) tmpEntryId)) + { + LPMAPISESSION mapiSession + = MsOutlookAddrBookContactSourceService_getMapiSession(); + ULONG objType; + LPUNKNOWN iUnknown; + HRESULT hResult = mapiSession->OpenEntry( + tmpEntryIdSize, + tmpEntryId, + NULL, + MAPI_BEST_ACCESS, + &objType, + &iUnknown); + if(hResult == S_OK) + { + free(tmpEntryId); + return iUnknown; + } + } + free(tmpEntryId); + return NULL; +} + +/** + * Registers a callback function for when the message store table changes. + */ +ULONG registerNotifyTable( + LPMAPITABLE iUnknown) +{ + LPMAPIADVISESINK adviseSink; + HrAllocAdviseSink( + &tableChanged, + iUnknown, + &adviseSink); + ULONG nbConnection = 0; + iUnknown->Advise( + fnevTableModified, + adviseSink, + &nbConnection); + + return nbConnection; +} + +/** + * Function called when a message store table changed. + */ +LONG STDAPICALLTYPE tableChanged( + LPVOID lpvContext, + ULONG cNotifications, + LPNOTIFICATION lpNotifications) +{ + if(lpNotifications->ulEventType == fnevTableModified + && (lpNotifications->info.tab.ulTableEvent == TABLE_CHANGED + || lpNotifications->info.tab.ulTableEvent == TABLE_ERROR + || lpNotifications->info.tab.ulTableEvent == TABLE_RELOAD + || lpNotifications->info.tab.ulTableEvent == TABLE_ROW_ADDED + || lpNotifications->info.tab.ulTableEvent == TABLE_ROW_DELETED)) + { + // Frees and recreates all the notification for the table. + freeAllMsgStores(); + openAllMsgStores(MsOutlookAddrBookContactSourceService_getMapiSession()); + } +} diff --git a/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.h b/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.h index 0092adfcd..06991203d 100644 --- a/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.h +++ b/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.h @@ -7,6 +7,11 @@ #ifdef __cplusplus extern "C" { #endif + +#include +#include +#include + /* * Class: net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery * Method: foreachMailUser @@ -23,6 +28,22 @@ JNIEXPORT void JNICALL Java_net_java_sip_communicator_plugin_addrbook_msoutlook_ JNIEXPORT jobjectArray JNICALL Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery_IMAPIProp_1GetProps (JNIEnv *, jclass, jlong, jlongArray, jlong); +JNIEXPORT jboolean JNICALL Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery_IMAPIProp_1SetPropString + (JNIEnv *, jclass, jlong, jstring, jstring); + +JNIEXPORT jboolean JNICALL Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery_IMAPIProp_1DeleteProp + (JNIEnv *, jclass, jlong, jstring); + +LPUNKNOWN openEntry( + ULONG cbEntryID, + LPENTRYID lpEntryID, + LPVOID lpvContext); + +void openAllMsgStores( + LPMAPISESSION mapiSession); + +void freeAllMsgStores(void); + #ifdef __cplusplus } #endif diff --git a/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.cxx b/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.cxx index 3e7aa2c38..99d1d5271 100644 --- a/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.cxx +++ b/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.cxx @@ -7,13 +7,141 @@ #include "net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.h" +#include "net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactQuery.h" + +#include "../AddrBookContactQuery.h" #include "MsOutlookMAPI.h" #include "MsOutlookMAPIHResultException.h" +#include "lib/MAPINotification.h" + +#include #include #include #include +static jobject contactSourceServiceObject; +static jmethodID contactSourceServiceMethodIdInserted; +static jmethodID contactSourceServiceMethodIdUpdated; +static jmethodID contactSourceServiceMethodIdDeleted; +static JNIEnv *contactSourceServiceJniEnv; +static JavaVM *contactSourceServiceVM; + +/** + * Registers java callback functions when a contact is deleted, inserted or + * updated. + */ +JNIEXPORT void JNICALL +Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService_setDelegate + (JNIEnv *jniEnv, jclass clazz, jobject callback) +{ + if(jniEnv->GetJavaVM(&contactSourceServiceVM) < 0) + { + fprintf(stderr, "Failed to get the Java VM\n"); + fflush(stderr); + } + contactSourceServiceJniEnv = jniEnv; + contactSourceServiceObject = jniEnv->NewGlobalRef(callback); + jclass callbackClass = jniEnv->GetObjectClass( + callback); + contactSourceServiceMethodIdInserted = jniEnv->GetMethodID( + callbackClass, + "inserted", + "(J)V"); + contactSourceServiceMethodIdUpdated = jniEnv->GetMethodID( + callbackClass, + "updated", + "(J)V"); + contactSourceServiceMethodIdDeleted = jniEnv->GetMethodID( + callbackClass, + "deleted", + "(Ljava/lang/String;)V"); +} + +/** + * Calls back the java side when a contact is inserted. + * + * @param iUnknown A pointer to the newly created contact. + */ +void callInsertedCallbackMethod( + LPUNKNOWN iUnknown) +{ + JNIEnv *tmpJniEnv = contactSourceServiceJniEnv; + + if(contactSourceServiceVM->GetEnv( + (void**) &tmpJniEnv, + JNI_VERSION_1_6) + != JNI_OK) + { + contactSourceServiceVM->AttachCurrentThread((void**) &tmpJniEnv, NULL); + } + + tmpJniEnv->CallBooleanMethod( + contactSourceServiceObject, + contactSourceServiceMethodIdInserted, + iUnknown); + + contactSourceServiceVM->DetachCurrentThread(); +} + +/** + * Calls back the java side when a contact is updated. + * + * @param iUnknown A pointer to the updated contact. + */ +void callUpdatedCallbackMethod( + LPUNKNOWN iUnknown) +{ + JNIEnv *tmpJniEnv = contactSourceServiceJniEnv; + + if(contactSourceServiceVM->GetEnv( + (void**) &tmpJniEnv, + JNI_VERSION_1_6) + != JNI_OK) + { + contactSourceServiceVM->AttachCurrentThread((void**) &tmpJniEnv, NULL); + } + + tmpJniEnv->CallBooleanMethod( + contactSourceServiceObject, + contactSourceServiceMethodIdUpdated, + iUnknown); + + contactSourceServiceVM->DetachCurrentThread(); +} + +/** + * Calls back the java side when a contact is deleted. + * + * @param iUnknown The string representation of the entry id of the deleted + * contact. + */ +void callDeletedCallbackMethod( + LPSTR iUnknown) +{ + JNIEnv *tmpJniEnv = contactSourceServiceJniEnv; + + if(contactSourceServiceVM->GetEnv( + (void**) &tmpJniEnv, + JNI_VERSION_1_6) + != JNI_OK) + { + contactSourceServiceVM->AttachCurrentThread((void**) &tmpJniEnv, NULL); + } + + jstring value; + value = tmpJniEnv->NewStringUTF(iUnknown); + + + tmpJniEnv->CallBooleanMethod( + contactSourceServiceObject, + contactSourceServiceMethodIdDeleted, + value); + + contactSourceServiceVM->DetachCurrentThread(); +} + + static LPMAPIALLOCATEBUFFER MsOutlookAddrBookContactSourceService_mapiAllocateBuffer; static LPMAPIFREEBUFFER MsOutlookAddrBookContactSourceService_mapiFreeBuffer; @@ -317,6 +445,18 @@ Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContac } } + if (HR_SUCCEEDED(hResult) + && MsOutlookAddrBookContactSourceService_mapiSession == NULL) + { + hResult = MAPILogonEx( + 0, + NULL, NULL, + MAPI_EXTENDED | MAPI_NO_MAIL | MAPI_USE_DEFAULT, + &MsOutlookAddrBookContactSourceService_mapiSession); + openAllMsgStores( + MsOutlookAddrBookContactSourceService_mapiSession); + } + /* Report any possible error regardless of where it has come from. */ if (HR_FAILED(hResult)) { @@ -335,6 +475,7 @@ Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContac &MsOutlookAddrBookContactSourceService_mapiSessionCriticalSection); if (MsOutlookAddrBookContactSourceService_mapiSession) { + freeAllMsgStores(); MsOutlookAddrBookContactSourceService_mapiSession->Logoff(0, 0, 0); MsOutlookAddrBookContactSourceService_mapiSession->Release(); MsOutlookAddrBookContactSourceService_mapiSession = NULL; @@ -428,3 +569,8 @@ MsOutlookAddrBookContactSourceService_isValidDefaultMailClient } return validDefaultMailClient; } + +LPMAPISESSION MsOutlookAddrBookContactSourceService_getMapiSession() +{ + return MsOutlookAddrBookContactSourceService_mapiSession; +} diff --git a/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.h b/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.h index 6b03cbb41..088c13dcd 100644 --- a/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.h +++ b/src/native/addrbook/msoutlook/net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService.h @@ -7,6 +7,11 @@ #ifdef __cplusplus extern "C" { #endif + +#include +#include +#include + /* * Class: net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService * Method: MAPIInitialize @@ -23,6 +28,21 @@ JNIEXPORT void JNICALL Java_net_java_sip_communicator_plugin_addrbook_msoutlook_ JNIEXPORT void JNICALL Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService_MAPIUninitialize (JNIEnv *, jclass); +JNIEXPORT void JNICALL +Java_net_java_sip_communicator_plugin_addrbook_msoutlook_MsOutlookAddrBookContactSourceService_setDelegate + (JNIEnv *jniEnv, jclass clazz, jobject callback); + +void callInsertedCallbackMethod( + LPUNKNOWN iUnknown); + +void callUpdatedCallbackMethod( + LPUNKNOWN iUnknown); + +void callDeletedCallbackMethod( + LPSTR iUnknown); + +LPMAPISESSION MsOutlookAddrBookContactSourceService_getMapiSession(); + #ifdef __cplusplus } #endif diff --git a/src/native/build.xml b/src/native/build.xml index 3876ca6a1..5e70d7748 100644 --- a/src/native/build.xml +++ b/src/native/build.xml @@ -832,5 +832,43 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactDetail.java b/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactDetail.java new file mode 100644 index 000000000..0877313c8 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactDetail.java @@ -0,0 +1,111 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.addrbook.msoutlook; + +import net.java.sip.communicator.service.contactsource.*; + +import java.util.*; + +/** + * Implements a custom ContactDetail for the Address Book of Microsoft + * Outlook. + * + * @author Vincent Lucas + */ +public class MsOutlookAddrBookContactDetail + extends EditableContactDetail +{ + /** + * The source contact which contains this contact detail. + */ + private MsOutlookAddrBookSourceContact sourceContact; + + /** + * The list of codes used by outlook to identify the property corresponding + * to this contact detail. + */ + private Vector outlookPropId; + + /** + * Initializes a new ContactDetail instance which is to represent a + * specific contact address and which is to be optionally labeled with a + * specific set of labels. + * + * @param contactDetailValue the contact detail value to be represented by + * the new ContactDetail instance + * @param category The category of this contact detail. + * @param subCategories the set of sub categories with which the new + * ContactDetail instance is to be labeled. + * @param outlookPropId The identifier of the outlook property used to + * get/set this contact detail. + */ + public MsOutlookAddrBookContactDetail( + String contactDetailValue, + Category category, + SubCategory[] subCategories, + long outlookPropId) + { + super(contactDetailValue, category, subCategories); + + this.outlookPropId = new Vector(1, 1); + this.outlookPropId.add(new Long(outlookPropId)); + this.sourceContact = null; + } + + /** + * Sets the source contact that contains this contact detail. + * + * @param sourceContact The source contact that contains this contact + * detail. + */ + public void setSourceContact(MsOutlookAddrBookSourceContact sourceContact) + { + this.sourceContact = sourceContact; + } + + /** + * If the given contact detail is similar to the current one (same category + * and same detail value), then return true. False otherwise. + * + * @param contactDetail The contact detail to compare with. + * + * @return True, if the given contact detail is similar to the current one + * (same category and same detail value). False otherwise. + */ + public boolean match(ContactDetail contactDetail) + { + return (this.getCategory() == contactDetail.getCategory() + && this.getDetail().equals(contactDetail.getDetail())); + } + + /** + * Returns the list of outlook properties corresponding to this contact + * detail. + * + * @return The list of outlook properties corresponding to this contact + * detail. + */ + public Vector getOutlookPropId() + { + return this.outlookPropId; + } + + /** + * Sets the given detail value. + * + * @param value the new value of the detail + */ + public void setDetail(String value) + { + super.setDetail(value); + + if(this.sourceContact != null) + { + this.sourceContact.save(); + } + } +} diff --git a/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactQuery.java b/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactQuery.java index e79a5796b..c2fa88ccb 100644 --- a/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactQuery.java +++ b/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactQuery.java @@ -18,9 +18,10 @@ * Implements ContactQuery for the Address Book of Microsoft Outlook. * * @author Lyubomir Marinov + * @author Vincent Lucas */ public class MsOutlookAddrBookContactQuery - extends AsyncContactQuery + extends AbstractAddrBookContactQuery { /** * The Logger used by the MsOutlookAddrBookContactQuery @@ -62,7 +63,19 @@ public class MsOutlookAddrBookContactQuery 0x00008083 /* dispidEmail1EmailAddress */, 0x00008093 /* dispidEmail2EmailAddress */, 0x000080A3 /* dispidEmail3EmailAddress */, - 0x3A16 /* PR_COMPANY_NAME */ + 0x3A16 /* PR_COMPANY_NAME */, + 0x0FFF /* PR_ORIGINAL_ENTRYID */, + 0x3A24 /* dispidFax1EmailAddress */, + 0x3A25 /* dispidFax2EmailAddress */, + 0x3A23 /* dispidFax3EmailAddress */, + 0x3A4F /* PR_NICKNAME */, + 0x3A45 /* PR_DISPLAY_NAME_PREFIX */, + 0x3A50 /* PR_PERSONAL_HOME_PAGE */, + 0x3A51 /* PR_BUSINESS_HOME_PAGE */ + //0x00008062 /* dispidInstMsg */, // EDITION NOT WORKING + //0x0000801A /* dispidHomeAddress */, // EDITION NOT WORKING + //0x0000801B /* dispidWorkAddress */, // EDITION NOT WORKING + //0x0000801C /* dispidOtherAddress */ // EDITION ONT WORKING }; /** @@ -150,6 +163,67 @@ public class MsOutlookAddrBookContactQuery */ private static final int PR_SURNAME = 4; + /** + * The index of the id of the PR_ORIGINAL_ENTRYID property + * in {@link #MAPI_MAILUSER_PROP_IDS}. + */ + private static final int PR_ORIGINAL_ENTRYID = 15; + + /** + * The index of the 1st fax telephone number (business fax). + */ + private static final int dispidFax1EmailAddress = 16; + + /** + * The index of the 2nd fax telephone number (home fax). + */ + private static final int dispidFax2EmailAddress = 17; + + /** + * The index of the 3rd fax telephone number (other fax). + */ + private static final int dispidFax3EmailAddress = 18; + + /** + * The index of the nickname. + */ + private static final int PR_NICKNAME = 19; + + /** + * The index of the name prefix. + */ + private static final int PR_DISPLAY_NAME_PREFIX = 20; + + /** + * The index of the personnal home page + */ + private static final int PR_PERSONAL_HOME_PAGE = 21; + + /** + * The index of the business home page + */ + private static final int PR_BUSINESS_HOME_PAGE = 22; + + /** + * The index of the instant messaging address. + */ + //private static final int dispidInstMsg = 23; + + /** + * The index of the home address + */ + //private static final int dispidHomeAddress = 24; + + /** + * The index of the work address + */ + //private static final int dispidWorkAddress = 25; + + /** + * The index of the other address + */ + //private static final int dispidOtherAddress = 26; + /** * The indexes in {@link #MAPI_MAILUSER_PROP_IDS} of the property IDs which * are to be represented in SourceContact as @@ -159,6 +233,9 @@ public class MsOutlookAddrBookContactQuery = new int[] { PR_EMAIL_ADDRESS, + PR_GIVEN_NAME, + PR_MIDDLE_NAME, + PR_SURNAME, PR_BUSINESS_TELEPHONE_NUMBER, PR_BUSINESS2_TELEPHONE_NUMBER, PR_HOME_TELEPHONE_NUMBER, @@ -166,7 +243,19 @@ public class MsOutlookAddrBookContactQuery PR_MOBILE_TELEPHONE_NUMBER, dispidEmail1EmailAddress, dispidEmail2EmailAddress, - dispidEmail3EmailAddress + dispidEmail3EmailAddress, + PR_COMPANY_NAME, + dispidFax1EmailAddress, + dispidFax2EmailAddress, + dispidFax3EmailAddress, + PR_NICKNAME, + PR_DISPLAY_NAME_PREFIX, + PR_PERSONAL_HOME_PAGE, + PR_BUSINESS_HOME_PAGE + //dispidInstMsg, + //dispidHomeAddress, + //dispidWorkAddress, + //dispidOtherAddress }; static @@ -211,7 +300,7 @@ public MsOutlookAddrBookContactQuery( * @param callback the PtrCallback to be notified about the * matching MAPI_MAILUSERs */ - private static native void foreachMailUser( + public static native void foreachMailUser( String query, PtrCallback callback); @@ -219,6 +308,16 @@ private ContactDetail.Category getCategory(int propIndex) { switch (propIndex) { + case PR_GIVEN_NAME: + case PR_MIDDLE_NAME: + case PR_SURNAME: + case PR_NICKNAME: + case PR_DISPLAY_NAME_PREFIX: + case PR_PERSONAL_HOME_PAGE: + return ContactDetail.Category.Personal; + case PR_COMPANY_NAME: + case PR_BUSINESS_HOME_PAGE: + return ContactDetail.Category.Organization; case dispidEmail1EmailAddress: case dispidEmail2EmailAddress: case dispidEmail3EmailAddress: @@ -229,7 +328,16 @@ private ContactDetail.Category getCategory(int propIndex) case PR_HOME2_TELEPHONE_NUMBER: case PR_HOME_TELEPHONE_NUMBER: case PR_MOBILE_TELEPHONE_NUMBER: + case dispidFax1EmailAddress: + case dispidFax2EmailAddress: + case dispidFax3EmailAddress: return ContactDetail.Category.Phone; + //case dispidInstMsg: + // return ContactDetail.Category.InstantMessaging; + //case dispidHomeAddress: + //case dispidWorkAddress: + //case dispidOtherAddress: + // return ContactDetail.Category.Address; default: return null; } @@ -248,8 +356,29 @@ private ContactDetail.SubCategory[] getSubCategories(int propIndex) { switch (propIndex) { + case PR_GIVEN_NAME: + case PR_MIDDLE_NAME: + return + new ContactDetail.SubCategory[] + { + ContactDetail.SubCategory.Name + }; + case PR_SURNAME: + return + new ContactDetail.SubCategory[] + { + ContactDetail.SubCategory.LastName + }; + case PR_NICKNAME: + return + new ContactDetail.SubCategory[] + { + ContactDetail.SubCategory.Nickname + }; + case PR_COMPANY_NAME: case PR_BUSINESS2_TELEPHONE_NUMBER: case PR_BUSINESS_TELEPHONE_NUMBER: + //case dispidWorkAddress: return new ContactDetail.SubCategory[] { @@ -257,6 +386,7 @@ private ContactDetail.SubCategory[] getSubCategories(int propIndex) }; case PR_HOME2_TELEPHONE_NUMBER: case PR_HOME_TELEPHONE_NUMBER: + //case dispidHomeAddress: return new ContactDetail.SubCategory[] { @@ -268,16 +398,130 @@ private ContactDetail.SubCategory[] getSubCategories(int propIndex) { ContactDetail.SubCategory.Mobile }; + case dispidFax1EmailAddress: + return + new ContactDetail.SubCategory[] + { + ContactDetail.SubCategory.Fax, + ContactDetail.SubCategory.Work + }; + case dispidFax2EmailAddress: + return + new ContactDetail.SubCategory[] + { + ContactDetail.SubCategory.Fax, + ContactDetail.SubCategory.Home + }; + case dispidFax3EmailAddress: + return + new ContactDetail.SubCategory[] + { + ContactDetail.SubCategory.Fax, + ContactDetail.SubCategory.Other + }; + //case dispidOtherAddress: + // return + // new ContactDetail.SubCategory[] + // { + // ContactDetail.SubCategory.Other + // }; default: return null; } } + /** + * Find the outlook property tag from category and subcategories. + * + * @param category The category. + * @param subCategories The subcategories. + * + * @return The outlook property tag corresponding to the given category and + * subcategories. + */ + public static long getProperty( + ContactDetail.Category category, + Collection subCategories) + { + switch(category) + { + case Personal: + if(subCategories.contains(ContactDetail.SubCategory.Name)) + return MAPI_MAILUSER_PROP_IDS[PR_GIVEN_NAME]; + // PR_MIDDLE_NAME: + else if(subCategories.contains( + ContactDetail.SubCategory.LastName)) + return MAPI_MAILUSER_PROP_IDS[PR_SURNAME]; + else if(subCategories.contains( + ContactDetail.SubCategory.Nickname)) + return MAPI_MAILUSER_PROP_IDS[PR_NICKNAME]; + else if(subCategories.contains( + ContactDetail.SubCategory.HomePage)) + return MAPI_MAILUSER_PROP_IDS[PR_PERSONAL_HOME_PAGE]; + else + return MAPI_MAILUSER_PROP_IDS[PR_DISPLAY_NAME_PREFIX]; + case Organization: + if(subCategories.contains(ContactDetail.SubCategory.Work)) + return MAPI_MAILUSER_PROP_IDS[PR_COMPANY_NAME]; + else + return MAPI_MAILUSER_PROP_IDS[PR_BUSINESS_HOME_PAGE]; + case Email: + return MAPI_MAILUSER_PROP_IDS[dispidEmail1EmailAddress]; + //dispidEmail2EmailAddress: + //dispidEmail3EmailAddress: + // PR_EMAIL_ADDRESS: + case Phone: + if(subCategories.contains(ContactDetail.SubCategory.Fax)) + { + if(subCategories.contains(ContactDetail.SubCategory.Work)) + return MAPI_MAILUSER_PROP_IDS[dispidFax1EmailAddress]; + else if(subCategories.contains( + ContactDetail.SubCategory.Home)) + return MAPI_MAILUSER_PROP_IDS[dispidFax2EmailAddress]; + else if(subCategories.contains( + ContactDetail.SubCategory.Other)) + return MAPI_MAILUSER_PROP_IDS[dispidFax3EmailAddress]; + } + else if(subCategories.contains(ContactDetail.SubCategory.Work)) + return MAPI_MAILUSER_PROP_IDS[PR_BUSINESS_TELEPHONE_NUMBER]; + // PR_BUSINESS2_TELEPHONE_NUMBER: + else if(subCategories.contains(ContactDetail.SubCategory.Home)) + return MAPI_MAILUSER_PROP_IDS[PR_HOME_TELEPHONE_NUMBER]; + // PR_HOME2_TELEPHONE_NUMBER: + else if(subCategories.contains( + ContactDetail.SubCategory.Mobile)) + return MAPI_MAILUSER_PROP_IDS[PR_MOBILE_TELEPHONE_NUMBER]; + break; + //case InstantMessaging: + // return MAPI_MAILUSER_PROP_IDS[dispidInstMsg]; + //case Address: + // if(subCategories.contains(ContactDetail.SubCategory.Work)) + // return MAPI_MAILUSER_PROP_IDS[dispidWorkAddress]; + // else if(subCategories.contains( + // ContactDetail.SubCategory.Home)) + // return MAPI_MAILUSER_PROP_IDS[dispidHomeAddress]; + // else if(subCategories.contains( + // ContactDetail.SubCategory.Other)) + // return MAPI_MAILUSER_PROP_IDS[dispidOtherAddress]; + // break; + } + return -1; + } + private static native Object[] IMAPIProp_GetProps( long mapiProp, long[] propIds, long flags) throws MsOutlookMAPIHResultException; + public static native boolean IMAPIProp_SetPropString( + long propId, + String value, + String entryId); + + public static native boolean IMAPIProp_DeleteProp( + long propId, + String entryId); + /** * Determines whether a specific index in {@link #MAPI_MAILUSER_PROP_IDS} * stands for a property with a phone number value. @@ -365,43 +609,7 @@ private boolean onMailUser(long iUnknown) } if (matches) { - List> supportedOpSets - = new ArrayList>(2); - - supportedOpSets.add(OperationSetBasicTelephony.class); - // can be added as contacts - supportedOpSets.add(OperationSetPersistentPresence.class); - - List contactDetails - = new LinkedList(); - - for (int i = 0; i < CONTACT_DETAIL_PROP_INDEXES.length; i++) - { - propIndex = CONTACT_DETAIL_PROP_INDEXES[i]; - - Object prop = props[propIndex]; - - if (prop instanceof String) - { - String stringProp = (String) prop; - - if (stringProp.length() != 0) - { - if (isPhoneNumber(propIndex)) - stringProp = - PhoneNumberI18nService.normalize(stringProp); - - ContactDetail contactDetail - = new ContactDetail( - stringProp, - getCategory(propIndex), - getSubCategories(propIndex)); - - contactDetail.setSupportedOpSets(supportedOpSets); - contactDetails.add(contactDetail); - } - } - } + List contactDetails = getContactDetails(props); /* * What's the point of showing a contact who has no contact details? @@ -413,9 +621,10 @@ private boolean onMailUser(long iUnknown) if ((displayName == null) || (displayName.length() == 0)) displayName = (String) props[PR_COMPANY_NAME]; - GenericSourceContact sourceContact - = new GenericSourceContact( + MsOutlookAddrBookSourceContact sourceContact + = new MsOutlookAddrBookSourceContact( getContactSource(), + (String) props[PR_ORIGINAL_ENTRYID], displayName, contactDetails); @@ -450,6 +659,57 @@ private boolean onMailUser(long iUnknown) return (getStatus() == QUERY_IN_PROGRESS); } + /** + * Gets the contactDetails to be set on a SourceContact + * which is to represent an ABPerson specified by the values of its + * {@link #ABPERSON_PROPERTIES}. + * + * @param values the values of the ABPERSON_PROPERTIES which + * represent the ABPerson to get the contactDetails of + * @return the contactDetails to be set on a SourceContact + * which is to represent the ABPerson specified by values + */ + private List getContactDetails(Object[] values) + { + List> supportedOpSets + = new ArrayList>(2); + supportedOpSets.add(OperationSetBasicTelephony.class); + // can be added as contacts + supportedOpSets.add(OperationSetPersistentPresence.class); + + List contactDetails = new LinkedList(); + + for (int i = 0; i < CONTACT_DETAIL_PROP_INDEXES.length; i++) + { + int property = CONTACT_DETAIL_PROP_INDEXES[i]; + Object value = values[property]; + + if (value instanceof String) + { + String stringValue = (String) value; + + if (stringValue.length() != 0) + { + if(isPhoneNumber(property)) + stringValue + = PhoneNumberI18nService.normalize(stringValue); + + MsOutlookAddrBookContactDetail contactDetail + = new MsOutlookAddrBookContactDetail( + stringValue, + getCategory(property), + getSubCategories(property), + MAPI_MAILUSER_PROP_IDS[property]); + + contactDetail.setSupportedOpSets(supportedOpSets); + contactDetails.add(contactDetail); + } + } + } + + return contactDetails; + } + /** * Performs this AsyncContactQuery in a background Thread. * @@ -487,24 +747,89 @@ public boolean callback(long iUnknown) } /** - * Notifies this AsyncContactQuery that it has stopped performing - * in the associated background Thread. + * Callback method when receiving notifications for inserted items. + * + * @param person The pointer to the outlook contact object. + */ + public void inserted(long person) + { + try + { + onMailUser(person); + } + catch (MsOutlookMAPIHResultException e) + { + if (logger.isDebugEnabled()) + { + logger.debug( + MsOutlookAddrBookContactQuery.class.getSimpleName() + + "#onMailUser(long)", + e); + } + } + } + + /** + * Callback method when receiving notifications for updated items. * - * @param completed true if this ContactQuery has - * successfully completed, false if an error has been encountered - * during its execution - * @see AsyncContactQuery#stopped(boolean) + * @param person The pointer to the outlook contact object. */ - @Override - protected void stopped(boolean completed) + public void updated(long person) { + Object[] props = null; try { - super.stopped(completed); + props = IMAPIProp_GetProps( + person, + MAPI_MAILUSER_PROP_IDS, + MAPI_UNICODE); } - finally + catch (MsOutlookMAPIHResultException e) { - getContactSource().stopped(this); + if (logger.isDebugEnabled()) + { + logger.debug( + MsOutlookAddrBookContactQuery.class.getSimpleName() + + "#IMAPIProp_GetProps(long, long[], long)", + e); + } + } + + if(props[PR_ORIGINAL_ENTRYID] != null) + { + SourceContact sourceContact + = findSourceContactByID((String) props[PR_ORIGINAL_ENTRYID]); + + if(sourceContact != null + && sourceContact instanceof MsOutlookAddrBookSourceContact) + { + // let's update the the details + MsOutlookAddrBookSourceContact editableSourceContact + = (MsOutlookAddrBookSourceContact) sourceContact; + + List contactDetails = getContactDetails(props); + editableSourceContact.setDetails(contactDetails); + + fireContactChanged(sourceContact); + } + } + } + + /** + * Callback method when receiving notifications for deleted items. + * + * @param id The outlook contact identifier. + */ + public void deleted(String id) + { + if(id != null) + { + SourceContact sourceContact = findSourceContactByID(id); + + if(sourceContact != null) + { + fireContactRemoved(sourceContact); + } } } } diff --git a/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactSourceService.java b/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactSourceService.java index 1f270acef..00797ccd6 100644 --- a/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactSourceService.java +++ b/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookContactSourceService.java @@ -11,16 +11,26 @@ import net.java.sip.communicator.plugin.addrbook.*; import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.util.*; /** * Implements ContactSourceService for the Address Book of Microsoft * Outlook. * * @author Lyubomir Marinov + * @author Vincent Lucas */ public class MsOutlookAddrBookContactSourceService extends AsyncContactSourceService { + /** + * The Logger used by the + * MsOutlookAddrBookContactSourceService class and its instances + * for logging output. + */ + private static final Logger logger + = Logger.getLogger(MsOutlookAddrBookContactSourceService.class); + /** * The outlook address book prefix. */ @@ -31,6 +41,11 @@ public class MsOutlookAddrBookContactSourceService private static final long MAPI_MULTITHREAD_NOTIFICATIONS = 0x00000001; + /** + * The latest query created. + */ + private MsOutlookAddrBookContactQuery latestQuery = null; + static { System.loadLibrary("jmsoutlookaddrbook"); @@ -53,13 +68,6 @@ public class MsOutlookAddrBookContactSourceService } } - /** - * The List of MsOutlookAddrBookContactQuery instances - * which have been started and haven't stopped yet. - */ - private final List queries - = new LinkedList(); - /** * Initializes a new MsOutlookAddrBookContactSourceService * instance. @@ -72,6 +80,8 @@ public MsOutlookAddrBookContactSourceService() throws MsOutlookMAPIHResultException { MAPIInitialize(MAPI_INIT_VERSION, MAPI_MULTITHREAD_NOTIFICATIONS); + + setDelegate(new NotificationsDelegate()); } /** @@ -119,33 +129,13 @@ private static native void MAPIInitialize(long version, long flags) */ public ContactQuery queryContactSource(Pattern query) { - MsOutlookAddrBookContactQuery msoabcq - = new MsOutlookAddrBookContactQuery(this, query); - - synchronized (queries) - { - queries.add(msoabcq); - } + if(latestQuery != null) + latestQuery.clear(); - boolean msoabcqHasStarted = false; + latestQuery = new MsOutlookAddrBookContactQuery(this, query); - try - { - msoabcq.start(); - msoabcqHasStarted = true; - } - finally - { - if (!msoabcqHasStarted) - { - synchronized (queries) - { - if (queries.remove(msoabcq)) - queries.notify(); - } - } - } - return msoabcq; + latestQuery.start(); + return latestQuery; } /** @@ -156,26 +146,11 @@ public ContactQuery queryContactSource(Pattern query) */ public void stop() { - boolean interrupted = false; - - synchronized (queries) + if(latestQuery != null) { - while (!queries.isEmpty()) - { - queries.get(0).cancel(); - try - { - queries.wait(); - } - catch (InterruptedException iex) - { - interrupted = true; - } - } + latestQuery.clear(); + latestQuery = null; } - if (interrupted) - Thread.currentThread().interrupt(); - MAPIUninitialize(); } @@ -192,28 +167,48 @@ public String getPhoneNumberPrefix() } /** - * Notifies this MsOutlookAddrBookContactSourceService that a - * specific MsOutlookAddrBookContactQuery has stopped. + * Returns the index of the contact source in the result list. * - * @param msoabcq the MsOutlookAddrBookContactQuery which has - * stopped + * @return the index of the contact source in the result list */ - void stopped(MsOutlookAddrBookContactQuery msoabcq) + public int getIndex() { - synchronized (queries) - { - if (queries.remove(msoabcq)) - queries.notify(); - } + return -1; } /** - * Returns the index of the contact source in the result list. - * - * @return the index of the contact source in the result list + * Delegate class to be notified for addressbook changes. */ - public int getIndex() + public class NotificationsDelegate { - return -1; + /** + * Callback method when receiving notifications for inserted items. + */ + public void inserted(long person) + { + if(latestQuery != null) + latestQuery.inserted(person); + } + + /** + * Callback method when receiving notifications for updated items. + */ + public void updated(long person) + { + if(latestQuery != null) + latestQuery.updated(person); + } + + /** + * Callback method when receiving notifications for deleted items. + */ + public void deleted(String id) + { + if(latestQuery != null) + latestQuery.deleted(id); + } } + + public static native void setDelegate( + NotificationsDelegate callback); } diff --git a/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookSourceContact.java b/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookSourceContact.java new file mode 100644 index 000000000..1741981f8 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/addrbook/msoutlook/MsOutlookAddrBookSourceContact.java @@ -0,0 +1,195 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.addrbook.msoutlook; + +import net.java.sip.communicator.plugin.addrbook.*; +import net.java.sip.communicator.service.contactsource.*; +import net.java.sip.communicator.util.*; + +import java.util.*; + +/** + * Implements a custom SourceContact for the Address Book of Microsoft + * Outlook. + * + * @author Vincent Lucas + */ +public class MsOutlookAddrBookSourceContact + extends GenericSourceContact + implements EditableSourceContact +{ + /** + * The Logger used by the MsOutlookAddrBookSourceContact + * class and its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(MsOutlookAddrBookSourceContact.class); + + /** + * Initializes a new MsOutlookAddrBookSourceContact instance. + * + * @param contactSource The ContactSourceService which is creating the new + * instance. + * @param id The outlook entry identifier for contacts. + * @param displayName The display name of the new instance. + * @param contactDetails The ContactDetails of the new instance. + */ + public MsOutlookAddrBookSourceContact( + ContactSourceService contactSource, + String id, + String displayName, + List contactDetails) + { + super(contactSource, displayName, contactDetails); + + this.setData(SourceContact.DATA_ID, id); + } + + /** + * Returns the string identifier for this source contact. + * + * @return The string identifier for this source contact. + */ + public String getId() + { + return (String) this.getData(SourceContact.DATA_ID); + } + + /** + * Changes the details list with the supplied one. Called when the outlook + * database for this source contact has been updated. + * + * @param details the details. + */ + public void setDetails(List details) + { + synchronized(this.contactDetails) + { + contactDetails.clear(); + contactDetails.addAll(details); + } + } + + /** + * Saves all the properties from this source contact into the outlook + * database. + */ + public void save() + { + synchronized(this.contactDetails) + { + MsOutlookAddrBookContactDetail outlookContactDetail; + + for(ContactDetail contactDetail: this.contactDetails) + { + if(contactDetail instanceof MsOutlookAddrBookContactDetail) + { + outlookContactDetail + = (MsOutlookAddrBookContactDetail) contactDetail; + for(Long propId: outlookContactDetail.getOutlookPropId()) + { + MsOutlookAddrBookContactQuery.IMAPIProp_SetPropString( + propId.longValue(), + contactDetail.getDetail(), + this.getId()); + } + } + } + } + } + + /** + * Removes the given ContactDetail from the list of details for + * this SourceContact. + * + * @param detail the ContactDetail to remove + */ + public void removeContactDetail(ContactDetail detail) + { + synchronized(this.contactDetails) + { + int i = 0; + while(i < this.contactDetails.size()) + { + MsOutlookAddrBookContactDetail contactDetail + = ((MsOutlookAddrBookContactDetail) + this.contactDetails.get(i)); + if(contactDetail.match(detail)) + { + this.removeProperty(contactDetail); + this.contactDetails.remove(i); + } + else + { + ++i; + } + } + } + } + + /** + * Removes the contact detail from the outlook database. + * + * @param contactDetail The contact detail to remove. + */ + public void removeProperty( + final MsOutlookAddrBookContactDetail contactDetail) + { + for(Long propId: contactDetail.getOutlookPropId()) + { + MsOutlookAddrBookContactQuery.IMAPIProp_DeleteProp( + propId.longValue(), + this.getId()); + } + } + + /** + * Adds a contact detail to the list of contact details. + * + * @param detail the ContactDetail to add + */ + public void addContactDetail(ContactDetail detail) + { + synchronized(this.contactDetails) + { + MsOutlookAddrBookContactDetail addDetail; + if(!(detail instanceof MsOutlookAddrBookContactDetail)) + { + long property = MsOutlookAddrBookContactQuery.getProperty( + detail.getCategory(), + detail.getSubCategories()); + Collection subCategories + = detail.getSubCategories(); + addDetail = new MsOutlookAddrBookContactDetail( + detail.getDetail(), + detail.getCategory(), + subCategories.toArray( + new ContactDetail.SubCategory[ + subCategories.size()]), + property); + } + else + { + addDetail = (MsOutlookAddrBookContactDetail) detail; + } + + // Checks if this property already exists. + for(int i = 0; i < this.contactDetails.size(); ++ i) + { + MsOutlookAddrBookContactDetail contactDetail + = ((MsOutlookAddrBookContactDetail) + this.contactDetails.get(i)); + if(contactDetail.match(addDetail)) + { + return; + } + } + this.contactDetails.add(addDetail); + this.save(); + } + } +}