/* MAPIStoreFolder.m - this file is part of SOGo
 *
 * Copyright (C) 2011-2014 Inverse inc
 *
 * 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.
 */

/* TODO: main key arrays must be initialized */

#import <Foundation/NSArray.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSData.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSException.h>
#import <Foundation/NSString.h>
#import <Foundation/NSURL.h>
#import <NGExtensions/NSObject+Logs.h>
#import <SOGo/SOGoContentObject.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoFolder.h>

#import "MAPIStoreActiveTables.h"
#import "MAPIStoreContext.h"
#import "MAPIStoreFAIMessage.h"
#import "MAPIStoreFAIMessageTable.h"
#import "MAPIStoreFolder.h"
#import "MAPIStoreFolderTable.h"
#import "MAPIStoreMapping.h"
#import "MAPIStoreMessage.h"
#import "MAPIStorePermissionsTable.h"
#import "MAPIStoreSamDBUtils.h"
#import "MAPIStoreTypes.h"
#import "MAPIStoreUserContext.h"
#import "NSData+MAPIStore.h"
#import "NSDate+MAPIStore.h"
#import "NSString+MAPIStore.h"
#import "NSObject+MAPIStore.h"
#import <SOGo/SOGoCacheGCSFolder.h>
#import "SOGoMAPIDBMessage.h"
#import "SOGoCacheGCSObject+MAPIStore.h"

#include <gen_ndr/exchange.h>

#undef DEBUG
#include <util/attr.h>
#include <libmapiproxy.h>
#include <mapistore/mapistore.h>
#include <mapistore/mapistore_errors.h>

Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMessageTableK, MAPIStoreFolderTableK;

@implementation MAPIStoreFolder

+ (void) initialize
{
  NSExceptionK = [NSException class];
  MAPIStoreFAIMessageK = [MAPIStoreFAIMessage class];
  MAPIStoreMessageTableK = [MAPIStoreMessageTable class];
  MAPIStoreFAIMessageTableK = [MAPIStoreFAIMessageTable class];
  MAPIStoreFolderTableK = [MAPIStoreFolderTable class];
}

- (id) init
{
  if ((self = [super init]))
    {
      // messageKeys = nil;
      // faiMessageKeys = nil;
      // folderKeys = nil;
      dbFolder = nil;
      context = nil;

      // propsFolder = nil;
      // propsMessage = nil;
    }

  return self;
}

- (void) setupAuxiliaryObjects
{
  NSURL *folderURL;
  NSMutableString *pathPrefix;
  NSString *path, *folderName;
  NSArray *parts;
  NSUInteger lastPartIdx;
  MAPIStoreUserContext *userContext;
  /* We create this local variable so gcc knows it can't be changed in the
     methods we call. This way we don't get a "may be used uninitialized in this
     function" warning. */
  BOOL pathBool;

  folderURL = [NSURL URLWithString: [self url]];
  /* note: -[NSURL path] returns an unescaped representation */
  path = [folderURL path];
  path = [path substringFromIndex: 1];
  pathBool = [path length] > 0;
  if (pathBool)
    {
      parts = [path componentsSeparatedByString: @"/"];
      lastPartIdx = [parts count] - 1;
      if ([path hasSuffix: @"/"])
        lastPartIdx--;
      folderName = [parts objectAtIndex: lastPartIdx];
    }
  else
    folderName = [folderURL host];

  userContext = [self userContext];
  [userContext ensureFolderTableExists];

  ASSIGN (dbFolder,
          [SOGoCacheGCSFolder objectWithName: folderName
                               inContainer: [container dbFolder]]);
  [dbFolder setTableUrl: [userContext folderTableURL]];
  if (!container && pathBool)
    {
      pathPrefix = [NSMutableString stringWithCapacity: 64];
      [pathPrefix appendFormat: @"/%@", [folderURL host]];
      parts = [parts subarrayWithRange: NSMakeRange (0, lastPartIdx)];
      if ([parts count] > 0)
        [pathPrefix appendFormat: @"/%@", [parts componentsJoinedByString: @"/"]];
      [dbFolder setPathPrefix: pathPrefix];
    }
  [dbFolder reloadIfNeeded];

  /* propsMessage and self share the same properties dictionary */
  // ASSIGN (propsMessage,
  //         [SOGoMAPIDBMessage objectWithName: @"properties.plist"
  //                               inContainer: dbFolder]);
  // [propsMessage setObjectType: MAPIInternalCacheObject];
  // [propsMessage reloadIfNeeded];
  [properties release];
  properties = [dbFolder properties];
  [properties retain];

  [self setupVersionsMessage];
}

- (id) initWithSOGoObject: (id) newSOGoObject
              inContainer: (MAPIStoreObject *) newContainer
{
  /* The instantiation of auxiliary folders is postponed when newContainer is
     nil since there is no way to deduce the parent url. When setContext: is
     invoked, it becomes possible again. */
  if ((self = [super initWithSOGoObject: newSOGoObject
                            inContainer: newContainer])
      && newContainer)
    {
      [self setupAuxiliaryObjects];
    }

  return self;
}

- (void) setContext: (MAPIStoreContext *) newContext
{
  ASSIGN (context, newContext);
  if (newContext)
    [self setupAuxiliaryObjects];
}

- (MAPIStoreContext *) context
{
  if (!context)
    [self setContext: (MAPIStoreContext *) [container context]];

  return context;
}

- (void) dealloc
{
  //[self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__];

  // [messageKeys release];
  // [faiMessageKeys release];
  // [folderKeys release];
  // [propsMessage release];
  [dbFolder release];
  [context release];

  [super dealloc];
}

- (SOGoCacheGCSFolder *) dbFolder
{
  return dbFolder;
}

/* backend interface */

// - (SOGoMAPIDBMessage *) propertiesMessage
// {
//   return propsMessage;
// }

- (uint64_t) objectVersion
{
  NSNumber *value;
  uint64_t cn;

  value = [properties objectForKey: MAPIPropertyKey (PidTagChangeNumber)];
  if (value)
    cn = [value unsignedLongLongValue];
  else
    {
      [self logWithFormat: @"no value for PidTagChangeNumber, adding one now"];
      cn = [[self context] getNewChangeNumber];
      value = [NSNumber numberWithUnsignedLongLong: cn];

      [properties setObject: value
                     forKey: MAPIPropertyKey (PidTagChangeNumber)];
      [dbFolder save];
    }

  return cn >> 16;
}

- (id) lookupFolder: (NSString *) folderKey
{
  MAPIStoreFolder *childFolder = nil;
  SOGoFolder *sogoFolder;
  WOContext *woContext;

  if ([[self folderKeys] containsObject: folderKey])
    {
      woContext = [[self userContext] woContext];
      sogoFolder = [sogoObject lookupName: folderKey inContext: woContext
                                  acquire: NO];
      if (sogoFolder && ![sogoFolder isKindOfClass: NSExceptionK])
        {
          [sogoFolder setContext: woContext];
          childFolder = [isa mapiStoreObjectWithSOGoObject: sogoFolder
                                               inContainer: self];
        }
    }

  return childFolder;
}

