/* MAPIStoreContext.m - this file is part of SOGo * * Copyright (C) 2010 Inverse inc. * * Author: Wolfgang Sourdeau * * This file 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 3, or (at your option) * any later version. * * This file 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #import #import #import #import #import #import #import #import #import "SOGoMAPIFSFolder.h" #import "SOGoMAPIFSMessage.h" #import "MAPIApplication.h" #import "MAPIStoreAttachment.h" // #import "MAPIStoreAttachmentTable.h" #import "MAPIStoreAuthenticator.h" #import "MAPIStoreFolder.h" #import "MAPIStoreFolderTable.h" #import "MAPIStoreMapping.h" #import "MAPIStoreMessage.h" #import "MAPIStoreMessageTable.h" #import "MAPIStoreFAIMessage.h" #import "MAPIStoreFAIMessageTable.h" #import "MAPIStoreTypes.h" #import "NSArray+MAPIStore.h" #import "NSObject+MAPIStore.h" #import "NSString+MAPIStore.h" #import "MAPIStoreContext.h" #undef DEBUG #include #include #include #include #include #include #include /* TODO: homogenize method names and order of parameters */ @implementation MAPIStoreContext : NSObject /* sogo://username:password@{contacts,calendar,tasks,journal,notes,mail}/dossier/id */ static Class NSDataK, NSStringK, MAPIStoreFAIMessageK; static NSMutableDictionary *contextClassMapping; static NSMutableDictionary *userMAPIStoreMapping; + (void) initialize { NSArray *classes; Class currentClass; NSUInteger count, max; NSString *moduleName; NSDataK = [NSData class]; NSStringK = [NSString class]; MAPIStoreFAIMessageK = [MAPIStoreFAIMessage class]; contextClassMapping = [NSMutableDictionary new]; classes = GSObjCAllSubclassesOfClass (self); max = [classes count]; for (count = 0; count < max; count++) { currentClass = [classes objectAtIndex: count]; moduleName = [currentClass MAPIModuleName]; if (moduleName) { [contextClassMapping setObject: currentClass forKey: moduleName]; NSLog (@" registered class '%@' as handler of '%@' contexts", NSStringFromClass (currentClass), moduleName); } } userMAPIStoreMapping = [NSMutableDictionary new]; } static inline MAPIStoreContext * _prepareContextClass (struct mapistore_context *newMemCtx, Class contextClass, struct mapistore_connection_info *connInfo, NSURL *url, uint64_t fid) { static NSMutableDictionary *registration = nil; MAPIStoreContext *context; MAPIStoreAuthenticator *authenticator; if (!registration) registration = [NSMutableDictionary new]; if (![registration objectForKey: contextClass]) [registration setObject: [NSNull null] forKey: contextClass]; context = [[contextClass alloc] initFromURL: url withConnectionInfo: connInfo andFID: fid inMemCtx: newMemCtx]; [context autorelease]; authenticator = [MAPIStoreAuthenticator new]; [authenticator setUsername: [url user]]; [authenticator setPassword: [url password]]; [context setAuthenticator: authenticator]; [authenticator release]; [context setupRequest]; [context setupBaseFolder: url]; [context->folders setObject: context->baseFolder forKey: [NSNumber numberWithUnsignedLongLong: fid]]; [context tearDownRequest]; return context; } + (id) contextFromURI: (const char *) newUri withConnectionInfo: (struct mapistore_connection_info *) connInfo andFID: (uint64_t) fid inMemCtx: (struct mapistore_context *) newMemCtx { MAPIStoreContext *context; Class contextClass; NSString *module, *completeURLString, *urlString; NSURL *baseURL; NSLog (@"METHOD '%s' (%d) -- uri: '%s'", __FUNCTION__, __LINE__, newUri); context = nil; urlString = [NSString stringWithUTF8String: newUri]; if (urlString) { completeURLString = [@"sogo://" stringByAppendingString: urlString]; if (![completeURLString hasSuffix: @"/"]) completeURLString = [completeURLString stringByAppendingString: @"/"]; baseURL = [NSURL URLWithString: [completeURLString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]]; if (baseURL) { module = [baseURL host]; if (module) { contextClass = [contextClassMapping objectForKey: module]; if (contextClass) context = _prepareContextClass (newMemCtx, contextClass, connInfo, baseURL, fid); else NSLog (@"ERROR: unrecognized module name '%@'", module); } } else NSLog (@"ERROR: url could not be parsed"); } else NSLog (@"ERROR: url is an invalid UTF-8 string"); return context; } - (id) init { if ((self = [super init])) { messages = [NSMutableDictionary new]; folders = [NSMutableDictionary new]; woContext = [WOContext contextWithRequest: nil]; [woContext retain]; baseFolder = nil; contextUrl = nil; cachedTable = nil; cachedFolder = nil; } return self; } - (id) initFromURL: (NSURL *) newUrl withConnectionInfo: (struct mapistore_connection_info *) newConnInfo andFID: (uint64_t) newFid inMemCtx: (struct mapistore_context *) newMemCtx { NSString *username; if ((self = [self init])) { ASSIGN (contextUrl, newUrl); username = [NSString stringWithUTF8String: newConnInfo->username]; mapping = [userMAPIStoreMapping objectForKey: username]; if (!mapping) { [self logWithFormat: @"generating mapping of ids for user '%@'", username]; mapping = [MAPIStoreMapping mappingWithIndexing: newConnInfo->indexing]; [userMAPIStoreMapping setObject: mapping forKey: username]; } if (![mapping urlFromID: newFid]) [mapping registerURL: [newUrl absoluteString] withID: newFid]; contextFid = newFid; memCtx = newConnInfo->mstore_ctx; connInfo = newConnInfo; } return self; } - (void) dealloc { [messages release]; [folders release]; [cachedTable release]; [cachedFolder release]; [baseFolder release]; [woContext release]; [authenticator release]; [contextUrl release]; [super dealloc]; } - (WOContext *) woContext { return woContext; } - (MAPIStoreMapping *) mapping { return mapping; } - (void) setAuthenticator: (MAPIStoreAuthenticator *) newAuthenticator { ASSIGN (authenticator, newAuthenticator); } - (MAPIStoreAuthenticator *) authenticator { return authenticator; } - (NSURL *) url { return contextUrl; } - (struct mapistore_connection_info *) connectionInfo { return connInfo; } - (void) setupRequest { NSMutableDictionary *info; [MAPIApp setMAPIStoreContext: self]; info = [[NSThread currentThread] threadDictionary]; [info setObject: woContext forKey: @"WOContext"]; } - (void) tearDownRequest { NSMutableDictionary *info; info = [[NSThread currentThread] threadDictionary]; [info removeObjectForKey: @"WOContext"]; [MAPIApp setMAPIStoreContext: nil]; } - (MAPIStoreObject *) _lookupObjectWithParts: (NSArray *) parts { NSUInteger count, max; NSString *currentPart; MAPIStoreObject *currentObject; currentObject = baseFolder; max = [parts count]; for (count = 0; count < max; count++) { currentPart = [parts objectAtIndex: count]; if ([currentPart length] > 0) currentObject = [currentObject lookupChild: currentPart]; } return currentObject; } - (id) lookupObject: (NSString *) childURL { NSString *baseURL, *subURL; MAPIStoreObject *foundObject; NSArray *parts; baseURL = [contextUrl absoluteString]; if (![baseURL hasSuffix: @"/"]) baseURL = [NSString stringWithFormat: @"%@/", baseURL]; if (![childURL hasSuffix: @"/"]) childURL = [NSString stringWithFormat: @"%@/", childURL]; if ([childURL isEqualToString: baseURL]) foundObject = baseFolder; else if ([childURL hasPrefix: baseURL]) { subURL = [childURL substringFromIndex: [baseURL length]]; parts = [subURL componentsSeparatedByString: @"/"]; foundObject = [self _lookupObjectWithParts: parts]; [self logWithFormat: @"returning object '%@'", childURL]; } else { [self errorWithFormat: @"url '%@' is not a child of this context (%@)", childURL, baseURL]; foundObject = nil; } /* TODO hierarchy */ return foundObject; } - (id) lookupFolderWithFID: (uint64_t) fid { MAPIStoreFolder *folder; NSNumber *fidKey; NSString *folderURL; fidKey = [NSNumber numberWithUnsignedLongLong: fid]; folder = [folders objectForKey: fidKey]; if (!folder) { /* TODO: should handle folder hierarchies */ folderURL = [mapping urlFromID: fid]; if (folderURL) { folder = [self lookupObject: folderURL]; if (folder) [folders setObject: folder forKey: fidKey]; } else [self errorWithFormat: @"folder with url '%@' not found", folderURL]; } [folder setMAPIRetainCount: [folder mapiRetainCount] + 1]; return folder; } - (void) releaseFolderWithFID: (uint64_t) fid { MAPIStoreFolder *folder; NSNumber *fidKey; uint32_t retainCount; fidKey = [NSNumber numberWithUnsignedLongLong: fid]; folder = [folders objectForKey: fidKey]; if (folder) { retainCount = [folder mapiRetainCount]; if (retainCount == 1) [folders removeObjectForKey: fidKey]; else [folder setMAPIRetainCount: retainCount - 1]; } } /** \details Create a folder in the sogo backend \param private_data pointer to the current sogo context \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE_ERROR */ - (int) mkDir: (struct SRow *) aRow withFID: (uint64_t) fid inParentFID: (uint64_t) parentFID { NSString *folderURL, *folderKey; MAPIStoreFolder *parentFolder, *newFolder; NSNumber *fidKey; int rc; [self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__]; folderURL = [mapping urlFromID: fid]; if (folderURL) rc = MAPISTORE_ERR_EXIST; else { fidKey = [NSNumber numberWithUnsignedLongLong: parentFID]; parentFolder = [folders objectForKey: fidKey]; if (parentFolder) { folderKey = [parentFolder createFolder: aRow withFID: fid]; if (folderKey) { [parentFolder cleanupCaches]; folderURL = [NSString stringWithFormat: @"%@%@", [parentFolder url], folderKey]; [mapping registerURL: folderURL withID: fid]; newFolder = [parentFolder lookupChild: folderKey]; if (newFolder) [newFolder setProperties: aRow]; else [NSException raise: @"MAPIStoreIOException" format: @"unable to fetch created folder"]; rc = MAPISTORE_SUCCESS; } else rc = MAPISTORE_ERROR; } else rc = MAPISTORE_ERR_NOT_FOUND; } return rc; } /** \details Delete a folder from the sogo backend \param private_data pointer to the current sogo context \param parentFID the FID for the parent of the folder to delete \param fid the FID for the folder to delete \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE_ERROR */ - (int) rmDirWithFID: (uint64_t) fid inParentFID: (uint64_t) parentFid { [self logWithFormat: @"UNIMPLEMENTED METHOD '%s' (%d)", __FUNCTION__, __LINE__]; return MAPISTORE_ERROR; } /** \details Open a folder from the sogo backend \param private_data pointer to the current sogo context \param parentFID the parent folder identifier \param fid the identifier of the colder to open \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE_ERROR */ - (int) openDir: (uint64_t) fid { MAPIStoreFolder *folder; int rc; folder = [self lookupFolderWithFID: fid]; if (folder) rc = MAPISTORE_SUCCESS; else rc = MAPISTORE_ERR_NOT_FOUND; return rc; } /** \details Close a folder from the sogo backend \param private_data pointer to the current sogo context \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE_ERROR */ - (int) closeDir { // MAPIStoreFolder *folder; // NSNumber *fidKey; // uint32_t retainCount; // fidKey = [NSNumber numberWithUnsignedLongLong: fid]; // folder = [folders objectForKey: fidKey]; // if (folder) // { // rc = MAPISTORE_SUCCESS; // retainCount = [folder mapiRetainCount]; // if (retainCount == 0) // { // [self logWithFormat: @"folder with fid %.16x successfully removed" // @" from folder cache", // fmid]; // [folders removeObjectForKey: midKey]; // } // else // [folder setMAPIRetainCount: retainCount - 1]; // } // else // rc = MAPISTORE_ERR_NOT_FOUND; [self logWithFormat: @"UNIMNPLEMENTED METHOD '%s' -- leak ahead (%d)", __FUNCTION__, __LINE__]; return MAPISTORE_SUCCESS; } - (MAPIStoreTable *) _tableForFID: (uint64_t) fid andTableType: (uint8_t) tableType { MAPIStoreFolder *folder; MAPIStoreTable *table; NSNumber *fidKey; if (fid == cachedTableFID && tableType == cachedTableType) table = cachedTable; else { [cachedTable release]; cachedTable = nil; [cachedFolder release]; cachedFolder = nil; cachedTableFID = 0; cachedTableType = 0; fidKey = [NSNumber numberWithUnsignedLongLong: fid]; folder = [folders objectForKey: fidKey]; if (folder) { if (tableType == MAPISTORE_MESSAGE_TABLE) table = [folder messageTable]; else if (tableType == MAPISTORE_FAI_TABLE) table = [folder faiMessageTable]; else if (tableType == MAPISTORE_FOLDER_TABLE) table = [folder folderTable]; else { table = nil; [NSException raise: @"MAPIStoreIOException" format: @"unsupported table type: %d", tableType]; } if (table) { cachedTableFID = fid; cachedTableType = tableType; ASSIGN (cachedTable, table); ASSIGN (cachedFolder, folder); } } else { table = nil; [self errorWithFormat: @"folder with fid %Lu not found", (unsigned long long) fid]; } } return table; } /** \details Read directory content from the sogo backend \param private_data pointer to the current sogo context \return MAPISTORE_SUCCESS on success, otherwise MAPISTORE_ERROR */ - (int) readCount: (uint32_t *) rowCount ofTableType: (uint8_t) tableType inFID: (uint64_t) fid { NSArray *keys; NSString *url; NSNumber *fidKey; MAPIStoreFolder *folder; int rc; /* WARNING: make sure this method is no longer invoked for counting table elements */ [self logWithFormat: @"METHOD '%s' (%d) -- tableType: %d", __FUNCTION__, __LINE__, tableType]; url = [mapping urlFromID: fid]; if (url) { fidKey = [NSNumber numberWithUnsignedLongLong: fid]; folder = [folders objectForKey: fidKey]; if (folder) { if (tableType == MAPISTORE_MESSAGE_TABLE) keys = [folder messageKeys]; else if (tableType == MAPISTORE_FOLDER_TABLE) keys = [folder folderKeys]; else if (tableType == MAPISTORE_FAI_TABLE) keys = [folder faiMessageKeys]; *rowCount = [keys count]; rc = MAPI_E_SUCCESS; } else { [self errorWithFormat: @"No folder found for URL: %@", url]; rc = MAPISTORE_ERR_NOT_FOUND; } } else { [self errorWithFormat: @"No url found for FID: %lld", fid]; rc = MAPISTORE_ERR_NOT_FOUND; } // } [self logWithFormat: @"result: count = %d, rc = %d", *rowCount, rc]; return rc; } // - (void) logRestriction: (struct mapi_SRestriction *) res // withState: (MAPIRestrictionState) state // { // NSString *resStr; // resStr = MAPIStringForRestriction (res); // [self logWithFormat: @"%@ --> %@", resStr, MAPIStringForRestrictionState (state)]; // } - (int) setRestrictions: (const struct mapi_SRestriction *) res withFID: (uint64_t) fid andTableType: (uint8_t) tableType getTableStatus: (uint8_t *) tableStatus { MAPIStoreTable *table; [self errorWithFormat: @"%s: obsolete method", __FUNCTION__]; table = [self _tableForFID: fid andTableType: tableType]; [table setRestrictions: res]; // FIXME: we should not flush the caches if the restrictions matches [table cleanupCaches]; return MAPISTORE_SUCCESS; } - (int) setSortOrder: (const struct SSortOrderSet *) set withFID: (uint64_t) fid andTableType: (uint8_t) type getTableStatus: (uint8_t *) tableStatus { MAPIStoreTable *table; [self errorWithFormat: @"%s: obsolete method", __FUNCTION__]; table = [self _tableForFID: fid andTableType: type]; [table setSortOrder: set]; [table cleanupCaches]; return MAPISTORE_SUCCESS; } - (enum MAPISTATUS) getTableProperty: (void **) data withTag: (enum MAPITAGS) propTag atPosition: (uint32_t) pos withTableType: (uint8_t) tableType andQueryType: (enum table_query_type) queryType inFID: (uint64_t) fid { NSString *folderURL; MAPIStoreTable *table; MAPIStoreObject *object; const char *propName; int rc; // [self errorWithFormat: @"%s: obsolete method", __FUNCTION__]; folderURL = [mapping urlFromID: fid]; if (folderURL) { table = [self _tableForFID: fid andTableType: tableType]; *data = NULL; object = [table childAtRowID: pos forQueryType: queryType]; if (object) { rc = [object getProperty: data withTag: propTag]; if (rc == MAPISTORE_ERR_NOT_FOUND) rc = MAPI_E_NOT_FOUND; else if (rc == MAPISTORE_ERR_NO_MEMORY) rc = MAPI_E_NOT_ENOUGH_MEMORY; else if (rc == MAPISTORE_SUCCESS && *data == NULL) { propName = get_proptag_name (propTag); if (!propName) propName = ""; [self errorWithFormat: @"both 'success' and NULL data" @" returned for proptag %s(0x%.8x)", propName, propTag]; rc = MAPI_E_NOT_FOUND; } } else rc = MAPI_E_INVALID_OBJECT; } else { [self errorWithFormat: @"No url found for FID: %lld", fid]; rc = MAPI_E_INVALID_OBJECT; } return rc; } - (int) getAvailableProperties: (struct SPropTagArray **) propertiesP ofTableType: (uint8_t) type { int rc = MAPISTORE_SUCCESS; switch (type) { case MAPISTORE_FOLDER_TABLE: [[baseFolder class] getAvailableProperties: propertiesP]; break; case MAPISTORE_MESSAGE_TABLE: [[baseFolder messageClass] getAvailableProperties: propertiesP]; break; case MAPISTORE_FAI_TABLE: [MAPIStoreFAIMessage getAvailableProperties: propertiesP]; break; case MAPISTORE_RULE_TABLE: [self errorWithFormat: @"%s: rules not handled yet", __PRETTY_FUNCTION__]; rc = MAPISTORE_ERROR; break; case MAPISTORE_ATTACHMENT_TABLE: [self errorWithFormat: @"%s: attachments not handled yet", __PRETTY_FUNCTION__]; rc = MAPISTORE_ERROR; break; } return rc; } - (int) openMessage: (struct mapistore_message *) msg withMID: (uint64_t) mid inFID: (uint64_t) fid { NSString *messageKey, *messageURL; MAPIStoreMessage *message; MAPIStoreFolder *folder; NSNumber *midKey, *fidKey; int rc; midKey = [NSNumber numberWithUnsignedLongLong: mid]; message = [messages objectForKey: midKey]; if (message) rc = MAPISTORE_SUCCESS; else { rc = MAPISTORE_ERR_NOT_FOUND; messageURL = [mapping urlFromID: mid]; if (messageURL) { fidKey = [NSNumber numberWithUnsignedLongLong: fid]; folder = [folders objectForKey: fidKey]; messageKey = [self extractChildNameFromURL: messageURL andFolderURLAt: NULL]; message = [folder lookupChild: messageKey]; if (message) { [message openMessage: msg]; [messages setObject: message forKey: midKey]; rc = MAPISTORE_SUCCESS; } } } [message setMAPIRetainCount: [message mapiRetainCount] + 1]; return rc; } - (int) createMessageWithMID: (uint64_t) mid inFID: (uint64_t) fid isAssociated: (BOOL) isAssociated { NSNumber *midKey, *fidKey; NSString *childURL; MAPIStoreMessage *message; MAPIStoreFolder *folder; int rc; [self logWithFormat: @"METHOD '%s' -- mid: 0x%.16x, fid: 0x%.16x, associated: %d", __FUNCTION__, mid, fid, isAssociated]; midKey = [NSNumber numberWithUnsignedLongLong: mid]; message = [messages objectForKey: midKey]; if (message) rc = MAPISTORE_ERR_EXIST; else { fidKey = [NSNumber numberWithUnsignedLongLong: fid]; folder = [folders objectForKey: fidKey]; if (folder) { message = [folder createMessage: isAssociated]; if (message) { [messages setObject: message forKey: midKey]; [message setMAPIRetainCount: [message mapiRetainCount] + 1]; childURL = [NSString stringWithFormat: @"%@%@", [folder url], [message nameInContainer]]; [mapping registerURL: childURL withID: mid]; rc = MAPISTORE_SUCCESS; } else rc = MAPISTORE_ERROR; // { // if (![folderURL hasSuffix: @"/"]) // folderURL = [NSString stringWithFormat: @"%@/", folderURL]; // messageURL = [NSString stringWithFormat: @"%@%@", folderURL, // [message nameInContainer]]; // [mapping registerURL: messageURL withID: mid]; } else rc = MAPISTORE_ERR_NOT_FOUND; } return rc; } - (int) _saveOrSubmitChangesInMessageWithMID: (uint64_t) mid andFlags: (uint8_t) flags save: (BOOL) isSave { int rc; MAPIStoreMessage *message; MAPIStoreFolder *folder; NSNumber *midKey; NSArray *activeTables; NSUInteger count, max; // NSArray *propKeys; struct mapistore_object_notification_parameters *notif_parameters; // uint16_t count, max; uint64_t folderId; midKey = [NSNumber numberWithUnsignedLongLong: mid]; message = [messages objectForKey: midKey]; if (message) { rc = MAPISTORE_SUCCESS; folder = (MAPIStoreFolder *) [message container]; [self logWithFormat: @"folder for message is: %p", folder]; if (isSave) { /* notifications */ folderId = [folder objectId]; /* folder modified */ notif_parameters = talloc_zero(memCtx, struct mapistore_object_notification_parameters); notif_parameters->object_id = folderId; if ([message isNew]) { notif_parameters->tag_count = 3; notif_parameters->tags = talloc_array (notif_parameters, enum MAPITAGS, 3); notif_parameters->tags[0] = PR_CONTENT_COUNT; notif_parameters->tags[1] = PR_MESSAGE_SIZE; notif_parameters->tags[2] = PR_NORMAL_MESSAGE_SIZE; notif_parameters->new_message_count = true; notif_parameters->message_count = [[folder messageKeys] count] + 1; } mapistore_push_notification (connInfo->mstore_ctx, MAPISTORE_FOLDER, MAPISTORE_OBJECT_MODIFIED, notif_parameters); /* message created */ if ([message isNew]) { notif_parameters = talloc_zero(memCtx, struct mapistore_object_notification_parameters); notif_parameters->object_id = [message objectId]; notif_parameters->folder_id = folderId; notif_parameters->tag_count = 0xffff; mapistore_push_notification (connInfo->mstore_ctx, MAPISTORE_MESSAGE, MAPISTORE_OBJECT_CREATED, notif_parameters); talloc_free (notif_parameters); } /* we ensure the table caches are loaded so that old and new state can be compared */ activeTables = ([message isKindOfClass: MAPIStoreFAIMessageK] ? [folder activeFAIMessageTables] : [folder activeMessageTables]); max = [activeTables count]; for (count = 0; count < max; count++) [[activeTables objectAtIndex: count] restrictedChildKeys]; [message save]; /* table modified */ for (count = 0; count < max; count++) [[activeTables objectAtIndex: count] notifyChangesForChild: message]; } else [message submit]; [message setIsNew: NO]; [message resetNewProperties]; [folder cleanupCaches]; } else rc = MAPISTORE_ERROR; return rc; } - (int) saveChangesInMessageWithMID: (uint64_t) mid andFlags: (uint8_t) flags { [self logWithFormat: @"METHOD '%s' -- mid: 0x%.16x, flags: 0x%x", __FUNCTION__, mid, flags]; return [self _saveOrSubmitChangesInMessageWithMID: mid andFlags: flags save: YES]; } - (int) submitMessageWithMID: (uint64_t) mid andFlags: (uint8_t) flags { [self logWithFormat: @"METHOD '%s' -- mid: 0x%.16x, flags: 0x%x", __FUNCTION__, mid, flags]; return [self _saveOrSubmitChangesInMessageWithMID: mid andFlags: flags save: NO]; } - (int) getProperties: (struct SPropTagArray *) sPropTagArray ofTableType: (uint8_t) tableType inRow: (struct SRow *) aRow withMID: (uint64_t) fmid { NSNumber *midKey; MAPIStoreObject *child; NSInteger count; void *propValue; const char *propName; enum MAPITAGS tag; enum MAPISTATUS propRc; struct mapistore_property_data *data; int rc; [self logWithFormat: @"METHOD '%s' -- fmid: 0x%.16x, tableType: %d", __FUNCTION__, fmid, tableType]; midKey = [NSNumber numberWithUnsignedLongLong: fmid]; child = [messages objectForKey: midKey]; if (!child) child = [folders objectForKey: midKey]; if (child) { data = talloc_array (memCtx, struct mapistore_property_data, sPropTagArray->cValues); memset (data, 0, sizeof (struct mapistore_property_data) * sPropTagArray->cValues); rc = [child getProperties: data withTags: sPropTagArray->aulPropTag andCount: sPropTagArray->cValues]; if (rc == MAPISTORE_SUCCESS) { aRow->lpProps = talloc_array (aRow, struct SPropValue, sPropTagArray->cValues); aRow->cValues = sPropTagArray->cValues; for (count = 0; count < sPropTagArray->cValues; count++) { tag = sPropTagArray->aulPropTag[count]; propValue = data[count].data; propRc = data[count].error; // propName = get_proptag_name (tag); // if (!propName) // propName = ""; // [self logWithFormat: @" lookup of property %s (%.8x) returned %d", // propName, tag, propRc]; if (propRc == MAPI_E_SUCCESS && !propValue) { propName = get_proptag_name (tag); if (!propName) propName = ""; [self errorWithFormat: @"both 'success' and NULL data" @" returned for proptag %s(0x%.8x)", propName, tag]; propRc = MAPI_E_NOT_FOUND; } if (propRc != MAPI_E_SUCCESS) { if (propRc == MAPISTORE_ERR_NOT_FOUND) propRc = MAPI_E_NOT_FOUND; // else if (propRc == MAPISTORE_ERR_NO_MEMORY) // propRc = MAPI_E_NOT_ENOUGH_MEMORY; if (propValue) talloc_free (propValue); propValue = MAPILongValue (memCtx, propRc); tag = (tag & 0xffff0000) | 0x000a; } set_SPropValue_proptag (aRow->lpProps + count, tag, propValue); } } talloc_free (data); } else { [self errorWithFormat: @"no message/folder found for fmid %lld", fmid]; rc = MAPI_E_INVALID_OBJECT; } return rc; } - (int) getPath: (char **) path ofFMID: (uint64_t) fmid withTableType: (uint8_t) tableType { int rc; NSString *objectURL, *url; // TDB_DATA key, dbuf; url = [contextUrl absoluteString]; objectURL = [mapping urlFromID: fmid]; if (objectURL) { if ([objectURL hasPrefix: url]) { *path = [[objectURL substringFromIndex: 7] asUnicodeInMemCtx: memCtx]; [self logWithFormat: @"found path '%s' for fmid %.16x", *path, fmid]; rc = MAPI_E_SUCCESS; } else { [self logWithFormat: @"context (%@, %@) does not contain" @" found fmid: 0x%.16x", objectURL, url, fmid]; *path = NULL; rc = MAPI_E_NOT_FOUND; } } else { [self errorWithFormat: @"%s: you should *never* get here", __PRETTY_FUNCTION__]; // /* attempt to populate our mapping dict with data from indexing.tdb */ // key.dptr = (unsigned char *) talloc_asprintf (memCtx, "0x%.16llx", // (long long unsigned int )fmid); // key.dsize = strlen ((const char *) key.dptr); // dbuf = tdb_fetch (memCtx->indexing_list->index_ctx->tdb, key); // talloc_free (key.dptr); // uri = talloc_strndup (memCtx, (const char *)dbuf.dptr, dbuf.dsize); *path = NULL; rc = MAPI_E_NOT_FOUND; } return rc; } - (int) getFID: (uint64_t *) fid byName: (const char *) foldername inParentFID: (uint64_t) parent_fid { [self logWithFormat: @"METHOD '%s' (%d) -- foldername: %s, parent_fid: %lld", __FUNCTION__, __LINE__, foldername, parent_fid]; return MAPISTORE_ERROR; } - (int) setPropertiesWithFMID: (uint64_t) fmid ofTableType: (uint8_t) tableType inRow: (struct SRow *) aRow { MAPIStoreMessage *message; MAPIStoreFolder *folder; NSMutableDictionary *properties; NSNumber *fmidKey; struct SPropValue *cValue; NSUInteger counter; int rc; [self logWithFormat: @"METHOD '%s' -- fmid: 0x%.16x, tableType: %d", __FUNCTION__, fmid, tableType]; fmidKey = [NSNumber numberWithUnsignedLongLong: fmid]; switch (tableType) { case MAPISTORE_MESSAGE: message = [messages objectForKey: fmidKey]; if (message) { properties = [NSMutableDictionary dictionaryWithCapacity: aRow->cValues]; [self logWithFormat: @"fmid 0x%.16x found", fmid]; for (counter = 0; counter < aRow->cValues; counter++) { cValue = aRow->lpProps + counter; [properties setObject: NSObjectFromSPropValue (cValue) forKey: MAPIPropertyKey (cValue->ulPropTag)]; } [message addNewProperties: properties]; [self logWithFormat: @"(%s) message props after op", __PRETTY_FUNCTION__]; MAPIStoreDumpMessageProperties (properties); rc = MAPISTORE_SUCCESS; } else { [self errorWithFormat: @"fmid 0x%.16x *not* found (faking success)", fmid]; rc = MAPISTORE_SUCCESS; } break; case MAPISTORE_FOLDER: folder = [folders objectForKey: fmidKey]; if (folder) rc = [folder setProperties: aRow]; else rc = MAPISTORE_ERR_NOT_FOUND; break; default: [self errorWithFormat: @"%s: value of tableType not handled: %d", __FUNCTION__, tableType]; rc = MAPISTORE_ERROR; } return rc; } - (NSDictionary *) _convertRecipientFromRow: (struct RecipientRow *) row { NSMutableDictionary *recipient; NSString *value; SOGoUser *recipientUser; recipient = [NSMutableDictionary dictionaryWithCapacity: 5]; if ((row->RecipientFlags & 0x07) == 1) { value = [NSString stringWithUTF8String: row->X500DN.recipient_x500name]; [recipient setObject: value forKey: @"x500dn"]; recipientUser = [SOGoUser userWithLogin: [value lowercaseString]]; if (recipientUser) { value = [recipientUser cn]; if ([value length] > 0) [recipient setObject: value forKey: @"fullName"]; value = [[recipientUser allEmails] objectAtIndex: 0]; if ([value length] > 0) [recipient setObject: value forKey: @"email"]; } } else { switch ((row->RecipientFlags & 0x208)) { case 0x08: // TODO: we cheat value = [NSString stringWithUTF8String: row->EmailAddress.lpszA]; break; case 0x208: value = [NSString stringWithUTF8String: row->EmailAddress.lpszW]; break; default: value = nil; } if (value) [recipient setObject: value forKey: @"email"]; switch ((row->RecipientFlags & 0x210)) { case 0x10: // TODO: we cheat value = [NSString stringWithUTF8String: row->DisplayName.lpszA]; break; case 0x210: value = [NSString stringWithUTF8String: row->DisplayName.lpszW]; break; default: value = nil; } if (value) [recipient setObject: value forKey: @"fullName"]; } return recipient; } - (int) modifyRecipientsWithMID: (uint64_t) mid inRows: (struct ModifyRecipientRow *) rows withCount: (NSUInteger) max { static NSString *recTypes[] = { @"orig", @"to", @"cc", @"bcc" }; MAPIStoreMessage *message; NSDictionary *newProperties; NSMutableDictionary *recipients; NSMutableArray *list; NSString *recType; struct ModifyRecipientRow *currentRow; NSUInteger count; int rc; [self logWithFormat: @"METHOD '%s' -- mid: 0x%.16x", __FUNCTION__, mid]; message = [messages objectForKey: [NSNumber numberWithUnsignedLongLong: mid]]; if (message) { recipients = [NSMutableDictionary new]; newProperties = [NSDictionary dictionaryWithObject: recipients forKey: @"recipients"]; [recipients release]; for (count = 0; count < max; count++) { currentRow = rows + count; if (currentRow->RecipClass >= MAPI_ORIG && currentRow->RecipClass < MAPI_BCC) { recType = recTypes[currentRow->RecipClass]; list = [recipients objectForKey: recType]; if (!list) { list = [NSMutableArray new]; [recipients setObject: list forKey: recType]; [list release]; } [list addObject: [self _convertRecipientFromRow: &(currentRow->RecipientRow)]]; } } [message addNewProperties: newProperties]; rc = MAPISTORE_SUCCESS; } else rc = MAPISTORE_ERR_NOT_FOUND; return rc; } - (int) deleteMessageWithMID: (uint64_t) mid inFID: (uint64_t) fid withFlags: (uint8_t) flags { NSString *childURL, *childKey; NSNumber *fidKey; MAPIStoreFolder *folder; MAPIStoreMessage *message; NSArray *activeTables; NSUInteger count, max; struct mapistore_object_notification_parameters *notif_parameters; int rc; [self logWithFormat: @"-deleteMessageWithMID: mid: 0x%.16x flags: %d", mid, flags]; childURL = [mapping urlFromID: mid]; if (childURL) { [self logWithFormat: @"-deleteMessageWithMID: url (%@) found for object", childURL]; childKey = [self extractChildNameFromURL: childURL andFolderURLAt: NULL]; fidKey = [NSNumber numberWithUnsignedLongLong: fid]; folder = [folders objectForKey: fidKey]; message = [folder lookupChild: childKey]; if (message) { /* we ensure the table caches are loaded so that old and new state can be compared */ /* we ensure the table caches are loaded so that old and new state can be compared */ activeTables = ([message isKindOfClass: MAPIStoreFAIMessageK] ? [folder activeFAIMessageTables] : [folder activeMessageTables]); max = [activeTables count]; for (count = 0; count < max; count++) [[activeTables objectAtIndex: count] restrictedChildKeys]; if ([[message sogoObject] delete]) { rc = MAPISTORE_ERROR; [self logWithFormat: @"ERROR deleting object at URL: %@", childURL]; } else { if (![message isNew]) { /* folder notification */ notif_parameters = talloc_zero(memCtx, struct mapistore_object_notification_parameters); notif_parameters->object_id = fid; notif_parameters->tag_count = 5; notif_parameters->tags = talloc_array (notif_parameters, enum MAPITAGS, 5); notif_parameters->tags[0] = PR_CONTENT_COUNT; notif_parameters->tags[1] = PR_DELETED_COUNT_TOTAL; notif_parameters->tags[2] = PR_MESSAGE_SIZE; notif_parameters->tags[3] = PR_NORMAL_MESSAGE_SIZE; notif_parameters->tags[4] = PR_DELETED_MSG_COUNT; notif_parameters->new_message_count = true; notif_parameters->message_count = [[folder messageKeys] count] - 1; mapistore_push_notification (connInfo->mstore_ctx, MAPISTORE_FOLDER, MAPISTORE_OBJECT_MODIFIED, notif_parameters); talloc_free(notif_parameters); /* message notification */ notif_parameters = talloc_zero(memCtx, struct mapistore_object_notification_parameters); notif_parameters->object_id = mid; notif_parameters->folder_id = fid; /* Exchange sends a fnevObjectCreated!! */ mapistore_push_notification (connInfo->mstore_ctx, MAPISTORE_MESSAGE, MAPISTORE_OBJECT_CREATED, notif_parameters); talloc_free(notif_parameters); /* table notification */ for (count = 0; count < max; count++) [[activeTables objectAtIndex: count] notifyChangesForChild: message]; } [self logWithFormat: @"successfully deleted object at URL: %@", childURL]; [mapping unregisterURLWithID: mid]; [folder cleanupCaches]; rc = MAPISTORE_SUCCESS; } } else rc = MAPI_E_INVALID_OBJECT; } else rc = MAPISTORE_ERR_NOT_FOUND; return rc; } - (int) releaseRecordWithFMID: (uint64_t) fmid ofTableType: (uint8_t) tableType { NSNumber *fmidKey; MAPIStoreObject *child; NSUInteger retainCount; NSMutableDictionary *childCache; int rc = MAPISTORE_SUCCESS; switch (tableType) { case MAPISTORE_MESSAGE_TABLE: childCache = messages; break; case MAPISTORE_FOLDER_TABLE: childCache = folders; break; default: [self errorWithFormat: @"%s: value of tableType not handled: %d", __FUNCTION__, tableType]; [self logWithFormat: @" fmid: 0x%.16x tableType: %d", fmid, tableType]; rc = MAPISTORE_ERR_INVALID_PARAMETER; } if (rc == MAPISTORE_SUCCESS) { fmidKey = [NSNumber numberWithUnsignedLongLong: fmid]; child = [childCache objectForKey: fmidKey]; if (child) { retainCount = [child mapiRetainCount]; if (retainCount == 0) { [self logWithFormat: @"child with mid %.16x successfully removed" @" from child cache", fmid]; [childCache removeObjectForKey: fmidKey]; } else [child setMAPIRetainCount: retainCount - 1]; } else { [self warnWithFormat: @"child with mid %.16x not found" @" in child cache", fmid]; rc = MAPISTORE_ERR_NOT_FOUND; } } return rc; } /* utils */ - (NSString *) extractChildNameFromURL: (NSString *) objectURL andFolderURLAt: (NSString **) folderURL; { NSString *childKey; NSRange lastSlash; NSUInteger slashPtr; if ([objectURL hasSuffix: @"/"]) objectURL = [objectURL substringToIndex: [objectURL length] - 2]; lastSlash = [objectURL rangeOfString: @"/" options: NSBackwardsSearch]; if (lastSlash.location != NSNotFound) { slashPtr = NSMaxRange (lastSlash); childKey = [objectURL substringFromIndex: slashPtr]; if ([childKey length] == 0) childKey = nil; if (folderURL) *folderURL = [objectURL substringToIndex: slashPtr]; } else childKey = nil; return childKey; } - (uint64_t) idForObjectWithKey: (NSString *) key inFolderURL: (NSString *) folderURL { NSString *childURL; uint64_t mappingId; uint32_t contextId; if (key) childURL = [NSString stringWithFormat: @"%@%@", folderURL, key]; else childURL = folderURL; mappingId = [mapping idFromURL: childURL]; if (mappingId == NSNotFound) { [self warnWithFormat: @"no id exist yet, requesting one..."]; openchangedb_get_new_folderID (connInfo->oc_ctx, &mappingId); [mapping registerURL: childURL withID: mappingId]; contextId = 0; mapistore_search_context_by_uri (memCtx, [folderURL UTF8String] + 7, &contextId); mapistore_indexing_record_add_mid (memCtx, contextId, mappingId); } return mappingId; } /* proof of concept */ - (int) getTable: (void **) tablePtr andRowCount: (uint32_t *) countPtr withFID: (uint64_t) fid tableType: (uint8_t) tableType andHandleId: (uint32_t) handleId { MAPIStoreTable *table; table = [self _tableForFID: fid andTableType: tableType]; [table retain]; [table setHandleId: handleId]; *countPtr = [[table childKeys] count]; *tablePtr = table; return MAPISTORE_SUCCESS; } - (int) getAttachmentTable: (void **) tablePtr andRowCount: (uint32_t *) count withMID: (uint64_t) mid { MAPIStoreAttachmentTable *attTable; MAPIStoreMessage *message; NSNumber *midKey; int rc; rc = MAPISTORE_ERR_NOT_FOUND; midKey = [NSNumber numberWithUnsignedLongLong: mid]; message = [messages objectForKey: midKey]; if (message) { *count = [[message childKeysMatchingQualifier: nil andSortOrderings: nil] count]; attTable = [message attachmentTable]; if (attTable) { [attTable retain]; *tablePtr = attTable; rc = MAPISTORE_SUCCESS; } } return rc; } - (int) getAttachment: (void **) attachmentPtr withAID: (uint32_t) aid inMID: (uint64_t) mid { MAPIStoreMessage *message; MAPIStoreAttachment *attachment; NSNumber *midKey; NSArray *keys; int rc; rc = MAPISTORE_ERR_NOT_FOUND; midKey = [NSNumber numberWithUnsignedLongLong: mid]; message = [messages objectForKey: midKey]; if (message) { keys = [message childKeysMatchingQualifier: nil andSortOrderings: nil]; if (aid < [keys count]) { attachment = [message lookupChild: [keys objectAtIndex: aid]]; if (attachment) { [attachment retain]; *attachmentPtr = attachment; rc = MAPISTORE_SUCCESS; } } } return rc; } - (int) createAttachment: (void **) attachmentPtr inAID: (uint32_t *) aid withMessage: (uint64_t) mid { MAPIStoreMessage *message; MAPIStoreAttachment *attachment; NSNumber *midKey; int rc; rc = MAPISTORE_ERR_NOT_FOUND; midKey = [NSNumber numberWithUnsignedLongLong: mid]; message = [messages objectForKey: midKey]; if (message) { attachment = [message createAttachment]; if (attachment) { [attachment retain]; *attachmentPtr = attachment; *aid = [attachment AID]; rc = MAPISTORE_SUCCESS; } } return rc; } /* subclasses */ + (NSString *) MAPIModuleName { [self subclassResponsibility: _cmd]; return nil; } - (void) setupBaseFolder: (NSURL *) newURL { [self subclassResponsibility: _cmd]; } @end