2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "NGImap4FileManager.h"
23 #include <NGImap4/NGImap4Folder.h>
24 #include <NGImap4/NGImap4Context.h>
25 #include <NGImap4/NGImap4Message.h>
26 #include <NGExtensions/NGFileFolderInfoDataSource.h>
28 #include <NGImap4/NGImap4DataSource.h>
30 @interface NGImap4FileManager(Privates)
32 - (BOOL)loginWithUser:(NSString *)_user
33 password:(NSString *)_pwd
34 host:(NSString *)_host;
36 - (id<NGImap4Folder>)_lookupFolderAtPath:(NSArray *)_paths;
37 - (id<NGImap4Folder>)_lookupFolderAtPathString:(NSString *)_path;
39 - (EOQualifier *)_qualifierForFileName:(NSString *)_filename;
43 @implementation NGImap4FileManager
45 static BOOL debugOn = NO;
48 return [super version] + 0 /* v0 */;
51 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
53 NSAssert2([super version] == 0,
54 @"invalid superclass (%@) version %i !",
55 NSStringFromClass([self superclass]), [super version]);
57 if ((debugOn = [ud boolForKey:@"NGImap4FileManagerDebugEnabled"]))
58 NSLog(@"NGImap4FileManager debugging is enabled.");
61 - (id)initWithUser:(NSString *)_user
62 password:(NSString *)_pwd
63 host:(NSString *)_host
65 if ((self = [super init])) {
66 if (![self loginWithUser:_user password:_pwd host:_host]) {
67 [self logWithFormat:@"could not login user '%@' host '%@'.",
76 return [self initWithUser:nil password:nil host:nil];
78 - (id)initWithURL:(NSURL *)_url {
84 if ((self = [super init])) {
85 self->imapContext = [NGImap4Context alloc]; /* keep gcc happy */
86 if ((self->imapContext = [self->imapContext initWithURL:_url]) == nil){
87 [self logWithFormat:@"ERROR: got no IMAP4 context for url %@ ...",_url];
92 [self->imapContext enterSyncMode];
94 if (![self->imapContext openConnection]) {
95 [self logWithFormat:@"ERROR: could not open IMAP4 connection ..."];
100 if ((self->rootFolder = [[self->imapContext serverRoot] retain]) == nil) {
101 [self logWithFormat:@"ERROR: did not find root folder ..."];
105 if ((self->currentFolder=[[self->imapContext inboxFolder] retain])==nil) {
106 [self logWithFormat:@"ERROR: did not find inbox folder ..."];
111 if (![[_url path] isEqualToString:@"/"]) {
112 if (![self changeCurrentDirectoryPath:[_url path]]) {
113 [self logWithFormat:@"ERROR: couldn't change to URL path: %@", _url];
123 return self->imapContext;
127 [self->currentFolder release];
128 [self->imapContext release];
129 [self->rootFolder release];
135 - (BOOL)loginWithUser:(NSString *)_user
136 password:(NSString *)_pwd
137 host:(NSString *)_host
139 NSException *loginException;
140 NSDictionary *conDict;
142 [self->imapContext release]; self->imapContext = nil;
143 [self->rootFolder release]; self->rootFolder = nil;
144 [self->currentFolder release]; self->currentFolder = nil;
146 conDict = [NSDictionary dictionaryWithObjectsAndKeys:
147 _user ? _user : @"anonymous", @"login",
148 _pwd ? _pwd : @"", @"passwd",
149 _host ? _host : @"localhost", @"host",
152 loginException = nil;
155 [[NGImap4Context alloc] initWithConnectionDictionary:conDict];
156 [self->imapContext enterSyncMode];
158 if (![self->imapContext openConnection])
161 if ((self->rootFolder = [[self->imapContext serverRoot] retain]) == nil)
163 if ((self->currentFolder = [[self->imapContext inboxFolder] retain]) == nil)
171 - (id<NGImap4Folder>)_lookupFolderAtPath:(NSArray *)_paths {
172 id<NGImap4Folder> folder;
176 folder = self->currentFolder;
178 e = [_paths objectEnumerator];
179 while ((path = [e nextObject]) && (folder != nil)) {
180 if ([path isEqualToString:@"."])
182 if ([path isEqualToString:@""])
184 if ([path isEqualToString:@".."]) {
185 folder = [folder parentFolder];
188 if ([path isEqualToString:@"/"]) {
189 folder = self->rootFolder;
193 folder = [folder subFolderWithName:path caseInsensitive:NO];
198 - (id<NGImap4Folder>)_lookupFolderAtPathString:(NSString *)_path {
199 return [self _lookupFolderAtPath:[_path pathComponents]];
202 - (EOQualifier *)_qualifierForFileName:(NSString *)_filename {
203 return [EOQualifier qualifierWithQualifierFormat:@"uid=%@", _filename];
208 - (BOOL)createDirectoryAtPath:(NSString *)_path
209 attributes:(NSDictionary *)_attributes
211 id<NGImap4Folder> folder;
214 if (![_path isAbsolutePath])
215 _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
217 filename = [_path lastPathComponent];
218 _path = [_path stringByDeletingLastPathComponent];
220 if ((folder = [self _lookupFolderAtPathString:_path]) == nil)
223 return [folder createSubFolderWithName:filename];
226 - (BOOL)changeCurrentDirectoryPath:(NSString *)_path {
227 id<NGImap4Folder> folder;
229 if ([_path length] == 0)
232 if (![_path isAbsolutePath]) {
233 _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
236 if ((folder = [self _lookupFolderAtPathString:_path]) == nil)
239 ASSIGN(self->currentFolder, folder);
244 - (NSString *)currentDirectoryPath {
245 if ([self->currentFolder isEqual:[self->currentFolder parentFolder]] ||
246 [self->currentFolder parentFolder] == nil)
248 else return [self->currentFolder absoluteName];
251 - (NGImap4Folder *)currentFolder {
252 return self->currentFolder;
257 - (NSArray *)directoryContentsAtPath:(NSString *)_path {
258 return [self directoryContentsAtPath:_path directories:YES files:YES];
261 - (NSArray *)directoriesAtPath:(NSString *)_path {
262 return [self directoryContentsAtPath:_path directories:YES files:NO];
265 - (NSArray *)filesAtPath:(NSString *)_path {
266 return [self directoryContentsAtPath:_path directories:NO files:YES];
269 - (NSArray *)directoryContentsAtPath:(NSString *)_path
270 directories:(BOOL)_dirs
273 id<NGImap4Folder> folder;
274 NSMutableArray *results;
279 if (![_path isAbsolutePath])
280 _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
282 if ((folder = [self _lookupFolderAtPath:[_path pathComponents]]) == nil) {
283 /* folder does not exist */
284 if (debugOn) [self debugWithFormat:@"did not find folder."];
288 results = [NSMutableArray arrayWithCapacity:64];
293 [self debugWithFormat:@" add subfolders: %@", [folder subFolders]];
295 e = [[folder subFolders] objectEnumerator];
296 while ((tmp = [e nextObject]) != nil)
297 [results addObject:[tmp name]];
302 e = [[folder messages] objectEnumerator];
303 while ((msg = [e nextObject]))
304 [results addObject:[NSString stringWithFormat:@"%d", [msg uid]]];
308 [self debugWithFormat:@" dir contents: %@", results];
312 - (NGImap4Message *)messageAtPath:(NSString *)_path {
313 id<NGImap4Folder> folder;
319 if (![_path isAbsolutePath])
320 _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
322 filename = [_path lastPathComponent];
323 _path = [_path stringByDeletingLastPathComponent];
325 if ((folder = [self _lookupFolderAtPath:[_path pathComponents]]) == nil)
328 q = [self _qualifierForFileName:filename];
329 //NSLog(@"qualifier: %@", q);
331 msgs = [folder messagesForQualifier:q maxCount:2];
332 if ([msgs count] == 0) {
333 /* no such message .. */
336 if ([msgs count] > 1) {
337 NSLog(@"multiple messages for uid %@", filename);
340 msg = [msgs objectAtIndex:0];
344 - (NSData *)contentsAtPath:(NSString *)_path {
345 return [self contentsAtPath:_path part:@""];
348 - (NSData *)contentsAtPath:(NSString *)_path part:(NSString *)_part {
349 id<NGImap4Folder> folder;
352 if (![_path isAbsolutePath])
353 _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
355 fileName = [_path lastPathComponent];
356 _path = [_path stringByDeletingLastPathComponent];
358 if ((folder = [self _lookupFolderAtPath:[_path pathComponents]]) == nil)
361 if (![folder respondsToSelector:@selector(blobForUid:part:)])
364 return [(NGImap4Folder *)folder blobForUid:[fileName unsignedIntValue]
368 - (BOOL)fileExistsAtPath:(NSString *)_path {
370 return [self fileExistsAtPath:_path isDirectory:&isDir];
372 - (BOOL)fileExistsAtPath:(NSString *)_path isDirectory:(BOOL *)_isDir {
373 id<NGImap4Folder> folder;
377 if (![_path isAbsolutePath])
378 _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
380 if ([_path isEqualToString:@"/"]) {
381 if (_isDir) *_isDir = YES;
382 return self->rootFolder != nil ? YES : NO;
385 fileName = [_path lastPathComponent];
386 _path = [_path stringByDeletingLastPathComponent];
387 paths = [_path pathComponents];
388 folder = [self _lookupFolderAtPath:paths];
391 [self debugWithFormat:
392 @"base '%@' file '%@' paths: %@, file %@, folder %@",
393 _path, fileName, paths, fileName, folder];
399 if ([fileName isEqualToString:@"."]) {
403 if ([fileName isEqualToString:@".."]) {
408 // TODO: what is the caseInsensitive good for?
409 if (debugOn) [self debugWithFormat:@" lookup '%@' in %@", fileName, folder];
410 if ([folder subFolderWithName:fileName caseInsensitive:NO] != nil) {
417 /* check for message 'file' */
422 q = [self _qualifierForFileName:fileName];
423 msgs = [folder messagesForQualifier:q maxCount:2];
425 if ([msgs count] > 0)
432 - (BOOL)isReadableFileAtPath:(NSString *)_path {
433 return [self fileExistsAtPath:_path];
435 - (BOOL)isWritableFileAtPath:(NSString *)_path {
436 return [self fileExistsAtPath:_path];
438 - (BOOL)isExecutableFileAtPath:(NSString *)_path {
441 - (BOOL)isDeletableFileAtPath:(NSString *)_path {
442 return [self fileExistsAtPath:_path];
447 - (NSDictionary *)_fileAttributesOfFolder:(id<NGImap4Folder>)_folder {
448 NSMutableDictionary *attrs;
451 attrs = [NSMutableDictionary dictionaryWithCapacity:12];
453 if ((tmp = [_folder absoluteName]))
454 [attrs setObject:tmp forKey:NSFilePath];
455 if ((tmp = [_folder name]))
456 [attrs setObject:tmp forKey:NSFileName];
457 if ((tmp = [[_folder parentFolder] absoluteName]))
458 [attrs setObject:tmp forKey:NSParentPath];
460 [attrs setObject:[self->imapContext login] forKey:NSFileOwnerAccountName];
461 [attrs setObject:NSFileTypeDirectory forKey:NSFileType];
466 - (NSDictionary *)_fileAttributesOfMessage:(NGImap4Message *)_msg
467 inFolder:(NGImap4Folder *)_folder
469 NSMutableDictionary *attrs;
470 NSString *fileName, *filePath;
471 NSDictionary *headers;
474 static NGMimeHeaderNames *Fields = NULL;
477 Fields = (NGMimeHeaderNames *)[NGMimePartParser headerFieldNames];
480 headers = (id)[_msg headers];
481 //NSLog(@"headers: %@", headers);
483 fileName = [NSString stringWithFormat:@"%i", [_msg uid]];
484 filePath = [[_folder absoluteName] stringByAppendingPathComponent:fileName];
485 attrs = [NSMutableDictionary dictionaryWithCapacity:12];
487 if (filePath) [attrs setObject:filePath forKey:NSFilePath];
488 if (fileName) [attrs setObject:fileName forKey:NSFileName];
490 if ((tmp = [_folder absoluteName]))
491 [attrs setObject:tmp forKey:NSParentPath];
493 if ((tmp = [headers objectForKey:@"date"])) {
494 /* should parse date ? */
495 NSCalendarDate *date;
497 if ([tmp isKindOfClass:[NSDate class]])
500 NGMimeRFC822DateHeaderFieldParser *parser;
501 parser = [[NGMimeRFC822DateHeaderFieldParser alloc] init];
502 date = [parser parseValue:
503 [tmp dataUsingEncoding:[NSString defaultCStringEncoding]]
504 ofHeaderField:@"date"];
509 NSLog(@"couldn't parse date: %@", tmp);
511 [attrs setObject:date ? date : tmp forKey:NSFileModificationDate];
514 if ((tmp = [headers objectForKey:Fields->from]))
515 [attrs setObject:tmp forKey:@"NGImapFrom"];
516 if ((tmp = [headers objectForKey:Fields->xMailer]))
517 [attrs setObject:tmp forKey:@"NGImapMailer"];
518 if ((tmp = [headers objectForKey:Fields->organization]))
519 [attrs setObject:tmp forKey:@"NGImapOrganization"];
520 if ((tmp = [headers objectForKey:Fields->to]))
521 [attrs setObject:tmp forKey:@"NGImapReceiver"];
522 if ((tmp = [headers objectForKey:Fields->subject]))
523 [attrs setObject:tmp forKey:@"NGImapSubject"];
524 if ((tmp = [headers objectForKey:Fields->contentType]))
525 [attrs setObject:tmp forKey:@"NGImapContentType"];
527 [attrs setObject:[self->imapContext login] forKey:NSFileOwnerAccountName];
528 [attrs setObject:[NSNumber numberWithInt:[_msg size]] forKey:NSFileSize];
530 if ((tmp = [headers objectForKey:Fields->messageID]))
531 [attrs setObject:tmp forKey:@"NSFileIdentifier"];
533 [attrs setObject:[NSNumber numberWithInt:[_msg uid]]
534 forKey:@"NSFileIdentifier"];
537 [attrs setObject:NSFileTypeRegular forKey:NSFileType];
542 - (NSDictionary *)fileAttributesAtPath:(NSString *)_path
543 traverseLink:(BOOL)flag
546 id<NGImap4Folder> folder, sfolder;
548 if (![_path isAbsolutePath])
549 _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
551 fileName = [_path lastPathComponent];
552 _path = [_path stringByDeletingLastPathComponent];
554 if ((folder = [self _lookupFolderAtPath:[_path pathComponents]]) == nil)
557 /* check for folder */
559 if ([fileName isEqualToString:@"."])
560 return [self _fileAttributesOfFolder:folder];
561 if ([fileName isEqualToString:@".."])
562 return [self _fileAttributesOfFolder:[folder parentFolder]];
564 if ((sfolder = [folder subFolderWithName:fileName caseInsensitive:NO]))
565 return [self _fileAttributesOfFolder:sfolder];
567 /* check for messages */
569 if (![folder isKindOfClass:[NGImap4Folder class]])
576 q = [self _qualifierForFileName:fileName];
577 msgs = [folder messagesForQualifier:q maxCount:2];
579 if ([msgs count] == 0) {
580 /* msg does not exist */
581 //NSLog(@"did not find msg for qualifier %@ in folder %@", q, folder);
585 return [self _fileAttributesOfMessage:[msgs objectAtIndex:0]
586 inFolder:(NGImap4Folder *)folder];
590 - (NSDictionary *)fileSystemAttributesAtPath:(NSString *)_path {
591 NSMutableDictionary *dict;
594 dict = [NSMutableDictionary dictionaryWithCapacity:12];
596 if ((tmp = [self->imapContext host]))
597 [dict setObject:tmp forKey:@"host"];
598 if ((tmp = [self->imapContext login]))
599 [dict setObject:tmp forKey:@"login"];
600 if ((tmp = [self->imapContext serverName]))
601 [dict setObject:tmp forKey:@"serverName"];
602 if ((tmp = [self->imapContext serverKind]))
603 [dict setObject:tmp forKey:@"serverKind"];
604 if ((tmp = [self->imapContext serverVersion]))
605 [dict setObject:tmp forKey:@"serverVersion"];
606 if ((tmp = [self->imapContext serverTag]))
607 [dict setObject:tmp forKey:@"serverTag"];
609 if ((tmp = [[self->imapContext trashFolder] absoluteName]))
610 [dict setObject:tmp forKey:@"trashFolderPath"];
611 if ((tmp = [[self->imapContext sentFolder] absoluteName]))
612 [dict setObject:tmp forKey:@"sentFolderPath"];
613 if ((tmp = [[self->imapContext draftsFolder] absoluteName]))
614 [dict setObject:tmp forKey:@"draftsFolderPath"];
615 if ((tmp = [[self->imapContext inboxFolder] absoluteName]))
616 [dict setObject:tmp forKey:@"inboxFolderPath"];
617 if ((tmp = [[self->imapContext serverRoot] absoluteName]))
618 [dict setObject:tmp forKey:@"rootFolderPath"];
624 - (EODataSource *)dataSourceAtPath:(NSString *)_path {
627 if ((f = [self _lookupFolderAtPath:[_path pathComponents]]) == nil)
630 // TODO: check whether 'f' is really an NGImap4Folder?
631 return [[[NGImap4DataSource alloc] initWithFolder:(NGImap4Folder *)f]
636 return [self->imapContext isInSyncMode];
639 - (void)setSyncMode:(BOOL)_bool {
641 [self->imapContext enterSyncMode];
643 [self->imapContext leaveSyncMode];
648 - (BOOL)isDebuggingEnabled {
654 - (void)appendAttributesToDescription:(NSMutableString *)ms {
655 [ms appendFormat:@" ctx=%@", self->imapContext];
656 [ms appendFormat:@" root=%@", self->rootFolder];
657 [ms appendFormat:@" wd=%@", self->currentFolder];
660 - (NSString *)description {
663 ms = [NSMutableString stringWithCapacity:64];
665 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
666 [self appendAttributesToDescription:ms];
667 [ms appendString:@">"];
671 @end /* NGImap4FileManager */