- (id) lookupFolderByURL: (NSString *) childURL
{
  MAPIStoreObject *foundObject;
  NSString *key, *slashLessURL;

  if ([childURL hasSuffix: @"/"])
    slashLessURL = [childURL substringToIndex: [childURL length] - 1];
  else
    slashLessURL = childURL;
  key = [self childKeyFromURL: slashLessURL];
  if (key)
    foundObject = [self lookupFolder: key];
  else
    foundObject = nil;

  return foundObject;
}

- (id) lookupMessage: (NSString *) messageKey
{
  MAPIStoreObject *childMessage = nil;
  Class messageClass;
  SOGoObject *msgObject;

  if (messageKey)
    {
      msgObject = [sogoObject lookupName: messageKey
                               inContext: nil
                                 acquire: NO];
      if (msgObject && ![msgObject isKindOfClass: NSExceptionK])
        {
          [msgObject setContext: [[self userContext] woContext]];
          messageClass = [msgObject mapistoreMessageClass];
          childMessage
            = [messageClass mapiStoreObjectWithSOGoObject: msgObject
                                              inContainer: self];
        }
    }

  return childMessage;
}

- (id) lookupFAIMessage: (NSString *) messageKey
{
  MAPIStoreObject *childMessage = nil;
  SOGoObject *msgObject;

  if (messageKey)
    {
      if ([[self faiMessageKeys] containsObject: messageKey])
        {
          msgObject = [dbFolder lookupName: messageKey
                                 inContext: nil
                                   acquire: NO];
          childMessage
            = [MAPIStoreFAIMessageK mapiStoreObjectWithSOGoObject: msgObject
                                                      inContainer: self];
        }
    }

  return childMessage;
}

