]> err.no Git - sope/blob - sope-mime/NGImap4/NGImap4FileManager.m
synced with latest additions and bumped framework versions
[sope] / sope-mime / NGImap4 / NGImap4FileManager.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
22 #include "NGImap4FileManager.h"
23 #include <NGImap4/NGImap4Folder.h>
24 #include <NGImap4/NGImap4Context.h>
25 #include <NGImap4/NGImap4Message.h>
26 #include <NGExtensions/NGFileFolderInfoDataSource.h>
27 #include "imCommon.h"
28 #include <NGImap4/NGImap4DataSource.h>
29
30 @interface NGImap4FileManager(Privates)
31
32 - (BOOL)loginWithUser:(NSString *)_user
33   password:(NSString *)_pwd
34   host:(NSString *)_host;
35
36 - (id<NGImap4Folder>)_lookupFolderAtPath:(NSArray *)_paths;
37 - (id<NGImap4Folder>)_lookupFolderAtPathString:(NSString *)_path;
38
39 - (EOQualifier *)_qualifierForFileName:(NSString *)_filename;
40
41 @end
42
43 @implementation NGImap4FileManager
44
45 static BOOL debugOn = NO;
46
47 + (int)version {
48   return [super version] + 0 /* v0 */;
49 }
50 + (void)initialize {
51   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
52   
53   NSAssert2([super version] == 0,
54             @"invalid superclass (%@) version %i !",
55             NSStringFromClass([self superclass]), [super version]);
56   
57   if ((debugOn = [ud boolForKey:@"NGImap4FileManagerDebugEnabled"]))
58     NSLog(@"NGImap4FileManager debugging is enabled.");
59 }
60
61 - (id)initWithUser:(NSString *)_user
62   password:(NSString *)_pwd
63   host:(NSString *)_host
64 {
65   if ((self = [super init])) {
66     if (![self loginWithUser:_user password:_pwd host:_host]) {
67       [self logWithFormat:@"could not login user '%@' host '%@'.", 
68               _user, _host];
69       [self release];
70       return nil;
71     }
72   }
73   return self;
74 }
75 - (id)init {
76   return [self initWithUser:nil password:nil host:nil];
77 }
78 - (id)initWithURL:(NSURL *)_url {
79   if (_url == nil) {
80     [self release];
81     return nil;
82   }
83   
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];
88       [self release];
89       return nil;
90     }
91     
92     [self->imapContext enterSyncMode];
93     
94     if (![self->imapContext openConnection]) {
95       [self logWithFormat:@"ERROR: could not open IMAP4 connection ..."];
96       [self release];
97       return nil;
98     }
99     
100     if ((self->rootFolder = [[self->imapContext serverRoot] retain]) == nil) {
101       [self logWithFormat:@"ERROR: did not find root folder ..."];
102       [self release];
103       return nil;
104     }
105     if ((self->currentFolder=[[self->imapContext inboxFolder] retain])==nil) {
106       [self logWithFormat:@"ERROR: did not find inbox folder ..."];
107       [self release];
108       return nil;
109     }
110     
111     if (![[_url path] isEqualToString:@"/"]) {
112       if (![self changeCurrentDirectoryPath:[_url path]]) {
113         [self logWithFormat:@"ERROR: couldn't change to URL path: %@", _url];
114         [self release];
115         return nil;
116       }
117     }
118   }
119   return self;
120 }
121
122 - (id)imapContext {
123   return self->imapContext;
124 }
125
126 - (void)dealloc {
127   [self->currentFolder release];
128   [self->imapContext   release];
129   [self->rootFolder    release];
130   [super dealloc];
131 }
132
133 /* operations */
134
135 - (BOOL)loginWithUser:(NSString *)_user
136   password:(NSString *)_pwd
137   host:(NSString *)_host
138 {
139   NSException  *loginException;
140   NSDictionary *conDict;
141   
142   [self->imapContext   release]; self->imapContext   = nil;
143   [self->rootFolder    release]; self->rootFolder    = nil;
144   [self->currentFolder release]; self->currentFolder = nil;
145   
146   conDict = [NSDictionary dictionaryWithObjectsAndKeys:
147                             _user ? _user : @"anonymous", @"login",
148                             _pwd  ? _pwd  : @"",          @"passwd",
149                             _host ? _host : @"localhost", @"host",
150                             nil];
151   
152   loginException = nil;
153   
154   self->imapContext =
155     [[NGImap4Context alloc] initWithConnectionDictionary:conDict];
156   [self->imapContext enterSyncMode];
157   
158   if (![self->imapContext openConnection])
159     return NO;
160   
161   if ((self->rootFolder = [[self->imapContext serverRoot] retain]) == nil)
162     return NO;
163   if ((self->currentFolder = [[self->imapContext inboxFolder] retain]) == nil)
164     return NO;
165   
166   return YES;
167 }
168
169 /* internals */
170
171 - (id<NGImap4Folder>)_lookupFolderAtPath:(NSArray *)_paths {
172   id<NGImap4Folder> folder;
173   NSEnumerator  *e;
174   NSString      *path;
175
176   folder = self->currentFolder;
177
178   e = [_paths objectEnumerator];
179   while ((path = [e nextObject]) && (folder != nil)) {
180     if ([path isEqualToString:@"."])
181       continue;
182     if ([path isEqualToString:@""])
183       continue;
184     if ([path isEqualToString:@".."]) {
185       folder = [folder parentFolder];
186       continue;
187     }
188     if ([path isEqualToString:@"/"]) {
189       folder = self->rootFolder;
190       continue;
191     }
192     
193     folder = [folder subFolderWithName:path caseInsensitive:NO];
194   }
195
196   return folder;
197 }
198 - (id<NGImap4Folder>)_lookupFolderAtPathString:(NSString *)_path {
199   return [self _lookupFolderAtPath:[_path pathComponents]];
200 }
201
202 - (EOQualifier *)_qualifierForFileName:(NSString *)_filename {
203   return [EOQualifier qualifierWithQualifierFormat:@"uid=%@", _filename];
204 }
205
206 /* directory ops */
207
208 - (BOOL)createDirectoryAtPath:(NSString *)_path
209   attributes:(NSDictionary *)_attributes
210 {
211   id<NGImap4Folder> folder;
212   NSString *filename;
213   
214   if (![_path isAbsolutePath])
215     _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
216   
217   filename = [_path lastPathComponent];
218   _path    = [_path stringByDeletingLastPathComponent];
219   
220   if ((folder = [self _lookupFolderAtPathString:_path]) == nil)
221     return NO;
222   
223   return [folder createSubFolderWithName:filename];
224 }
225
226 - (BOOL)changeCurrentDirectoryPath:(NSString *)_path {
227   id<NGImap4Folder> folder;
228
229   if ([_path length] == 0)
230     return NO;
231
232   if (![_path isAbsolutePath]) {
233     _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
234   }
235
236   if ((folder = [self _lookupFolderAtPathString:_path]) == nil)
237     return NO;
238
239   ASSIGN(self->currentFolder, folder);
240
241   return YES;
242 }
243
244 - (NSString *)currentDirectoryPath {
245   if ([self->currentFolder isEqual:[self->currentFolder parentFolder]] ||
246       [self->currentFolder parentFolder] == nil)
247     return @"/";
248   else return [self->currentFolder absoluteName];
249 }
250
251 - (NGImap4Folder *)currentFolder {
252   return self->currentFolder;
253 }
254
255 /* operations */
256
257 - (NSArray *)directoryContentsAtPath:(NSString *)_path {
258   return [self directoryContentsAtPath:_path directories:YES files:YES];
259 }
260
261 - (NSArray *)directoriesAtPath:(NSString *)_path {
262   return [self directoryContentsAtPath:_path directories:YES files:NO];
263 }
264
265 - (NSArray *)filesAtPath:(NSString *)_path {
266   return [self directoryContentsAtPath:_path directories:NO files:YES];
267 }
268
269 - (NSArray *)directoryContentsAtPath:(NSString *)_path
270   directories:(BOOL)_dirs
271   files:(BOOL)_files
272 {
273   id<NGImap4Folder> folder;
274   NSMutableArray *results;
275   NSEnumerator   *e;
276   NGImap4Folder  *tmp;
277   NGImap4Message *msg;
278   
279   if (![_path isAbsolutePath])
280     _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
281
282   if ((folder = [self _lookupFolderAtPath:[_path pathComponents]]) == nil) {
283     /* folder does not exist */
284     if (debugOn) [self debugWithFormat:@"did not find folder."];
285     return nil;
286   }
287   
288   results = [NSMutableArray arrayWithCapacity:64];
289   
290   /* add folders */
291   if (_dirs) {
292     if (debugOn) 
293       [self debugWithFormat:@"  add subfolders: %@", [folder subFolders]];
294     
295     e = [[folder subFolders] objectEnumerator];
296     while ((tmp = [e nextObject]) != nil)
297       [results addObject:[tmp name]];
298   }
299
300   /* add messages */
301   if (_files) {
302     e = [[folder messages] objectEnumerator];
303     while ((msg = [e nextObject]))
304       [results addObject:[NSString stringWithFormat:@"%d", [msg uid]]];
305   }
306
307   if (debugOn) 
308     [self debugWithFormat:@"  dir contents: %@", results];
309   return results;
310 }
311
312 - (NGImap4Message *)messageAtPath:(NSString *)_path {
313   id<NGImap4Folder> folder;
314   NSString       *filename;
315   EOQualifier    *q;
316   NSArray        *msgs;
317   NGImap4Message *msg;
318
319   if (![_path isAbsolutePath])
320     _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
321   
322   filename = [_path lastPathComponent];
323   _path    = [_path stringByDeletingLastPathComponent];
324
325   if ((folder = [self _lookupFolderAtPath:[_path pathComponents]]) == nil)
326     return nil;
327
328   q = [self _qualifierForFileName:filename];
329   //NSLog(@"qualifier: %@", q);
330
331   msgs = [folder messagesForQualifier:q maxCount:2];
332   if ([msgs count] == 0) {
333     /* no such message .. */
334     return nil;
335   }
336   if ([msgs count] > 1) {
337     NSLog(@"multiple messages for uid %@", filename);
338     return nil;
339   }
340   msg = [msgs objectAtIndex:0];
341   return msg;
342 }
343
344 - (NSData *)contentsAtPath:(NSString *)_path {
345   return [self contentsAtPath:_path part:@""];
346 }
347
348 - (NSData *)contentsAtPath:(NSString *)_path part:(NSString *)_part {
349   id<NGImap4Folder> folder;
350   NSString          *fileName;
351
352   if (![_path isAbsolutePath])
353     _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
354
355   fileName = [_path lastPathComponent];
356   _path    = [_path stringByDeletingLastPathComponent];
357
358   if ((folder = [self _lookupFolderAtPath:[_path pathComponents]]) == nil)
359     return nil;
360   
361   if (![folder respondsToSelector:@selector(blobForUid:part:)])
362     return nil;
363   
364   return [(NGImap4Folder *)folder blobForUid:[fileName unsignedIntValue] 
365                            part:_part];
366 }
367
368 - (BOOL)fileExistsAtPath:(NSString *)_path {
369   BOOL isDir;
370   return [self fileExistsAtPath:_path isDirectory:&isDir];
371 }
372 - (BOOL)fileExistsAtPath:(NSString *)_path isDirectory:(BOOL *)_isDir {
373   id<NGImap4Folder> folder;
374   NSArray  *paths;
375   NSString *fileName;
376   
377   if (![_path isAbsolutePath])
378     _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
379
380   if ([_path isEqualToString:@"/"]) {
381     if (_isDir) *_isDir = YES;
382     return self->rootFolder != nil ? YES : NO;
383   }
384   
385   fileName = [_path lastPathComponent];
386   _path    = [_path stringByDeletingLastPathComponent];
387   paths    = [_path pathComponents];
388   folder   = [self _lookupFolderAtPath:paths];
389   
390   if (debugOn) {
391     [self debugWithFormat:
392             @"base '%@' file '%@' paths: %@, file %@, folder %@", 
393             _path, fileName, paths, fileName, folder];
394   }
395   
396   if (folder == nil)
397     return NO;
398
399   if ([fileName isEqualToString:@"."]) {
400     *_isDir = YES;
401     return YES;
402   }
403   if ([fileName isEqualToString:@".."]) {
404     *_isDir = YES;
405     return YES;
406   }
407   
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) {
411     *_isDir = YES;
412     return YES;
413   }
414   
415   *_isDir = NO;
416
417   /* check for message 'file' */
418   {
419     EOQualifier *q;
420     NSArray *msgs;
421
422     q = [self _qualifierForFileName:fileName];
423     msgs = [folder messagesForQualifier:q maxCount:2];
424
425     if ([msgs count] > 0)
426       return YES;
427   }
428   
429   return NO;
430 }
431
432 - (BOOL)isReadableFileAtPath:(NSString *)_path {
433   return [self fileExistsAtPath:_path];
434 }
435 - (BOOL)isWritableFileAtPath:(NSString *)_path {
436   return [self fileExistsAtPath:_path];
437 }
438 - (BOOL)isExecutableFileAtPath:(NSString *)_path {
439   return NO;
440 }
441 - (BOOL)isDeletableFileAtPath:(NSString *)_path {
442   return [self fileExistsAtPath:_path];
443 }
444
445 /* attributes */
446
447 - (NSDictionary *)_fileAttributesOfFolder:(id<NGImap4Folder>)_folder {
448   NSMutableDictionary *attrs;
449   id tmp;
450
451   attrs = [NSMutableDictionary dictionaryWithCapacity:12];
452   
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];
459   
460   [attrs setObject:[self->imapContext login] forKey:NSFileOwnerAccountName];
461   [attrs setObject:NSFileTypeDirectory forKey:NSFileType];
462   
463   return attrs;
464 }
465
466 - (NSDictionary *)_fileAttributesOfMessage:(NGImap4Message *)_msg
467   inFolder:(NGImap4Folder *)_folder
468 {
469   NSMutableDictionary *attrs;
470   NSString            *fileName, *filePath;
471   NSDictionary        *headers;
472   id                  tmp;
473
474   static NGMimeHeaderNames *Fields = NULL;
475
476   if (!Fields)
477     Fields = (NGMimeHeaderNames *)[NGMimePartParser headerFieldNames];
478   
479   
480   headers = (id)[_msg headers];
481   //NSLog(@"headers: %@", headers);
482   
483   fileName = [NSString stringWithFormat:@"%i", [_msg uid]];
484   filePath = [[_folder absoluteName] stringByAppendingPathComponent:fileName];
485   attrs    = [NSMutableDictionary dictionaryWithCapacity:12];
486   
487   if (filePath) [attrs setObject:filePath forKey:NSFilePath];
488   if (fileName) [attrs setObject:fileName forKey:NSFileName];
489   
490   if ((tmp = [_folder absoluteName]))
491     [attrs setObject:tmp forKey:NSParentPath];
492   
493   if ((tmp = [headers objectForKey:@"date"])) {
494     /* should parse date ? */
495     NSCalendarDate *date;
496
497     if ([tmp isKindOfClass:[NSDate class]])
498       date = tmp;
499     else {
500       NGMimeRFC822DateHeaderFieldParser *parser;
501       parser = [[NGMimeRFC822DateHeaderFieldParser alloc] init];
502       date = [parser parseValue:
503                      [tmp dataUsingEncoding:[NSString defaultCStringEncoding]]
504                      ofHeaderField:@"date"];
505       [parser release];
506     }
507     
508     if (date == nil)
509       NSLog(@"couldn't parse date: %@", tmp);
510     
511     [attrs setObject:date ? date : tmp forKey:NSFileModificationDate];
512   }
513   
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"];
526   
527   [attrs setObject:[self->imapContext login] forKey:NSFileOwnerAccountName];
528   [attrs setObject:[NSNumber numberWithInt:[_msg size]] forKey:NSFileSize];
529
530   if ((tmp = [headers objectForKey:Fields->messageID]))
531     [attrs setObject:tmp forKey:@"NSFileIdentifier"];
532   else {
533     [attrs setObject:[NSNumber numberWithInt:[_msg uid]]
534            forKey:@"NSFileIdentifier"];
535   }
536   
537   [attrs setObject:NSFileTypeRegular forKey:NSFileType];
538   
539   return attrs;
540 }
541
542 - (NSDictionary *)fileAttributesAtPath:(NSString *)_path
543   traverseLink:(BOOL)flag
544 {
545   NSString      *fileName;
546   id<NGImap4Folder> folder, sfolder;
547   
548   if (![_path isAbsolutePath])
549     _path = [[self currentDirectoryPath] stringByAppendingPathComponent:_path];
550   
551   fileName = [_path lastPathComponent];
552   _path    = [_path stringByDeletingLastPathComponent];
553   
554   if ((folder = [self _lookupFolderAtPath:[_path pathComponents]]) == nil)
555     return nil;
556   
557   /* check for folder */
558   
559   if ([fileName isEqualToString:@"."])
560     return [self _fileAttributesOfFolder:folder];
561   if ([fileName isEqualToString:@".."])
562     return [self _fileAttributesOfFolder:[folder parentFolder]];
563   
564   if ((sfolder = [folder subFolderWithName:fileName caseInsensitive:NO])) 
565     return [self _fileAttributesOfFolder:sfolder];
566   
567   /* check for messages */
568   
569   if (![folder isKindOfClass:[NGImap4Folder class]])
570     return nil;
571   
572   {
573     EOQualifier *q;
574     NSArray *msgs;
575     
576     q = [self _qualifierForFileName:fileName];
577     msgs = [folder messagesForQualifier:q maxCount:2];
578
579     if ([msgs count] == 0) {
580       /* msg does not exist */
581       //NSLog(@"did not find msg for qualifier %@ in folder %@", q, folder);
582       return nil;
583     }
584     
585     return [self _fileAttributesOfMessage:[msgs objectAtIndex:0]
586                  inFolder:(NGImap4Folder *)folder];
587   }
588 }
589
590 - (NSDictionary *)fileSystemAttributesAtPath:(NSString *)_path {
591   NSMutableDictionary *dict;
592   id tmp;
593
594   dict = [NSMutableDictionary dictionaryWithCapacity:12];
595
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"];
608
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"];
619   
620   return dict;
621 }
622
623
624 - (EODataSource *)dataSourceAtPath:(NSString *)_path {
625   id<NGImap4Folder> f;
626   
627   if ((f = [self _lookupFolderAtPath:[_path pathComponents]]) == nil)
628     return nil;
629   
630   // TODO: check whether 'f' is really an NGImap4Folder?
631   return [[[NGImap4DataSource alloc] initWithFolder:(NGImap4Folder *)f] 
632            autorelease];
633 }
634
635 - (BOOL)syncMode {
636   return [self->imapContext isInSyncMode];
637 }
638
639 - (void)setSyncMode:(BOOL)_bool {
640   if (_bool)
641     [self->imapContext enterSyncMode];
642   else
643     [self->imapContext leaveSyncMode];
644 }
645
646 /* debugging */
647
648 - (BOOL)isDebuggingEnabled {
649   return debugOn;
650 }
651
652 /* description */
653
654 - (void)appendAttributesToDescription:(NSMutableString *)ms {
655   [ms appendFormat:@" ctx=%@",  self->imapContext];
656   [ms appendFormat:@" root=%@", self->rootFolder];
657   [ms appendFormat:@" wd=%@",   self->currentFolder];
658 }
659
660 - (NSString *)description {
661   NSMutableString *ms;
662
663   ms = [NSMutableString stringWithCapacity:64];
664   
665   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
666   [self appendAttributesToDescription:ms];
667   [ms appendString:@">"];
668   return ms;
669 }
670
671 @end /* NGImap4FileManager */