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