- (NSString *) childKeyFromURL: (NSString *) childURL
{
  NSString *baseURL, *subURL, *key = nil;
  NSArray *parts;
  NSUInteger partsCount;

  baseURL = [self url];
  if (![baseURL hasSuffix: @"/"])
    baseURL = [NSString stringWithFormat: @"%@/", baseURL];
  if ([childURL hasPrefix: baseURL])
    {
      subURL = [childURL substringFromIndex: [baseURL length]];
      if ([subURL length] > 0)
        {
          parts = [subURL componentsSeparatedByString: @"/"];
          partsCount = [parts count];
          if (partsCount == 1)
            {
              key = [[parts objectAtIndex: 0]
                      stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
            }
        }
    }

  return key;
}

- (id) lookupMessageByURL: (NSString *) childURL
{
  MAPIStoreObject *foundObject;
  NSString *key;

  key = [self childKeyFromURL: childURL];
  if (key)
    {
      foundObject = [self lookupFAIMessage: key];
      if (!foundObject)
        foundObject = [self lookupMessage: key];
    }
  else
    foundObject = nil;

  return foundObject;
}

- (int) openFolder: (MAPIStoreFolder **) childFolderPtr
           withFID: (uint64_t) fid
{
  int rc = MAPISTORE_ERR_NOT_FOUND;
  MAPIStoreFolder *childFolder;
  MAPIStoreMapping *mapping;
  NSString *childURL;

  //[self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__];

  mapping = [self mapping];
  childURL = [mapping urlFromID: fid];
  if (childURL)
    {
      childFolder = [self lookupFolderByURL: childURL];
      if (childFolder)
        {
          *childFolderPtr = childFolder;
          rc = MAPISTORE_SUCCESS;
        }
    }

  return rc;
}

- (int) createFolder: (MAPIStoreFolder **) childFolderPtr
             withRow: (struct SRow *) aRow
              andFID: (uint64_t) fid
{
  int rc = MAPISTORE_SUCCESS;
  MAPIStoreMapping *mapping;
  NSString *baseURL, *childURL, *folderKey;
  MAPIStoreFolder *childFolder;
  SOGoUser *ownerUser;

  //[self logWithFormat: @"METHOD '%s' (%d)", __FUNCTION__, __LINE__];

  ownerUser = [[self userContext] sogoUser];
  if ([[context activeUser] isEqual: ownerUser]
      || [self subscriberCanCreateSubFolders])
    {
      mapping = [self mapping];
      childURL = [mapping urlFromID: fid];
      if (childURL)
        rc = MAPISTORE_ERR_EXIST;
      else
        {
          rc = [self createFolder: aRow withFID: fid andKey: &folderKey];
          if (rc == MAPISTORE_SUCCESS)
            {
              [self cleanupCaches];
              baseURL = [self url];
              if (![baseURL hasSuffix: @"/"])
                baseURL = [NSString stringWithFormat: @"%@/", baseURL];
              childURL = [NSString stringWithFormat: @"%@%@/",
                                   baseURL,
                                   [folderKey stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
              [mapping registerURL: childURL withID: fid];
              childFolder = [self lookupFolder: folderKey];
              if (childFolder)
                {
                  [childFolder addPropertiesFromRow: aRow];
                  *childFolderPtr = childFolder;
                }
              else
                [NSException raise: @"MAPIStoreIOException"
                            format: @"unable to fetch created folder"];
            }
        }
    }
  else
    rc = MAPISTORE_ERR_DENIED;

  return rc;
}

- (void) deleteFolderImpl
{
  // TODO: raise exception in case underlying delete fails?
  // [propsMessage delete];
  [dbFolder delete];
}

- (int) deleteFolder
{
  [self deleteFolderImpl];

  [self cleanupCaches];

  return MAPISTORE_SUCCESS;
}

- (int) getChildCount: (uint32_t *) rowCount
          ofTableType: (enum mapistore_table_type) tableType
{
  NSArray *keys;
  int rc = MAPISTORE_SUCCESS;

  //[self logWithFormat: @"METHOD '%s' (%d) -- tableType: %d",
	//__FUNCTION__, __LINE__, tableType];

  if (tableType == MAPISTORE_MESSAGE_TABLE)
    keys = [self messageKeys];
  else if (tableType == MAPISTORE_FOLDER_TABLE)
    keys = [self folderKeys];
  else if (tableType == MAPISTORE_FAI_TABLE)
    keys = [self faiMessageKeys];
  else
    {
      keys = nil;
      rc = MAPISTORE_ERR_NOT_FOUND;
    }
  *rowCount = [keys count];

  return rc;
}

- (int) openMessage: (MAPIStoreMessage **) messagePtr
            withMID: (uint64_t) mid
         forWriting: (BOOL) readWrite
           inMemCtx: (TALLOC_CTX *) memCtx;
{
  NSString *messageURL;
  MAPIStoreMapping *mapping;
  MAPIStoreMessage *message;
  SOGoUser *ownerUser;
  int rc = MAPISTORE_ERR_NOT_FOUND;

  mapping = [self mapping];
  messageURL = [mapping urlFromID: mid];
  if (messageURL)
    {
      message = [self lookupMessageByURL: messageURL];
      if (message)
        {
          ownerUser = [[self userContext] sogoUser];
          if ([[context activeUser] isEqual: ownerUser]
              || (readWrite && [message subscriberCanModifyMessage])
              || (!readWrite && [message subscriberCanReadMessage]))
            {
              *messagePtr = message;
              rc = MAPISTORE_SUCCESS;
            }
          else
            rc = MAPISTORE_ERR_DENIED;
        }
    }

  return rc;
}

- (int) createMessage: (MAPIStoreMessage **) messagePtr
              withMID: (uint64_t) mid
         isAssociated: (BOOL) isAssociated
{
  enum mapistore_error rc;
  MAPIStoreMessage *message;
  NSString *baseURL, *childURL;
  MAPIStoreMapping *mapping;
  SOGoUser *ownerUser;

  //[self logWithFormat: @"METHOD '%s' -- mid: 0x%.16llx  associated: %d",
  //	__FUNCTION__, mid, isAssociated];

  context = [self context];
  ownerUser = [[self userContext] sogoUser];

  if ([[context activeUser] isEqual: ownerUser]
      || (!isAssociated && [self subscriberCanCreateMessages]))
    {
      mapping = [self mapping];
      if ([mapping urlFromID: mid])
        rc = MAPISTORE_ERR_EXIST;
      else
        {
          message = [self createMessage: isAssociated];
          if (message)
            {
              baseURL = [self url];
              if (![baseURL hasSuffix: @"/"])
                baseURL = [NSString stringWithFormat: @"%@/", baseURL];
              childURL = [NSString stringWithFormat: @"%@%@",
                                   baseURL, [message nameInContainer]];
              [mapping registerURL: childURL withID: mid];
              *messagePtr = message;
              rc = MAPISTORE_SUCCESS;
            }
          else
            rc = MAPISTORE_ERROR;
        }
    }
  else
    rc = MAPISTORE_ERR_DENIED;

  return rc;
}

- (int) deleteMessageWithMID: (uint64_t) mid
                    andFlags: (uint8_t) flags
{
  NSString *childURL;
  MAPIStoreMapping *mapping;
  MAPIStoreMessage *message;
  NSArray *activeTables;
  NSUInteger count, max;
  id msgObject;
  SOGoUser *ownerUser;
  struct mapistore_connection_info *connInfo;
  struct mapistore_object_notification_parameters *notif_parameters;
  int rc;

  [self logWithFormat: @"-deleteMessageWithMID: mid: 0x%.16llx  flags: %d", mid, flags];
  
  mapping = [self mapping];
  childURL = [mapping urlFromID: mid];
  if (childURL)
    {
      message = [self lookupMessageByURL: childURL];
      if (message)
        {
          ownerUser = [[self userContext] sogoUser];

          if ([[context activeUser] isEqual: ownerUser]
              || (![message isKindOfClass: MAPIStoreFAIMessageK]
                  && [self subscriberCanDeleteMessages]))
            {
              /* we ensure the table caches are loaded so that old and new state
                 can be compared */
              activeTables = ([message isKindOfClass: MAPIStoreFAIMessageK]
                              ? [self activeFAIMessageTables]
                              : [self activeMessageTables]);
              max = [activeTables count];
              for (count = 0; count < max; count++)
                [[activeTables objectAtIndex: count] restrictedChildKeys];

              msgObject = [message sogoObject];
              if (([msgObject respondsToSelector: @selector (prepareDelete)]
                   && [msgObject prepareDelete])
                  || [msgObject delete])
                {
                  rc = MAPISTORE_ERROR;
                  [self logWithFormat: @"ERROR deleting object at URL: %@", childURL];
                }
              else
                {
                  if (![message isNew])
                    {
                      /* folder notification */
                      notif_parameters
                        = talloc_zero(NULL,
                                      struct mapistore_object_notification_parameters);
                      notif_parameters->object_id = [self objectId];
                      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 = [[self messageKeys]
                                                          count] - 1;
                      connInfo = [[self context] connectionInfo];
                      mapistore_push_notification (connInfo->mstore_ctx,
                                                   MAPISTORE_FOLDER,
                                                   MAPISTORE_OBJECT_MODIFIED,
                                                   notif_parameters);
                      talloc_free(notif_parameters);

                      /* message notification */
                      notif_parameters
                        = talloc_zero(NULL,
                                      struct mapistore_object_notification_parameters);
                      notif_parameters->object_id = mid;
                      notif_parameters->folder_id = [self objectId];
                      /* 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];
                  [self cleanupCaches];
                  rc = MAPISTORE_SUCCESS;
                }
            }
          else
            rc = MAPISTORE_ERR_DENIED;
        }
      else
        rc = MAPISTORE_ERR_NOT_FOUND;
    }
  else
    rc = MAPISTORE_ERR_NOT_FOUND;

  return rc;
}

// private method
- (int) _moveCopyMessageWithMID: (uint64_t) srcMid
                     fromFolder: (MAPIStoreFolder *) sourceFolder
                        withMID: (uint64_t) targetMid
                   andChangeKey: (struct Binary_r *) targetChangeKey
                       wantCopy: (uint8_t) wantCopy
                       inMemCtx: (TALLOC_CTX *) memCtx
{
  int rc;
  MAPIStoreMessage *sourceMsg, *destMsg;
  //TALLOC_CTX *memCtx;
  struct SRow aRow;
  struct SPropValue property;

  [self logWithFormat: @"-moveCopyMessageWithMID: 0x%.16llx .. withMID: 0x%.16llx .. wantCopy: %d", srcMid, targetMid, wantCopy];

  //memCtx = talloc_zero (NULL, TALLOC_CTX);
  rc = [sourceFolder openMessage: &sourceMsg
                         withMID: srcMid
                      forWriting: NO
                        inMemCtx: memCtx];
  if (rc != MAPISTORE_SUCCESS)
    goto end;

  rc = [self createMessage: &destMsg withMID: targetMid
              isAssociated: [sourceMsg isKindOfClass: MAPIStoreFAIMessageK]];
  if (rc != MAPISTORE_SUCCESS)
    goto end;

  [sourceMsg copyToMessage: destMsg  inMemCtx: memCtx];

  if (targetChangeKey)
    {
      property.ulPropTag = PidTagChangeKey;
      property.value.bin = *targetChangeKey;
      aRow.cValues = 1;
      aRow.lpProps = &property;
      rc = [destMsg addPropertiesFromRow: &aRow];
      if (rc != MAPISTORE_SUCCESS)
        goto end;
    }
  [destMsg save: memCtx];
  if (!wantCopy)
    rc = [sourceFolder deleteMessageWithMID: srcMid andFlags: 0];

 end:
  //talloc_free (memCtx);

  return rc;
}

- (int) moveCopyMessagesWithMIDs: (uint64_t *) srcMids
                        andCount: (uint32_t) midCount
                      fromFolder: (MAPIStoreFolder *) sourceFolder
                        withMIDs: (uint64_t *) targetMids
                   andChangeKeys: (struct Binary_r **) targetChangeKeys
                        wantCopy: (uint8_t) wantCopy
                        inMemCtx: (TALLOC_CTX *) memCtx
{
  int rc = MAPISTORE_SUCCESS;
  NSUInteger count;
  NSMutableArray *oldMessageURLs;
  NSString *oldMessageURL;
  MAPIStoreMapping *mapping;
  SOGoUser *ownerUser;
  struct Binary_r *targetChangeKey;
  //TALLOC_CTX *memCtx;

  //memCtx = talloc_zero (NULL, TALLOC_CTX);

  ownerUser = [[self userContext] sogoUser];

  if (wantCopy || [[context activeUser] isEqual: ownerUser])
    {
      if ([sourceFolder isKindOfClass: isa]
          || [self isKindOfClass: [sourceFolder class]])
        [self logWithFormat: @"%s: this class could probably implement"
              @" a specialized/optimized version", __FUNCTION__];
      oldMessageURLs = [NSMutableArray arrayWithCapacity: midCount];
      mapping = [self mapping];
      for (count = 0; rc == MAPISTORE_SUCCESS && count < midCount; count++)
        {
          oldMessageURL = [mapping urlFromID: srcMids[count]];
          if (oldMessageURL)
            {
              [oldMessageURLs addObject: oldMessageURL];
              if (targetChangeKeys)
                targetChangeKey = targetChangeKeys[count];
              else
                targetChangeKey = NULL;
              rc = [self _moveCopyMessageWithMID: srcMids[count]
                                      fromFolder: sourceFolder
                                         withMID: targetMids[count]
                                    andChangeKey: targetChangeKey
                                        wantCopy: wantCopy
                                        inMemCtx: memCtx];
            }
          else
            rc = MAPISTORE_ERR_NOT_FOUND;
        }

      /* Notifications */
      if (rc == MAPISTORE_SUCCESS)
        {
          [self postNotificationsForMoveCopyMessagesWithMIDs: srcMids
                                              andMessageURLs: oldMessageURLs
                                                    andCount: midCount
                                                  fromFolder: sourceFolder
                                                    withMIDs: targetMids
                                                    wantCopy: wantCopy];

          // We cleanup cache of our source and destination folders
          [self cleanupCaches];
          [sourceFolder cleanupCaches];
        }
    }
  else
    rc = MAPISTORE_ERR_DENIED;
  
  //talloc_free (memCtx);
  
  return rc;
}

- (enum mapistore_error) moveCopyToFolder: (MAPIStoreFolder *) targetFolder
                              withNewName: (NSString *) newFolderName
                                   isMove: (BOOL) isMove
                              isRecursive: (BOOL) isRecursive
                                 inMemCtx: (TALLOC_CTX *) memCtx

{
  enum mapistore_error rc;
  NSAutoreleasePool *pool;
  struct SRow folderRow;
  struct SPropValue nameProperty;
  MAPIStoreFolder *subFolder, *newFolder;
  NSArray *children;
  MAPIStoreMapping *mapping;
  MAPIStoreMessage *message, *targetMessage;
  NSUInteger count, max;
  NSString *childKey;
  uint64_t fmid;
  //TALLOC_CTX *memCtx;

  //memCtx = talloc_zero (NULL, TALLOC_CTX);

  /* TODO: one possible issue with this algorithm is that moved messages will
     lack a version number and will all be assigned a new one, even though
     they have not changed. This also means that they will be transferred
     again to the client during a sync operation. */

  if ([targetFolder supportsSubFolders])
    {
      mapping = [self mapping];

      if (!newFolderName)
        newFolderName = [sogoObject displayName];
      nameProperty.ulPropTag = PidTagDisplayName;
      nameProperty.value.lpszW = [newFolderName UTF8String];
      folderRow.lpProps = &nameProperty;
      folderRow.cValues = 1;
      rc = [targetFolder createFolder: &folderRow
                              withFID: [self objectId]
                               andKey: &childKey];
      if (rc == MAPISTORE_SUCCESS)
        {
          newFolder = [targetFolder lookupFolder: childKey];
          [self copyPropertiesToObject: newFolder  inMemCtx: memCtx];

          pool = [NSAutoreleasePool new];
          children = [self messageKeys];
          max = [children count];
          for (count = 0; count < max; count++)
            {
              childKey = [children objectAtIndex: count];
              message = [self lookupMessage: childKey];
              targetMessage = [newFolder createMessage: NO];
              [targetMessage setIsNew: YES];
              [message copyToMessage: targetMessage  inMemCtx: memCtx];
              if (isMove)
                {
                  fmid = [mapping idFromURL: [message url]];
                  [self deleteMessageWithMID: fmid andFlags: 0];
                  [mapping registerURL: [targetMessage url]
                                withID: fmid];
                }
              [targetMessage save: memCtx];
            }
          [pool release];

          pool = [NSAutoreleasePool new];
          children = [self faiMessageKeys];
          max = [children count];
          for (count = 0; count < max; count++)
            {
              childKey = [children objectAtIndex: count];
              message = [self lookupFAIMessage: childKey];
              targetMessage = [newFolder createMessage: YES];
              [targetMessage setIsNew: YES];
              [message copyToMessage: targetMessage  inMemCtx: memCtx];
              if (isMove)
                {
                  fmid = [mapping idFromURL: [message url]];
                  [self deleteMessageWithMID: fmid andFlags: 0];
                  [mapping registerURL: [targetMessage url]
                                withID: fmid];
                }
              [targetMessage save: memCtx];
            }
          [pool release];

          if (isRecursive)
            {
              pool = [NSAutoreleasePool new];
              children = [self folderKeys];
              max = [children count];
              for (count = 0; count < max; count++)
                {
                  childKey = [children objectAtIndex: count];
                  subFolder = [self lookupFolder: childKey];
                  [subFolder moveCopyToFolder: newFolder withNewName: nil
                                       isMove: isMove
                                  isRecursive: isRecursive
                                     inMemCtx: memCtx];

                }
              [pool release];
            }

          if (isMove)
            {
              fmid = [mapping idFromURL: [self url]];
              [mapping unregisterURLWithID: fmid];
              [self deleteFolderImpl];
              [mapping registerURL: [newFolder url]
                            withID: fmid];
            }
          [targetFolder cleanupCaches];
        }
      [self cleanupCaches];
    }
  else
    rc = MAPISTORE_ERR_DENIED;

  //talloc_free (memCtx);

  return rc;
}

- (SOGoFolder *) aclFolder
{
  [self subclassResponsibility: _cmd];

  return nil;
}

- (void) _modifyPermissionEntryForUser: (NSString *) user
                             withRoles: (NSArray *) roles
                            isAddition: (BOOL) isAddition
                         withACLFolder: (SOGoFolder *) aclFolder
{
  if (user)
    {
      if (isAddition)
        [aclFolder addUserInAcls: user];
      [aclFolder setRoles: roles forUser: user];
    }
  else
    [self logWithFormat: @"user is nil, keeping intended entry intact"];
}

- (void) setupVersionsMessage
{
}

- (void) ensureIDsForChildKeys: (NSArray *) keys
{
  NSMutableArray *missingURLs;
  MAPIStoreMapping *mapping;
  NSUInteger count, max;
  NSString *baseURL, *URL, *key;
  NSArray *newIDs;
  uint64_t idNbr;
  
  baseURL = [self url];

  mapping = [self mapping];
  max = [keys count];
  missingURLs = [NSMutableArray arrayWithCapacity: max];
  for (count = 0; count < max; count++)
    {
      key = [keys objectAtIndex: count];
      URL = [NSString stringWithFormat: @"%@%@", baseURL, key];
      idNbr = [mapping idFromURL: URL];
      if (idNbr == NSNotFound)
        [missingURLs addObject: URL];
    }

  max = [missingURLs count];
  newIDs = [[self context] getNewFMIDs: max];
  [mapping registerURLs: missingURLs
                withIDs: newIDs];
}

- (void) postNotificationsForMoveCopyMessagesWithMIDs: (uint64_t *) srcMids
                                       andMessageURLs: (NSArray *) oldMessageURLs
                                             andCount: (uint32_t) midCount
                                           fromFolder: (MAPIStoreFolder *) sourceFolder
                                             withMIDs: (uint64_t *) targetMids
                                             wantCopy: (uint8_t) wantCopy
{
  NSArray *activeTables;
  NSUInteger count, tableCount, max;
  MAPIStoreMessage *message;
  NSString *messageURL;
  MAPIStoreMapping *mapping;
  struct mapistore_object_notification_parameters *notif_parameters;
  struct mapistore_connection_info *connInfo;

  connInfo = [[self context] connectionInfo];
  
  // For the "source folder, we ensure the table caches are loaded so
  // that old and new state can be compared
  activeTables = [sourceFolder activeMessageTables];
  max = [activeTables count];
  for (count = 0; count < max; count++)
    [[activeTables objectAtIndex: count] restrictedChildKeys];
 
  if (!wantCopy)
    {
      // We notify the client. We start with the source folder.
      notif_parameters = talloc_zero(NULL, struct mapistore_object_notification_parameters);
      notif_parameters->object_id = [sourceFolder objectId];
      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_RECIPIENT_ON_NORMAL_MSG_COUNT;
      notif_parameters->new_message_count = true;
      notif_parameters->message_count = [[sourceFolder messageKeys] count] - midCount;
      mapistore_push_notification (connInfo->mstore_ctx,
                                   MAPISTORE_FOLDER,
                                   MAPISTORE_OBJECT_MODIFIED,
                                   notif_parameters);
      talloc_free(notif_parameters);
    }

  // move/copy notification of the copied/moved message
  for (count = 0; count < midCount; count++)
    {
      notif_parameters = talloc_zero (NULL, struct mapistore_object_notification_parameters);
      notif_parameters->tag_count = 0;
      notif_parameters->new_message_count = true;
      notif_parameters->message_count = 0;
      notif_parameters->object_id = targetMids[count];
      notif_parameters->folder_id = [self objectId];
      notif_parameters->old_object_id = srcMids[count];
      notif_parameters->old_folder_id = [sourceFolder objectId];

      mapistore_push_notification (connInfo->mstore_ctx,
                                   MAPISTORE_MESSAGE,
                                   (wantCopy ? MAPISTORE_OBJECT_COPIED : MAPISTORE_OBJECT_MOVED),
                                   notif_parameters);
      talloc_free (notif_parameters);

      message = [sourceFolder lookupMessageByURL: [oldMessageURLs objectAtIndex: count]];
      // table notification 
      for (tableCount = 0; tableCount < max; tableCount++)
        [[activeTables objectAtIndex: tableCount]
          notifyChangesForChild: message];
    }

  // For the "destination folder, we ensure the table caches are loaded so
  // that old and new state can be compared
  activeTables = [self activeMessageTables];
  max = [activeTables count];
  for (count = 0; count < max; count++)
    [[activeTables objectAtIndex: count] restrictedChildKeys];

  notif_parameters = talloc_zero(NULL, struct mapistore_object_notification_parameters);
  notif_parameters->object_id = [self objectId];
  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_RECIPIENT_ON_NORMAL_MSG_COUNT;
  notif_parameters->new_message_count = true;
  notif_parameters->message_count = [[self messageKeys] count] + midCount;
  connInfo = [[self context] connectionInfo];
  mapistore_push_notification (connInfo->mstore_ctx,
                               MAPISTORE_FOLDER,
                               MAPISTORE_OBJECT_MODIFIED,
                               notif_parameters);
  talloc_free(notif_parameters);

  // table notification 
  mapping = [self mapping];
  for (count = 0; count < midCount; count++)
    {
      messageURL = [mapping urlFromID: targetMids[count]];
      message = [self lookupMessageByURL: messageURL];
      for (tableCount = 0; tableCount < max; tableCount++)
        [[activeTables objectAtIndex: tableCount]
          notifyChangesForChild: message];
    }
}

- (int) getDeletedFMIDs: (struct UI8Array_r **) fmidsPtr
                  andCN: (uint64_t *) cnPtr
       fromChangeNumber: (uint64_t) changeNum
            inTableType: (enum mapistore_table_type) tableType
               inMemCtx: (TALLOC_CTX *) memCtx
{
  int rc;
  NSString *baseURL, *format, *url;
  NSArray *keys;
  NSNumber *cnNbr;
  NSUInteger count, max;
  MAPIStoreMapping *mapping;
  struct UI8Array_r *fmids;
  uint64_t fmid;

  keys = [self getDeletedKeysFromChangeNumber: changeNum andCN: &cnNbr
                                  inTableType: tableType];
  if (keys)
    {
      mapping = [self mapping];

      max = [keys count];

      fmids = talloc_zero (memCtx, struct UI8Array_r);
      fmids->cValues = 0;
      fmids->lpui8 = talloc_array (fmids, uint64_t, max);
      *fmidsPtr = fmids;
      if (max > 0)
        *cnPtr = [cnNbr unsignedLongLongValue];

      baseURL = [self url];
      if ([baseURL hasSuffix: @"/"])
        format = @"%@%@";
      else
        format = @"%@/%@";

      for (count = 0; count < max; count++)
        {
          url = [NSString stringWithFormat: format,
                          baseURL, [keys objectAtIndex: count]];
          fmid = [mapping idFromURL: url];
          if (fmid != NSNotFound) /* if no fmid is returned, then the object
                                     "never existed" in the OpenChange
                                     databases */
            {
              fmids->lpui8[fmids->cValues] = fmid;
              fmids->cValues++;
            }
        }

      rc = MAPISTORE_SUCCESS;
    }
  else
    rc = MAPISTORE_ERR_NOT_FOUND;

  return rc;
}

- (int) getTable: (MAPIStoreTable **) tablePtr
     andRowCount: (uint32_t *) countPtr
       tableType: (enum mapistore_table_type) tableType
     andHandleId: (uint32_t) handleId
{
  int rc = MAPISTORE_SUCCESS;
  MAPIStoreTable *table;
  SOGoUser *ownerUser;

  if (tableType == MAPISTORE_MESSAGE_TABLE)
    table = [self messageTable];
  else if (tableType == MAPISTORE_FAI_TABLE)
    table = [self faiMessageTable];
  else if (tableType == MAPISTORE_FOLDER_TABLE)
    table = [self folderTable];
  else if (tableType == MAPISTORE_PERMISSIONS_TABLE)
    {
      ownerUser = [[self userContext] sogoUser];
      if ([[context activeUser] isEqual: ownerUser])
        table = [self permissionsTable];
      else
        rc = MAPISTORE_ERR_DENIED;
    }
  else
    {
      table = nil;
      [NSException raise: @"MAPIStoreIOException"
                  format: @"unsupported table type: %d", tableType];
    }

  if (rc == MAPISTORE_SUCCESS)
    {
      if (table)
        {
          [table setHandleId: handleId];
          *tablePtr = table;
          *countPtr = [[table childKeys] count];
        }
      else
        rc = MAPISTORE_ERR_NOT_FOUND;
    }

  return rc;
}

- (void) addProperties: (NSDictionary *) newProperties
{
  static enum MAPITAGS bannedProps[] = { PR_MID, PR_FID, PR_PARENT_FID,
                                         PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY,
                                         PR_CHANGE_KEY, 0x00000000 };
  enum MAPITAGS *currentProp;
  NSMutableDictionary *propsCopy;

  /* TODO: this should no longer be required once mapistore v2 API is in
     place, when we can then do this from -dealloc below */

  [dbFolder reloadIfNeeded];

  propsCopy = [newProperties mutableCopy];
  [propsCopy autorelease];

  currentProp = bannedProps;
  while (*currentProp)
    {
      [propsCopy removeObjectForKey: MAPIPropertyKey (*currentProp)];
      currentProp++;
    }

  [properties addEntriesFromDictionary: propsCopy];
  [dbFolder save];
}

- (NSArray *) messageKeys
{
  return [self messageKeysMatchingQualifier: nil
                           andSortOrderings: nil];
  // if (!messageKeys)
  //   {
  //     messageKeys = [self messageKeysMatchingQualifier: nil
  //                                     andSortOrderings: nil];
  //     [messageKeys retain];
  //   }

  // return messageKeys;
}

- (MAPIStoreFAIMessageTable *) faiMessageTable
{
  return [MAPIStoreFAIMessageTable tableForContainer: self];
}

- (NSArray *) faiMessageKeysMatchingQualifier: (EOQualifier *) qualifier
                             andSortOrderings: (NSArray *) sortOrderings
{
  return [dbFolder childKeysOfType: MAPIFAICacheObject
                    includeDeleted: NO
                 matchingQualifier: qualifier
                  andSortOrderings: sortOrderings];
}

- (NSArray *) faiMessageKeys
{
  return [self faiMessageKeysMatchingQualifier: nil
                              andSortOrderings: nil];
  // if (!faiMessageKeys)
  //   {
  //     faiMessageKeys = [self faiMessageKeysMatchingQualifier: nil
  //                                           andSortOrderings: nil];
  //     [faiMessageKeys retain];
  //   }

  // return faiMessageKeys;
}

- (MAPIStoreFolderTable *) folderTable
{
  return [MAPIStoreFolderTable tableForContainer: self];
}

- (NSArray *) folderKeys
{
  return [self folderKeysMatchingQualifier: nil
                          andSortOrderings: nil];
  // if (!folderKeys)
  //   {
  //     folderKeys = [self folderKeysMatchingQualifier: nil
  //                                   andSortOrderings: nil];
  //     [folderKeys retain];
  //   }

  // return folderKeys;
}

- (NSArray *) folderKeysMatchingQualifier: (EOQualifier *) qualifier
                         andSortOrderings: (NSArray *) sortOrderings
{
  if (qualifier)
    [self errorWithFormat: @"qualifier is not used for folders"];
  if (sortOrderings)
    [self errorWithFormat: @"sort orderings are not used for folders"];

  return [sogoObject toManyRelationshipKeys];
}

- (NSArray *) activeMessageTables
{
  return [[MAPIStoreActiveTables activeTables]
             activeTablesForFMID: [self objectId]
                         andType: MAPISTORE_MESSAGE_TABLE];
}

- (NSArray *) activeFAIMessageTables
{
  return [[MAPIStoreActiveTables activeTables]
             activeTablesForFMID: [self objectId]
                         andType: MAPISTORE_FAI_TABLE];
}

- (void) _cleanupTableCaches: (enum mapistore_table_type) tableType
{
  NSArray *tables;
  NSUInteger count, max;

  tables = [[MAPIStoreActiveTables activeTables]
               activeTablesForFMID: [self objectId]
                           andType: tableType];
  max = [tables count];
  for (count = 0; count < max; count++)
    [[tables objectAtIndex: count] cleanupCaches];
}

- (void) cleanupCaches
{
  [self _cleanupTableCaches: MAPISTORE_MESSAGE_TABLE];
  [self _cleanupTableCaches: MAPISTORE_FAI_TABLE];
  [self _cleanupTableCaches: MAPISTORE_FOLDER_TABLE];
  // [faiMessageKeys release];
  // faiMessageKeys = nil;
  // [messageKeys release];
  // messageKeys = nil;
  // [folderKeys release];
  // folderKeys = nil;
}

- (int) getPidTagParentFolderId: (void **) data
                       inMemCtx: (TALLOC_CTX *) memCtx
{
  *data = MAPILongLongValue (memCtx, [container objectId]);

  return MAPISTORE_SUCCESS;
}

- (int) getPidTagFolderId: (void **) data
                 inMemCtx: (TALLOC_CTX *) memCtx
{
  *data = MAPILongLongValue (memCtx, [self objectId]);

  return MAPISTORE_SUCCESS;
}

/*
  Possible values are:
  
  0x00000001 Modify
  0x00000002 Read
  0x00000004 Delete
  0x00000008 Create Hierarchy Table
  0x00000010 Create Contents Table
  0x00000020 Create Associated Contents Table
*/
- (int) getPidTagAccess: (void **) data
               inMemCtx: (TALLOC_CTX *) memCtx
{
  uint32_t access = 0;
  SOGoUser *ownerUser;
  BOOL userIsOwner;

  ownerUser = [[self userContext] sogoUser];

  userIsOwner = [[context activeUser] isEqual: ownerUser];
  if (userIsOwner || [self subscriberCanModifyMessages])
    access |= 0x01;
  if (userIsOwner || [self subscriberCanReadMessages])
    access |= 0x02;
  if (userIsOwner || [self subscriberCanDeleteMessages])
    access |= 0x04;
  if ((userIsOwner || [self subscriberCanCreateSubFolders])
      && [self supportsSubFolders])
    access |= 0x08;
  if (userIsOwner || [self subscriberCanCreateMessages])
    access |= 0x10;
  if (userIsOwner)
    access |= 0x20;
  
  *data = MAPILongValue (memCtx, access);

  return MAPISTORE_SUCCESS;
}

- (int) getPidTagRights: (void **) data
               inMemCtx: (TALLOC_CTX *) memCtx
{
  uint32_t rights = 0;
  SOGoUser *ownerUser;
  BOOL userIsOwner;

  ownerUser = [[self userContext] sogoUser];

  userIsOwner = [[context activeUser] isEqual: ownerUser];
  if (userIsOwner || [self subscriberCanReadMessages])
    rights |= RightsReadItems;
  if (userIsOwner || [self subscriberCanCreateMessages])
    rights |= RightsCreateItems;
  if (userIsOwner || [self subscriberCanModifyMessages])
    rights |= RightsEditOwn | RightsEditAll;
  if (userIsOwner || [self subscriberCanDeleteMessages])
    rights |= RightsDeleteOwn | RightsDeleteAll;
  if ((userIsOwner || [self subscriberCanCreateSubFolders])
      && [self supportsSubFolders])
    rights |= RightsCreateSubfolders;
  if (userIsOwner)
    rights |= RightsFolderOwner | RightsFolderContact;
  
  *data = MAPILongValue (memCtx, rights);

  return MAPISTORE_SUCCESS;
}

- (int) getPidTagAccessControlListData: (void **) data
                              inMemCtx: (TALLOC_CTX *) memCtx
{
  *data = [[NSData data] asBinaryInMemCtx: memCtx];

  return MAPISTORE_SUCCESS;
}

- (int) getPidTagAttributeHidden: (void **) data
                        inMemCtx: (TALLOC_CTX *) memCtx
{
  return [self getNo: data inMemCtx: memCtx];
}

- (int) getPidTagAttributeSystem: (void **) data
                        inMemCtx: (TALLOC_CTX *) memCtx
{
  return [self getNo: data inMemCtx: memCtx];
}

- (int) getPidTagAttributeReadOnly: (void **) data
                          inMemCtx: (TALLOC_CTX *) memCtx
{
  return [self getNo: data inMemCtx: memCtx];
}

- (int) getPidTagSubfolders: (void **) data
                   inMemCtx: (TALLOC_CTX *) memCtx
{
  *data = MAPIBoolValue (memCtx, [self supportsSubFolders] && [[self folderKeys] count] > 0);
  
  return MAPISTORE_SUCCESS;
}

- (int) getPidTagFolderChildCount: (void **) data
                         inMemCtx: (TALLOC_CTX *) memCtx
{
  *data = MAPILongValue (memCtx, [[self folderKeys] count]);
  
  return MAPISTORE_SUCCESS;
}

- (int) getPidTagContentCount: (void **) data
                     inMemCtx: (TALLOC_CTX *) memCtx
{
  *data = MAPILongValue (memCtx, [[self messageKeys] count]);

  return MAPISTORE_SUCCESS;
}

- (int) getPidTagContentUnreadCount: (void **) data
                           inMemCtx: (TALLOC_CTX *) memCtx
{
  *data = MAPILongValue (memCtx, 0);

  return MAPISTORE_SUCCESS;
}

- (int) getPidTagAssociatedContentCount: (void **) data
                               inMemCtx: (TALLOC_CTX *) memCtx
{
  *data = MAPILongValue (memCtx, [[self faiMessageKeys] count]);

  return MAPISTORE_SUCCESS;
}

- (int) getPidTagDeletedCountTotal: (void **) data
                          inMemCtx: (TALLOC_CTX *) memCtx
{
  /* TODO */
  *data = MAPILongValue (memCtx, 0);

  return MAPISTORE_SUCCESS;
}

- (int) getPidTagLocalCommitTimeMax: (void **) data
                           inMemCtx: (TALLOC_CTX *) memCtx
{
  int rc = MAPISTORE_SUCCESS;
  NSDate *date;

  date = [self lastMessageModificationTime];
  if (date)
    *data = [date asFileTimeInMemCtx: memCtx];
  else
    rc = MAPISTORE_ERR_NOT_FOUND;

  return rc;
}

- (int) getPidTagDefaultPostMessageClass: (void **) data
                                inMemCtx: (TALLOC_CTX *) memCtx
{
  *data = [@"IPM.Note" asUnicodeInMemCtx: memCtx];

  return MAPISTORE_SUCCESS;
}

- (int) getProperties: (struct mapistore_property_data *) data
             withTags: (enum MAPITAGS *) tags
             andCount: (uint16_t) columnCount
             inMemCtx: (TALLOC_CTX *) memCtx
{
  [dbFolder reloadIfNeeded];

  return [super getProperties: data
                     withTags: tags
                     andCount: columnCount
                     inMemCtx: memCtx];
}

- (int) getProperty: (void **) data
            withTag: (enum MAPITAGS) propTag
           inMemCtx: (TALLOC_CTX *) memCtx
{
  int rc;
  id value;

  value = [properties objectForKey: MAPIPropertyKey (propTag)];
  if (value)
    rc = [value getValue: data forTag: propTag inMemCtx: memCtx];
  else
    rc = [super getProperty: data withTag: propTag inMemCtx: memCtx];

  return rc;
}

- (MAPIStoreMessage *) _createAssociatedMessage
{
  MAPIStoreMessage *newMessage;
  SOGoMAPIDBMessage *dbObject;
  NSString *newKey;

  newKey = [NSString stringWithFormat: @"%@.plist",
                     [SOGoObject globallyUniqueObjectId]];
  dbObject = [SOGoMAPIDBMessage objectWithName: newKey inContainer: dbFolder];
  [dbObject setObjectType: MAPIFAICacheObject];
  [dbObject setIsNew: YES];
  newMessage = [MAPIStoreFAIMessageK mapiStoreObjectWithSOGoObject: dbObject
                                                       inContainer: self];
  
  return newMessage;
}

- (MAPIStoreMessage *) createMessage: (BOOL) isAssociated
{
  MAPIStoreMessage *newMessage;
  WOContext *woContext;

  if (isAssociated)
    newMessage = [self _createAssociatedMessage];
  else
    newMessage = [self createMessage];
  /* FIXME: this is ugly as the specifics of message creation should all be
     delegated to subclasses */
  if ([newMessage respondsToSelector: @selector (setIsNew:)])
    [newMessage setIsNew: YES];
  woContext = [[self userContext] woContext];
  /* FIXME: this is ugly too as the specifics of message creation should all
     be delegated to subclasses */
  if ([newMessage respondsToSelector: @selector (sogoObject:)])
    [[newMessage sogoObject] setContext: woContext];

  return newMessage;
}

- (enum mapistore_error) createFolder: (struct SRow *) aRow
                              withFID: (uint64_t) newFID
                               andKey: (NSString **) newKeyP
{
  [self errorWithFormat: @"new folders cannot be created in this context"];

  return MAPISTORE_ERR_DENIED;
}

/* helpers */

- (NSString *) url
{
  NSString *url;

  if (container)
    url = [NSString stringWithFormat: @"%@/", [super url]];
  else
    {
      url = [[context url] absoluteString];
      if (![url hasSuffix: @"/"])
        url = [NSString stringWithFormat: @"%@/", url];
    }

  return url;
}

- (MAPIStorePermissionsTable *) permissionsTable
{
  return [MAPIStorePermissionsTable tableForContainer: self];
}

- (NSArray *) permissionEntries
{
  NSMutableArray *permissionEntries;
  MAPIStorePermissionEntry *entry;
  NSArray *aclUsers;
  uint64_t memberId, regularMemberId = 1;
  NSUInteger count, max;
  NSString *username, *defaultUserId;
  SOGoFolder *aclFolder;

  aclFolder = [self aclFolder];

  defaultUserId = [aclFolder defaultUserID];
  aclUsers = [aclFolder aclUsers];
  max = [aclUsers count];
  permissionEntries = [NSMutableArray arrayWithCapacity: max];
  for (count = 0; count < max; count++)
    {
      username = [aclUsers objectAtIndex: count];
      if (![username hasPrefix: @"@"])
        {
          if ([username isEqualToString: defaultUserId])
            memberId = 0;
          else if ([username isEqualToString: @"anonymous"])
            memberId = ULLONG_MAX;
          else
            {
              memberId = regularMemberId;
              regularMemberId++;
            }
          entry = [MAPIStorePermissionEntry entryWithUserId: username
                                                andMemberId: memberId
                                                  forFolder: self];
          [permissionEntries addObject: entry];
        }
    }

  return permissionEntries;
}

- (NSArray *) rolesForExchangeRights: (uint32_t) rights
{
  [self subclassResponsibility: _cmd];
  return nil;
}

- (uint32_t) exchangeRightsForRoles: (NSArray *) roles
{
  [self subclassResponsibility: _cmd];
  return 0;
}

- (NSString *) _usernameFromEntryId: (struct SBinary_short *) bin
{
  struct Binary_r bin32;
  struct AddressBookEntryId *entryId;
  NSString *username;
  struct ldb_context *samCtx;

  if (bin && bin->cb)
    {
      bin32.cb = bin->cb;
      bin32.lpb = bin->lpb;

      entryId = get_AddressBookEntryId (NULL, &bin32);
      if (entryId)
        {
          samCtx = [[self context] connectionInfo]->sam_ctx;
          username = MAPIStoreSamDBUserAttribute (samCtx, @"legacyExchangeDN",
                                                  [NSString stringWithUTF8String: entryId->X500DN],
                                                  @"sAMAccountName");
        }
      else
        username = nil;
      talloc_free (entryId);
    }
  else
    username = nil;

  return username;
}

- (NSString *) _usernameFromMemberId: (uint64_t) memberId
                           inEntries: (NSArray *) entries
{
  NSString *username = nil;
  NSUInteger count, max;
  MAPIStorePermissionEntry *entry;

  if (memberId == 0)
    username = [[self aclFolder] defaultUserID];
  else if (memberId == ULLONG_MAX)
    username = @"anonymous";
  else
    {
      max = [entries count];
      for (count = 0; !username && count < max; count++)
        {
          entry = [entries objectAtIndex: count];
          if ([entry memberId] == memberId)
            username = [entry userId];
        }
    }

  return username;
}

- (void) _emptyACL
{
  NSUInteger count, max;
  NSArray *users;
  SOGoFolder *aclFolder;

  aclFolder = [self aclFolder];

  users = [aclFolder aclUsers];
  max = [users count];
  for (count = 0; count < max; count++)
    [aclFolder removeUserFromAcls: [users objectAtIndex: count]];
}

- (int) modifyPermissions: (struct PermissionData *) permissions
                withCount: (uint16_t) pcount
                 andFlags: (int8_t) flags
{
  NSUInteger count, propCount;
  struct PermissionData *currentPermission;
  struct mapi_SPropValue *mapiValue;
  NSString *permissionUser;
  NSArray *entries;
  NSArray *permissionRoles;
  BOOL reset, isAdd = NO, isDelete = NO, isModify = NO;
  SOGoFolder *aclFolder;

  aclFolder = [self aclFolder];

  reset = ((flags & ModifyPerms_ReplaceRows) != 0);
  if (reset)
    [self _emptyACL];

  entries = [self permissionEntries];

  for (count = 0; count < pcount; count++)
    {
      currentPermission = permissions + count;

      permissionUser = nil;
      permissionRoles = nil;
 
      if (currentPermission->PermissionDataFlags == ROW_ADD)
        isAdd = YES;
      else if (currentPermission->PermissionDataFlags == ROW_MODIFY)
        isModify = YES;
      else
        isDelete = YES;

      for (propCount = 0;
           propCount < currentPermission->lpProps.cValues;
           propCount++)
        {
          mapiValue = currentPermission->lpProps.lpProps + propCount;
          switch (mapiValue->ulPropTag)
            {
            case PR_ENTRYID:
              if (isAdd)
                permissionUser
                  = [self _usernameFromEntryId: &mapiValue->value.bin];
              break;
            case PR_MEMBER_ID:
              if (isModify || isDelete)
                permissionUser = [self _usernameFromMemberId: mapiValue->value.d
                                                   inEntries: entries];
              break;
            case PR_MEMBER_RIGHTS:
              if (isAdd || isModify)
                permissionRoles
                  = [self rolesForExchangeRights: mapiValue->value.l];
              break;
            default:
              if (mapiValue->ulPropTag != PR_MEMBER_NAME)
                [self warnWithFormat: @"unhandled permission property: %.8x",
                      mapiValue->ulPropTag];
            }
        }

      if (reset)
        {
          if (isAdd)
            [self _modifyPermissionEntryForUser: permissionUser
                                      withRoles: permissionRoles
                                     isAddition: YES
                                  withACLFolder: aclFolder];
        }
      else
        {
          if (isAdd || currentPermission->PermissionDataFlags == ROW_MODIFY)
            [self _modifyPermissionEntryForUser: permissionUser
                                      withRoles: permissionRoles
                                     isAddition: isAdd
                                  withACLFolder: aclFolder];
          else if (currentPermission->PermissionDataFlags == ROW_REMOVE)
            [aclFolder removeUserFromAcls: permissionUser];
          else
            [self errorWithFormat: @"unhandled permission action flag: %d",
                  currentPermission->PermissionDataFlags];
        }
    }

  return MAPISTORE_SUCCESS;
}

- (enum mapistore_error) preloadMessageBodiesWithMIDs: (const struct UI8Array_r *) mids
                                          ofTableType: (enum mapistore_table_type) tableType
{
  uint32_t count;
  NSMutableArray *messageKeys;
  MAPIStoreMapping *mapping;
  NSString *messageURL, *messageKey;

  messageKeys = [NSMutableArray arrayWithCapacity: mids->cValues];

  mapping = [self mapping];
  for (count = 0; count < mids->cValues; count++)
    {
      messageURL = [mapping urlFromID: mids->lpui8[count]];
      if (messageURL)
        {
          messageKey = [self childKeyFromURL: messageURL];
          if (messageKey)
            [messageKeys addObject: messageKey];
        }
    }

  return [self preloadMessageBodiesWithKeys: messageKeys
                                ofTableType: tableType];
}

- (enum mapistore_error) preloadMessageBodiesWithKeys: (NSArray *) keys
                                          ofTableType: (enum mapistore_table_type) tableType
{
  return MAPISTORE_SUCCESS;
}

- (uint64_t) objectId
{
  uint64_t objectId;
  NSString *folderKey;

  if (container)
    {
      folderKey = [NSString stringWithFormat: @"%@/",
                            [sogoObject nameInContainer]];
      objectId = [container idForObjectWithKey: folderKey];
    }
  else
    objectId = [self idForObjectWithKey: nil];

  return objectId;
}

- (uint64_t) idForObjectWithKey: (NSString *) childKey
{
  return [[self context] idForObjectWithKey: childKey
                                inFolderURL: [self url]];
}

- (NSDate *) creationTime
{
  return [dbFolder creationDate];
}

- (NSDate *) lastModificationTime
{
  return [dbFolder lastModified];
}

/* subclasses */

- (MAPIStoreMessageTable *) messageTable
{
  return nil;
}

- (NSArray *) messageKeysMatchingQualifier: (EOQualifier *) qualifier
                          andSortOrderings: (NSArray *) sortOrderings
{
  [self subclassResponsibility: _cmd];

  return nil;  
}

- (NSArray *) getDeletedKeysFromChangeNumber: (uint64_t) changeNum
                                       andCN: (NSNumber **) cnNbrs
                                 inTableType: (enum mapistore_table_type) tableType
{
  return nil;
}

- (MAPIStoreMessage *) createMessage
{
  [self logWithFormat: @"ignored method: %s", __PRETTY_FUNCTION__];
  return nil;
}

- (NSCalendarDate *) lastMessageModificationTime
{
  [self subclassResponsibility: _cmd];

  return nil;
}

- (BOOL) subscriberCanCreateMessages
{
  return NO;
}

- (BOOL) subscriberCanModifyMessages
{
  return NO;
}

- (BOOL) subscriberCanReadMessages
{
  return NO;
}

- (BOOL) subscriberCanDeleteMessages
{
  return NO;
}

- (BOOL) subscriberCanCreateSubFolders
{
  return NO;
}

- (BOOL) supportsSubFolders
{
  return NO;
}

@end
