sogo/OpenChange/MAPIStoreMailFolder.m

1005 lines
31 KiB
Matlab
Raw Normal View History

/* MAPIStoreMailFolder.m - this file is part of SOGo
*
* Copyright (C) 2011 Inverse inc
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* 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.
*/
#include <talloc.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSCalendarDate.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import <Foundation/NSURL.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <EOControl/EOQualifier.h>
#import <EOControl/EOSortOrdering.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <NGImap4/NGImap4Connection.h>
#import <NGImap4/NGImap4Client.h>
#import <Mailer/SOGoDraftsFolder.h>
#import <Mailer/SOGoMailAccount.h>
#import <Mailer/SOGoMailAccounts.h>
#import <Mailer/SOGoMailFolder.h>
#import <Mailer/SOGoMailObject.h>
#import <Mailer/SOGoSentFolder.h>
#import <Mailer/SOGoTrashFolder.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import "MAPIApplication.h"
#import "MAPIStoreAppointmentWrapper.h"
#import "MAPIStoreContext.h"
#import "MAPIStoreDraftsMessage.h"
#import "MAPIStoreFAIMessage.h"
#import "MAPIStoreMailMessage.h"
#import "MAPIStoreMailMessageTable.h"
#import "MAPIStoreMapping.h"
#import "MAPIStoreTypes.h"
#import "NSString+MAPIStore.h"
#import "SOGoMAPIFSMessage.h"
#import "MAPIStoreMailFolder.h"
static Class MAPIStoreDraftsMessageK;
static Class MAPIStoreMailMessageK;
static Class SOGoMailFolderK;
#undef DEBUG
#include <libmapi/libmapi.h>
#include <mapistore/mapistore.h>
#include <mapistore/mapistore_errors.h>
@implementation MAPIStoreMailFolder
+ (void) initialize
{
MAPIStoreMailMessageK = [MAPIStoreMailMessage class];
SOGoMailFolderK = [SOGoMailFolder class];
[MAPIStoreAppointmentWrapper class];
}
- (id) initWithURL: (NSURL *) newURL
inContext: (MAPIStoreContext *) newContext
{
SOGoUserFolder *userFolder;
SOGoMailAccounts *accountsFolder;
SOGoMailAccount *accountFolder;
SOGoFolder *currentContainer;
WOContext *woContext;
if ((self = [super initWithURL: newURL
inContext: newContext]))
{
woContext = [newContext woContext];
userFolder = [SOGoUserFolder objectWithName: [newURL user]
inContainer: MAPIApp];
[parentContainersBag addObject: userFolder];
[woContext setClientObject: userFolder];
accountsFolder = [userFolder lookupName: @"Mail"
inContext: woContext
acquire: NO];
[parentContainersBag addObject: accountsFolder];
[woContext setClientObject: accountsFolder];
accountFolder = [accountsFolder lookupName: @"0"
inContext: woContext
acquire: NO];
[[accountFolder imap4Connection]
enableExtension: @"QRESYNC"];
[parentContainersBag addObject: accountFolder];
[woContext setClientObject: accountFolder];
sogoObject = [self specialFolderFromAccount: accountFolder
inContext: woContext];
[sogoObject retain];
currentContainer = [sogoObject container];
while (currentContainer != (SOGoFolder *) accountFolder)
{
[parentContainersBag addObject: currentContainer];
currentContainer = [currentContainer container];
}
ASSIGN (versionsMessage,
[SOGoMAPIFSMessage objectWithName: @"versions.plist"
inContainer: propsFolder]);
}
return self;
}
- (id) initWithSOGoObject: (id) newSOGoObject
inContainer: (MAPIStoreObject *) newContainer
{
NSURL *propsURL;
NSString *urlString;
if ((self = [super initWithSOGoObject: newSOGoObject inContainer: newContainer]))
{
urlString = [[self url] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
propsURL = [NSURL URLWithString: urlString];
ASSIGN (versionsMessage,
[SOGoMAPIFSMessage objectWithName: @"versions.plist"
inContainer: propsFolder]);
}
return self;
}
- (void) dealloc
{
[versionsMessage release];
[super dealloc];
}
- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder
inContext: (WOContext *) woContext
{
[self subclassResponsibility: _cmd];
return nil;
}
- (MAPIStoreMessageTable *) messageTable
{
[self synchroniseCache];
return [MAPIStoreMailMessageTable tableForContainer: self];
}
- (Class) messageClass
{
return MAPIStoreMailMessageK;
}
- (NSString *) createFolder: (struct SRow *) aRow
withFID: (uint64_t) newFID
inContainer: (id) subfolderParent
{
NSString *folderName, *nameInContainer;
SOGoMailFolder *newFolder;
int i;
nameInContainer = nil;
folderName = nil;
for (i = 0; !folderName && i < aRow->cValues; i++)
{
if (aRow->lpProps[i].ulPropTag == PR_DISPLAY_NAME_UNICODE)
folderName = [NSString stringWithUTF8String: aRow->lpProps[i].value.lpszW];
else if (aRow->lpProps[i].ulPropTag == PR_DISPLAY_NAME)
folderName = [NSString stringWithUTF8String: aRow->lpProps[i].value.lpszA];
}
if (folderName)
{
nameInContainer = [NSString stringWithFormat: @"folder%@",
[folderName asCSSIdentifier]];
newFolder = [SOGoMailFolderK objectWithName: nameInContainer
inContainer: subfolderParent];
if (![newFolder create])
nameInContainer = nil;
}
return nameInContainer;
}
- (NSString *) createFolder: (struct SRow *) aRow
withFID: (uint64_t) newFID
{
return [self createFolder: aRow withFID: newFID
inContainer: sogoObject];
}
- (int) getPrContentUnread: (void **) data
inMemCtx: (TALLOC_CTX *) memCtx
{
EOQualifier *searchQualifier;
uint32_t longValue;
searchQualifier
= [EOQualifier qualifierWithQualifierFormat: @"flags = %@", @"unseen"];
longValue = [[sogoObject fetchUIDsMatchingQualifier: searchQualifier
sortOrdering: nil]
count];
*data = MAPILongValue (memCtx, longValue);
return MAPISTORE_SUCCESS;
}
- (int) getPrContainerClass: (void **) data
inMemCtx: (TALLOC_CTX *) memCtx
{
*data = [@"IPF.Note" asUnicodeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
- (int) getPrMessageClass: (void **) data
inMemCtx: (TALLOC_CTX *) memCtx
{
*data = [@"IPM.Note" asUnicodeInMemCtx: memCtx];
return MAPISTORE_SUCCESS;
}
- (EOQualifier *) nonDeletedQualifier
{
static EOQualifier *nonDeletedQualifier = nil;
EOQualifier *deletedQualifier;
if (!nonDeletedQualifier)
{
deletedQualifier
= [[EOKeyValueQualifier alloc]
initWithKey: @"FLAGS"
operatorSelector: EOQualifierOperatorContains
value: [NSArray arrayWithObject: @"Deleted"]];
nonDeletedQualifier = [[EONotQualifier alloc]
initWithQualifier: deletedQualifier];
[deletedQualifier release];
}
return nonDeletedQualifier;
}
- (NSArray *) messageKeysMatchingQualifier: (EOQualifier *) qualifier
andSortOrderings: (NSArray *) sortOrderings
{
NSArray *uidKeys;
EOQualifier *fetchQualifier;
if (!sortOrderings)
sortOrderings = [NSArray arrayWithObject: @"ARRIVAL"];
if (qualifier)
{
fetchQualifier
= [[EOAndQualifier alloc] initWithQualifiers:
[self nonDeletedQualifier], qualifier,
nil];
[fetchQualifier autorelease];
}
else
fetchQualifier = [self nonDeletedQualifier];
uidKeys = [sogoObject fetchUIDsMatchingQualifier: fetchQualifier
sortOrdering: sortOrderings];
return [uidKeys stringsWithFormat: @"%@.eml"];
}
- (NSMutableString *) _imapFolderNameRepresentation: (NSString *) subfolderName
{
NSMutableString *representation;
NSString *nameInContainer, *strippedName;
nameInContainer = [self nameInContainer];
if (container)
representation = [(MAPIStoreMailFolder *) container
_imapFolderNameRepresentation: nameInContainer];
else
{
if (![nameInContainer hasPrefix: @"folder"])
abort ();
strippedName = [nameInContainer substringFromIndex: 6];
representation = [NSMutableString stringWithString: strippedName];
}
if (![subfolderName hasPrefix: @"folder"])
abort ();
strippedName = [subfolderName substringFromIndex: 6];
[representation appendFormat: @"/%@", strippedName];
return representation;
}
- (void) _cleanupSubfolderKeys: (NSMutableArray *) subfolderKeys
{
SOGoMailAccount *account;
NSString *draftsFolderName, *sentFolderName, *trashFolderName;
NSString *subfolderKey, *cmpString;
NSUInteger count, max;
NSMutableArray *keysToRemove;
account = [(SOGoMailFolder *) sogoObject mailAccountFolder];
draftsFolderName = [account draftsFolderNameInContext: nil];
sentFolderName = [account sentFolderNameInContext: nil];
trashFolderName = [account trashFolderNameInContext: nil];
max = [subfolderKeys count];
keysToRemove = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
subfolderKey = [subfolderKeys objectAtIndex: count];
cmpString = [self _imapFolderNameRepresentation: subfolderKey];
if ([cmpString isEqualToString: draftsFolderName]
|| [cmpString isEqualToString: sentFolderName]
|| [cmpString isEqualToString: trashFolderName])
[keysToRemove addObject: subfolderKey];
}
[subfolderKeys removeObjectsInArray: keysToRemove];
}
- (NSArray *) folderKeysMatchingQualifier: (EOQualifier *) qualifier
andSortOrderings: (NSArray *) sortOrderings
{
NSMutableArray *subfolderKeys;
if (qualifier)
[self errorWithFormat: @"qualifier is not used for folders"];
if (sortOrderings)
[self errorWithFormat: @"sort orderings are not used for folders"];
subfolderKeys = [[sogoObject toManyRelationshipKeys] mutableCopy];
[subfolderKeys autorelease];
[self _cleanupSubfolderKeys: subfolderKeys];
return subfolderKeys;
}
- (id) lookupFolder: (NSString *) childKey
{
id childObject = nil;
SOGoMailFolder *childFolder;
[self folderKeys];
if ([folderKeys containsObject: childKey])
{
childFolder = [sogoObject lookupName: childKey inContext: nil
acquire: NO];
childObject = [MAPIStoreMailFolder mapiStoreObjectWithSOGoObject: childFolder
inContainer: self];
}
return childObject;
}
- (NSCalendarDate *) creationTime
{
return [NSCalendarDate dateWithTimeIntervalSince1970: 0x4dbb2dbe]; /* oc_version_time */
}
- (NSDate *) lastMessageModificationTime
{
NSNumber *ti;
NSDate *value = nil;
ti = [[versionsMessage properties]
objectForKey: @"SyncLastSynchronisationDate"];
if (ti)
value = [NSDate dateWithTimeIntervalSince1970: [ti doubleValue]];
else
value = [NSDate date];
[self logWithFormat: @"lastMessageModificationTime: %@", value];
return value;
}
/* synchronisation */
/* Tree:
{
SyncLastModseq = x;
SyncLastSynchronisationDate = x; ** not updated until something changed
Messages = {
MessageKey = {
Version = x;
Modseq = x;
Deleted = b;
};
...
};
VersionMapping = {
Version = MessageKey;
...
}
}
*/
static NSComparisonResult
_compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
{
static NSNumber *zeroNumber = nil;
NSNumber *modseq1, *modseq2;
if (!zeroNumber)
zeroNumber = [NSNumber numberWithUnsignedLongLong: 0];
modseq1 = [entry1 objectForKey: @"modseq"];
if (!modseq1)
modseq1 = zeroNumber;
modseq2 = [entry2 objectForKey: @"modseq"];
if (!modseq2)
modseq2 = zeroNumber;
return [modseq1 compare: modseq2];
}
- (BOOL) synchroniseCache
{
BOOL rc = YES;
uint64_t newChangeNum;
NSNumber *ti, *changeNumber, *modseq, *lastModseq, *nextModseq, *uid;
EOQualifier *searchQualifier;
NSArray *uids;
NSUInteger count, max;
NSArray *fetchResults;
NSDictionary *result;
NSMutableDictionary *currentProperties, *messages, *mapping, *messageEntry;
NSCalendarDate *now;
now = [NSCalendarDate date];
[now setTimeZone: utcTZ];
currentProperties = [[versionsMessage properties] mutableCopy];
if (!currentProperties)
currentProperties = [NSMutableDictionary new];
[currentProperties autorelease];
messages = [currentProperties objectForKey: @"Messages"];
if (!messages)
{
messages = [NSMutableDictionary new];
[currentProperties setObject: messages forKey: @"Messages"];
[messages release];
}
mapping = [currentProperties objectForKey: @"VersionMapping"];
if (!mapping)
{
mapping = [NSMutableDictionary new];
[currentProperties setObject: mapping forKey: @"VersionMapping"];
[mapping release];
}
lastModseq = [currentProperties objectForKey: @"SyncLastModseq"];
if (lastModseq)
{
nextModseq = [NSNumber numberWithUnsignedLongLong:
[lastModseq unsignedLongLongValue] + 1];
searchQualifier = [[EOKeyValueQualifier alloc]
initWithKey: @"modseq"
operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo
value: nextModseq];
[searchQualifier autorelease];
}
else
searchQualifier = [self nonDeletedQualifier];
uids = [sogoObject fetchUIDsMatchingQualifier: searchQualifier
sortOrdering: nil];
max = [uids count];
if (max > 0)
{
fetchResults
= [(NSDictionary *) [sogoObject fetchUIDs: uids
parts: [NSArray arrayWithObject: @"modseq"]]
objectForKey: @"fetch"];
/* NOTE: we sort items manually because Cyrus does not properly sort
entries with a MODSEQ of 0 */
fetchResults
= [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ
context: NULL];
ldb_transaction_start([[self context] connectionInfo]->oc_ctx);
for (count = 0; count < max; count++)
{
result = [fetchResults objectAtIndex: count];
uid = [result objectForKey: @"uid"];
modseq = [result objectForKey: @"modseq"];
[self logWithFormat: @"uid '%@' has modseq '%@'", uid, modseq];
newChangeNum = [[self context] getNewChangeNumber];
changeNumber = [NSNumber numberWithUnsignedLongLong: newChangeNum];
messageEntry = [NSMutableDictionary new];
[messages setObject: messageEntry forKey: uid];
[messageEntry release];
[messageEntry setObject: modseq forKey: @"modseq"];
[messageEntry setObject: changeNumber forKey: @"version"];
[mapping setObject: modseq forKey: changeNumber];
if (!lastModseq
|| ([lastModseq compare: modseq] == NSOrderedAscending))
lastModseq = modseq;
}
ldb_transaction_commit([[self context] connectionInfo]->oc_ctx);
ti = [NSNumber numberWithDouble: [now timeIntervalSince1970]];
[currentProperties setObject: ti
forKey: @"SyncLastSynchronisationDate"];
[currentProperties setObject: lastModseq forKey: @"SyncLastModseq"];
[versionsMessage appendProperties: currentProperties];
[versionsMessage save];
}
return rc;
}
- (NSNumber *) modseqFromMessageChangeNumber: (NSNumber *) changeNum
{
NSDictionary *mapping;
NSNumber *modseq;
mapping = [[versionsMessage properties] objectForKey: @"VersionMapping"];
modseq = [mapping objectForKey: changeNum];
return modseq;
}
- (NSNumber *) messageUIDFromMessageKey: (NSString *) messageKey
{
NSNumber *messageUid;
NSString *uidString;
NSRange dotRange;
dotRange = [messageKey rangeOfString: @".eml"];
if (dotRange.location != NSNotFound)
{
uidString = [messageKey substringToIndex: dotRange.location];
messageUid = [NSNumber numberWithInt: [uidString intValue]];
}
else
messageUid = nil;
return messageUid;
}
- (NSNumber *) changeNumberForMessageUID: (NSNumber *) messageUid
{
NSDictionary *messages;
NSNumber *changeNumber;
messages = [[versionsMessage properties] objectForKey: @"Messages"];
changeNumber = [[messages objectForKey: messageUid]
objectForKey: @"version"];
return changeNumber;
}
- (NSArray *) getDeletedKeysFromChangeNumber: (uint64_t) changeNum
andCN: (NSNumber **) cnNbr
inTableType: (uint8_t) tableType
{
NSArray *deletedKeys, *deletedUIDs;
NSNumber *changeNumNbr;
uint64_t modseq;
NSDictionary *versionProperties, *status;
NSMutableDictionary *messages, *mapping;
NSNumber *newChangeNumNbr, *highestModseq;
uint64_t newChangeNum;
NSUInteger count, max;
if (tableType == MAPISTORE_MESSAGE_TABLE)
{
changeNumNbr = [NSNumber numberWithUnsignedLongLong: changeNum];
modseq = [[self modseqFromMessageChangeNumber: changeNumNbr]
unsignedLongLongValue];
if (modseq > 0)
{
status
= [sogoObject
statusForFlags: [NSArray arrayWithObject: @"HIGHESTMODSEQ"]];
highestModseq = [status objectForKey: @"highestmodseq"];
versionProperties = [versionsMessage properties];
messages = [versionProperties objectForKey: @"Messages"];
deletedUIDs = [(SOGoMailFolder *) sogoObject
fetchUIDsOfVanishedItems: modseq];
deletedKeys = [deletedUIDs stringsWithFormat: @"%@.eml"];
max = [deletedUIDs count];
if (max > 0)
{
[messages removeObjectsForKeys: deletedUIDs];
mapping = [versionProperties objectForKey: @"VersionsMapping"];
for (count = 0; count < max; count++)
newChangeNum = [[self context] getNewChangeNumber];
newChangeNumNbr = [NSNumber numberWithUnsignedLongLong: newChangeNum];
*cnNbr = newChangeNumNbr;
[mapping setObject: newChangeNumNbr forKey: @"SyncLastModseq"];
[versionsMessage save];
}
}
else
deletedKeys = [NSArray array];
}
else
deletedKeys = [super getDeletedKeysFromChangeNumber: changeNum
andCN: cnNbr
inTableType: tableType];
return deletedKeys;
}
//
// We need to support creating "fake" emails for RopCopyTo and other
// other ROPs to function properly. We don't know what will be the
// object's name (as it's the IMAP's UID) until the message really
// is created in the mail store.
//
- (MAPIStoreMessage *) createMessageWithMID: (uint64_t) mid
{
MAPIStoreMessage *message;
SOGoMailObject *mail;
mail = [SOGoMailObject objectWithName: [NSString stringWithFormat: @"%llu", mid]
inContainer: sogoObject];
message = [MAPIStoreMailMessage mapiStoreObjectWithSOGoObject: mail
inContainer: self];
return message;
}
//
// Move (or eventually copy) the mail identified by
// "mid" within this current folder. The message is
// of course coming from an other folder
//
- (int) moveCopyMessageWithMID: (uint64_t) mid
toFolder: (MAPIStoreFolder *) targetFolder
inMessage: (MAPIStoreMessage *) targetMessage
wantCopy: (uint8_t) want_copy
{
NSString *folderName, *messageURL, *uid, *url, *v;
MAPIStoreMapping *mapping;
NSDictionary *result;
unsigned int new_uid;
uint64_t target_mid;
int rc;
mapping = [[self context] mapping];
messageURL = [mapping urlFromID: mid];
if (messageURL)
{
// We get the message UID from that folder by stripping the .eml
// This is the message we'll copy in the folder specified by targetURL
uid = [messageURL lastPathComponent];
uid = [uid substringToIndex: [uid length] - 4];
folderName = [[targetFolder sogoObject] relativeImap4Name];
if (uid && [folderName length] > 0)
{
struct mapistore_object_notification_parameters *notif_parameters;
struct mapistore_connection_info *connInfo;
NGImap4Client *client;
NSArray *a;
// We copy the message, get the new UID and set the old one as deleted
client = [[[self sogoObject] imap4Connection] client];
[client select: [[self sogoObject] relativeImap4Name]];
result = [client copyUid: [uid intValue] toFolder: folderName];
if (!want_copy)
[client storeUid: [uid intValue] add: [NSNumber numberWithBool: YES] flags: [NSArray arrayWithObject: @"Deleted"]];
//
// We use the UIDPLUS IMAP extension here in order to speedup UID retreival
// If supported by the server, we'll get something like: COPYUID 1315425789 1 8
//
// Sometimes COPYUID isn't returned at all by Cyrus or in case the server doesn't
// support the UIDPLUS IMAP extension, we fallback to a simple UID search.
//
v = [[[result objectForKey: @"RawResponse"] objectForKey: @"ResponseResult"] objectForKey: @"flag"];
if (v)
{
unsigned int current_uid, uid_validity;
const char *s;
char tag[7];
s = [v cStringUsingEncoding: NSASCIIStringEncoding];
sscanf(s, "%s %u %u %u", tag, &uid_validity, &current_uid, &new_uid);
}
else
{
[client select: folderName];
a = [[client sort: @"ARRIVAL" qualifier: nil encoding: @"UTF-8"] objectForKey: @"sort"];
new_uid = [[[a sortedArrayUsingSelector: @selector(compare:)] lastObject] intValue];
}
// We compute the URL of the move message and update our mapping
url = [NSString stringWithFormat: @"%@%d.eml", [targetFolder url], new_uid];
if (!want_copy)
[mapping unregisterURLWithID: mid];
// We adjust its name within the container with the newly obtained UID. This was previously the
// MID of the message and that value was temporary since we created a "fake" message
target_mid = strtoull([[[targetMessage sogoObject] nameInContainer] UTF8String], NULL, 10);
[[targetMessage sogoObject] setNameInContainer: [NSString stringWithFormat: @"%u.eml", new_uid]];
// We unregister the previously (and temporary) registered mid and register
// it again with its new and valid URL
[mapping unregisterURLWithID: target_mid];
[mapping registerURL: url withID: target_mid];
NSArray *activeTables;
NSUInteger count, max;
/* we ensure the table caches are loaded so that old and new state
can be compared */
MAPIStoreMessage *message;
message = [self lookupMessageByURL: messageURL];
activeTables = ([message isKindOfClass: [MAPIStoreFAIMessage class]]
? [self activeFAIMessageTables]
: [self activeMessageTables]);
max = [activeTables count];
for (count = 0; count < max; count++)
[[activeTables objectAtIndex: count] restrictedChildKeys];
// We notify the client
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];
connInfo = [[self context] connectionInfo];
mapistore_push_notification (connInfo->mstore_ctx,
MAPISTORE_FOLDER,
MAPISTORE_OBJECT_MODIFIED,
notif_parameters);
talloc_free(notif_parameters);
/* move/copy notification */
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 = target_mid;
notif_parameters->folder_id = [targetFolder objectId];
notif_parameters->old_object_id = mid;
notif_parameters->old_folder_id = [self objectId];
mapistore_push_notification (connInfo->mstore_ctx,
MAPISTORE_MESSAGE,
(want_copy ? MAPISTORE_OBJECT_COPIED : MAPISTORE_OBJECT_MOVED),
notif_parameters);
talloc_free(notif_parameters);
/* table notification */
for (count = 0; count < max; count++)
[[activeTables objectAtIndex: count]
notifyChangesForChild: message];
// We cleanup cache of our source and destionation folders
[self cleanupCaches];
[targetFolder cleanupCaches];
rc = MAPISTORE_SUCCESS;
}
else
rc = MAPISTORE_ERR_NOT_FOUND;
}
else
rc = MAPISTORE_ERR_NOT_FOUND;
return rc;
}
@end
@implementation MAPIStoreInboxFolder : MAPIStoreMailFolder
- (id) initWithURL: (NSURL *) newURL
inContext: (MAPIStoreContext *) newContext
{
NSDictionary *list, *response;
NGImap4Client *client;
if ((self = [super initWithURL: newURL
inContext: newContext]))
{
client = [[(SOGoMailFolder *) sogoObject imap4Connection] client];
list = [client list: @"" pattern: @"INBOX"];
response = [[list objectForKey: @"RawResponse"] objectForKey: @"list"];
usesAltNameSpace = [[response objectForKey: @"flags"] containsObject: @"noinferiors"];
}
return self;
}
- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder
inContext: (WOContext *) woContext
{
return [accountFolder inboxFolderInContext: woContext];
}
- (NSString *) createFolder: (struct SRow *) aRow
withFID: (uint64_t) newFID
{
id subfolderParent;
if (usesAltNameSpace)
subfolderParent = [(SOGoMailFolder *) sogoObject mailAccountFolder];
else
subfolderParent = sogoObject;
return [self createFolder: aRow withFID: newFID
inContainer: subfolderParent];
}
- (NSMutableString *) _imapFolderNameRepresentation: (NSString *) subfolderName
{
NSMutableString *representation;
if (usesAltNameSpace)
{
/* with "altnamespace", the subfolders are NEVER subfolders of INBOX... */;
if (![subfolderName hasPrefix: @"folder"])
abort ();
representation
= [NSMutableString stringWithString:
[subfolderName substringFromIndex: 6]];
}
else
representation = [super _imapFolderNameRepresentation: subfolderName];
return representation;
}
- (NSArray *) folderKeysMatchingQualifier: (EOQualifier *) qualifier
andSortOrderings: (NSArray *) sortOrderings
{
NSMutableArray *subfolderKeys;
SOGoMailAccount *account;
if (usesAltNameSpace)
{
if (qualifier)
[self errorWithFormat: @"qualifier is not used for folders"];
if (sortOrderings)
[self errorWithFormat: @"sort orderings are not used for folders"];
account = [(SOGoMailFolder *) sogoObject mailAccountFolder];
subfolderKeys
= [[account toManyRelationshipKeysWithNamespaces: NO]
mutableCopy];
[subfolderKeys removeObject: @"folderINBOX"];
[self _cleanupSubfolderKeys: subfolderKeys];
}
else
subfolderKeys = [[super folderKeysMatchingQualifier: qualifier
andSortOrderings: sortOrderings]
mutableCopy];
/* TODO: remove special folders */
[subfolderKeys autorelease];
return subfolderKeys;
}
- (id) lookupFolder: (NSString *) childKey
{
id childObject = nil;
SOGoMailAccount *account;
SOGoMailFolder *childFolder;
if (usesAltNameSpace)
{
[self folderKeys];
if ([folderKeys containsObject: childKey])
{
account = [(SOGoMailFolder *) sogoObject mailAccountFolder];
childFolder = [account lookupName: childKey inContext: nil
acquire: NO];
childObject = [MAPIStoreMailFolder mapiStoreObjectWithSOGoObject: childFolder
inContainer: self];
}
}
else
childObject = [super lookupFolder: childKey];
return childObject;
}
@end
@implementation MAPIStoreSentItemsFolder : MAPIStoreMailFolder
- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder
inContext: (WOContext *) woContext
{
return [accountFolder sentFolderInContext: woContext];
}
@end
@implementation MAPIStoreDraftsFolder : MAPIStoreMailFolder
- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder
inContext: (WOContext *) woContext
{
return [accountFolder draftsFolderInContext: woContext];
}
@end
// @implementation MAPIStoreDeletedItemsFolder : MAPIStoreMailFolder
// - (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder
// inContext: (WOContext *) woContext
// {
// return [accountFolder trashFolderInContext: woContext];
// }
// @end
//
//
//
@implementation MAPIStoreOutboxFolder : MAPIStoreMailFolder
+ (void) initialize
{
MAPIStoreDraftsMessageK = [MAPIStoreDraftsMessage class];
}
- (SOGoMailFolder *) specialFolderFromAccount: (SOGoMailAccount *) accountFolder
inContext: (WOContext *) woContext
{
return [accountFolder draftsFolderInContext: woContext];
}
- (Class) messageClass
{
return MAPIStoreDraftsMessageK;
}
- (MAPIStoreMessage *) createMessageWithMID: (uint64_t) mid
{
MAPIStoreDraftsMessage *newMessage;
SOGoDraftObject *newDraft;
newDraft = [sogoObject newDraft];
newMessage
= [MAPIStoreDraftsMessage mapiStoreObjectWithSOGoObject: newDraft
inContainer: self];
return newMessage;
}
@end