/* copyright 2002 Alexander Malmberg */ /* TODO: cleanups, better organization */ #include #include #include #include #include #include #include #include #include #include "MsgDB.h" #define HASH_SIZE (1031*5) NSString *MsgDB_MsgDStatusNotification=@"MsgDB_MsgDStatusNotification", *MsgDB_MsgMetaChangeNotification=@"MsgDB_MsgMetaChangeNotification", *MsgDB_LogMessageNotification=@"MsgDB_LogMessageNotification", *MsgDB_FolderAddMsgNotification=@"MsgDB_FolderAddMsgNotification", *MsgDB_FolderAddNotification=@"MsgDB_FolderAddNotification", *MsgDB_SourceAddNotification=@"MsgDB_SourceAddNotification", *MsgDB_SourceRemoveNotification=@"MsgDB_SourceRemoveNotification", *MsgDB_SourceChangeNotification=@"MsgDB_SourceChangeNotification"; @implementation MidNotification + notificationWithName: (NSString *)n object: (id)o mid: (msg_id_t)m folder: (NSObject *)f header: (int)h { MidNotification *mn; mn=[[self alloc] initWithName: n object: o mid: m folder: f header: h]; return [mn autorelease]; } - initWithName: (NSString *)n object: (id)o mid: (msg_id_t)m folder: (NSObject *)f header: (int)h { if (!(self=[super init])) return nil; name=[n copy]; object=[o retain]; mid=m; folder=f; header=h; return self; } -(void) dealloc { DESTROY(name); DESTROY(object); [super dealloc]; } -(NSString *) name { return name; } -(id) object { return object; } -(NSDictionary *) userInfo { return nil; } -(msg_id_t) mid { return mid; } -(NSObject *) folder { return folder; } -(const char *) headerName { return [[self object] getMetaHeaderName: header]; } @end @interface NSDictionary (MsgDB_extensions) -(id) keyForObject: (id)o; @end @implementation NSDictionary (MsgDB_extensions) -(id) keyForObject: (id)o { NSEnumerator *e; id k; e=[self keyEnumerator]; for (;(k=[e nextObject]);) if (o==[self objectForKey: k]) return k; return nil; } @end typedef struct msgdb_hash_s { int size; msg_id_t *h; } hash_t; typedef struct { int mhid; char *value; } meta_header_t; typedef struct message_s { char *message_id; meta_header_t *mhs; int num_mhs; msg_id_t hash_next; int dstatus; char *data; int data_length; unsigned int src_get_id; } message_t; @interface MsgDB_Folder : NSObject { msg_id_t *msg; int num_msg; } -(BOOL) containsMessage: (msg_id_t)mid; -(void) addMessage: (msg_id_t)mid; -(void) removeMessage: (msg_id_t)mid; @end @implementation MsgDB_Folder - init { if (!(self=[super init])) return nil; return self; } -(void) dealloc { if (msg) { free(msg); } msg=NULL; num_msg=0; [super dealloc]; } -(BOOL) containsMessage: (msg_id_t)mid { /* should do binary search here */ int l,h,m; l=0; h=num_msg-1; while (l<=h) { m=(l+h)/2; if (msg[m]==mid) return YES; if (mid=0;i--) { if (msg[i]==mid) return; if (msg[i]i;j--) msg[j]=msg[j-1]; msg[i]=mid; num_msg++; /* int i; for (i=0;imessage_id); hv%=hash->size; m->hash_next=hash->h[hv]; hash->h[hv]=mid; } -(msg_id_t) findHash: (const char *)message_id { unsigned int hv=hash_id(message_id); msg_id_t mid; hv%=hash->size; for (mid=hash->h[hv];mid;mid=messages[mid].hash_next) if (!strcmp(message_id,messages[mid].message_id)) return mid; return 0; } @end #include @implementation MsgDB - initWithDirectory: (NSString *)adir { self=[super init]; if (!self) return nil; dir=[adir copy]; // fprintf(stderr,"init %p in %@\n",self,dir); mkdir([dir cString],0777); /* TODO */ num_messages=1; hash=malloc(sizeof(hash_t)); if (!hash) return nil; hash->size=HASH_SIZE; hash->h=malloc(sizeof(msg_id_t)*hash->size); memset(hash->h,0,sizeof(msg_id_t)*hash->size); ncenter=[NSNotificationCenter defaultCenter]; sources=[[NSMutableDictionary alloc] init]; folders=[[NSMutableDictionary alloc] init]; { int i; char buf[128]; Class c; const char *next; msg_id_t mid; NSNumber *n; NSObject *src; if (![self loadFromDisk]) [self createFromScratch]; mid=main_id; while (1) { next=[self msg_getMetaHeader: "Next-source" : mid]; if (!next) break; i=atoi(next); if (i==-1) break; snprintf(buf,sizeof(buf),"source-%i",i); mid=[self midForId: buf]; if (!mid) { fprintf(stderr,"Warning: Broken source chain!\n"); break; } // fprintf(stderr,"got '%s' type '%s'\n",buf,[self msg_getMetaHeader: "Type" : mid]); c=objc_lookup_class([self msg_getMetaHeader: "Type" : mid]); n=[[NSNumber alloc] initWithInt: i]; src=[[c alloc] initWithMsg: mid db: self]; [sources setObject: src forKey: n]; [src release]; [n release]; } last_source_id=mid; } return self; } -(void) dealloc { /* TODO: fix all the leaks */ // fprintf(stderr,"MsgDB: deallocing %p\n",self); { CREATE_AUTORELEASE_POOL(arp); NSEnumerator *e; NSObject *src; for (e=[sources objectEnumerator];(src=[e nextObject]);) [src disconnect]; #ifdef DEBUG_AUTORELEASE_COUNT fprintf(stderr,"dealloc arp contains %i objects\n",[arp autoreleaseCount]); #endif DESTROY(arp); } DESTROY(sources); DESTROY(dir); DESTROY(folders); { int i,j; message_t *m; for (i=0;imessage_id); for (j=0;jnum_mhs;j++) free(m->mhs[j].value); if (m->data) free(m->data); } } free(messages); [super dealloc]; } -(NSNotificationCenter *)notificationCenter { return ncenter; } -(void)setNotificationCenter: (NSNotificationCenter *)nc { ncenter=nc; } -(msg_id_t) midForId: (const char *)msgid { int i; message_t *m; if (hash) return [self findHash: msgid]; for (i=1,m=messages+1;imessage_id)) return i; } return 0; } -(msg_id_t) createMessageWithId: (const char *)msgid source: (NSObject *)src { message_t *m; if ([self midForId: msgid]) return 0; m=realloc(messages,sizeof(message_t)*(num_messages+1)); if (!m) abort(); messages=m; m=messages+num_messages; memset(m,0,sizeof(message_t)); m->message_id=strdup(msgid); if (!m->message_id) abort(); num_messages++; if (src) { int num=[[sources keyForObject: src] intValue]; char buf[16]; snprintf(buf,sizeof(buf),"%i",num); [self msg_setMetaHeader: "Source" value: buf : num_messages-1]; } [self addToHash: num_messages-1]; return num_messages-1; } -(void) msg_setSource: (NSObject *)src : (msg_id_t) mid { if (src) { int num=[[sources keyForObject: src] intValue]; char buf[16]; snprintf(buf,sizeof(buf),"%i",num); [self msg_setMetaHeader: "Source" value: buf : mid]; } } -(int) getMetaHeaderNum: (const char *)hname { int i; char **c; for (i=0;i=num_meta_headers) return NULL; return meta_headers[num]; } -(void) updateAll { CREATE_AUTORELEASE_POOL(arp); NSEnumerator *e; NSObject *src; for (e=[sources objectEnumerator];(src=[e nextObject]);) [src update]; #ifdef DEBUG_AUTORELEASE_COUNT fprintf(stderr,"updateAll arp contains %i objects\n",[arp autoreleaseCount]); #endif DESTROY(arp); } -(const char *)msg_getHeader: (const char *)hname : (msg_id_t)mid { char *buf; const char *c; buf=malloc(strlen(hname)+6); if (!buf) abort(); strcpy(buf,"Real-"); strcat(buf,hname); c=[self msg_getMetaHeader: buf : mid]; free(buf); if (c) return c; return NULL; /* TODO */ } -(const char *) msg_getMessageID: (msg_id_t)mid { message_t *m=messages+mid; return m->message_id; } -(const char *)msg_getMetaHeaderWithNumber: (int)hn : (msg_id_t)mid { int i; message_t *m=messages+mid; for (i=0;inum_mhs;i++) if (m->mhs[i].mhid==hn) return m->mhs[i].value; return NULL; } -(void)msg_setMetaHeaderWithNumber: (int) hn value: (const char *)value : (msg_id_t)mid { int i; char *c; message_t *m=messages+mid; meta_header_t *mh; if (!value) abort(); c=strdup(value); if (!c) abort(); for (i=0;inum_mhs;i++) if (m->mhs[i].mhid==hn) { free(m->mhs[i].value); m->mhs[i].value=c; [ncenter postNotification: [MidNotification notificationWithName: MsgDB_MsgMetaChangeNotification object: self mid: mid folder: nil header: hn]]; return; } mh=realloc(m->mhs,sizeof(meta_header_t)*(m->num_mhs+1)); if (!mh) abort(); m->mhs=mh; mh+=m->num_mhs++; mh->mhid=hn; mh->value=c; [ncenter postNotification: [MidNotification notificationWithName: MsgDB_MsgMetaChangeNotification object: self mid: mid folder: nil header: hn]]; } -(const char *)msg_getMetaHeader: (const char *)hname : (msg_id_t)mid { return [self msg_getMetaHeaderWithNumber: [self getMetaHeaderNum: hname] : mid]; } -(void)msg_setMetaHeader: (const char *)hname value: (const char *)value : (msg_id_t)mid { [self msg_setMetaHeaderWithNumber: [self getMetaHeaderNum: hname] value: value : mid]; } -(void)msg_addToFolder: (const char *)foldername : (msg_id_t)mid { const char *c; char *buf; MsgDB_Folder *f=[self createFolderWithName: [NSString stringWithCString: foldername]]; if ([f containsMessage: mid]) return; c=[self msg_getMetaHeader: "Folder" : mid]; if (c) { buf=malloc(strlen(c)+1+strlen(foldername)+1); if (!buf) abort(); sprintf(buf,"%s %s",c,foldername); [self msg_setMetaHeader: "Folder" value: buf : mid]; free(buf); } else [self msg_setMetaHeader: "Folder" value: foldername : mid]; [f addMessage: mid]; [ncenter postNotification: [MidNotification notificationWithName: MsgDB_FolderAddMsgNotification object: self mid: mid folder: f header: -1]]; } -(void)dumpMessages { int i/*,n*/; /* msg_id_t mid;*/ for (i=1;isize,num_messages); for (i=0;isize;i++) { fprintf(stderr,"%03x:",i); n=0; for (mid=hash->h[i];mid;mid=messages[mid].hash_next) // fprintf(stderr," %i",mid); n++; fprintf(stderr," %i\n",n); } #endif fprintf(stderr,"..\n"); } -(void)dumpMessage: (msg_id_t)mid { message_t *m; int i; printf("-- message %i\n",mid); if (mid<0 || mid>=num_messages) { printf("no such message\n"); return; } m=messages+mid; printf("Message-Id: %s\n",m->message_id); for (i=0;inum_mhs;i++) printf("%2i : %3i '%s' '%s'\n",i,m->mhs[i].mhid,[self getMetaHeaderName: m->mhs[i].mhid],m->mhs[i].value); } -(void) syncToDisk { CREATE_AUTORELEASE_POOL(arp); const char *fname= [[[dir stringByAppendingPathComponent: @"metahead.txt"] stringByStandardizingPath] fileSystemRepresentation]; FILE *f; int i,j; message_t *m; // fprintf(stderr,"saving to '%s'\n",fname); f=fopen(fname,"wt"); if (!f) { fprintf(stderr,"couldn't save to '%s': %m\n",fname); return; } fprintf(f,"%i\n",num_meta_headers); for (i=0;inum_mhs,strlen(m->message_id)); fwrite(m->message_id,1,strlen(m->message_id),f); fputc('\n',f); for (j=0;jnum_mhs;j++) { fprintf(f,"%i %i ",m->mhs[j].mhid,strlen(m->mhs[j].value)); fwrite(m->mhs[j].value,1,strlen(m->mhs[j].value),f); fputc('\n',f); } } fclose(f); #ifdef DEBUG_AUTORELEASE_COUNT fprintf(stderr,"syncToDisk arp contains %i objects\n",[arp autoreleaseCount]); #endif DESTROY(arp); } -(int) loadFromDisk { CREATE_AUTORELEASE_POOL(arp); const char *fname= [[[dir stringByAppendingPathComponent: @"metahead.txt"] stringByStandardizingPath] fileSystemRepresentation]; FILE *f; int i,j,k,l; message_t *m; int *mhidx; char **mhname; int nmh; f=fopen(fname,"rt"); if (!f) return 0; if (fscanf(f,"%i\n",&nmh)!=1) { fprintf(stderr,"parse error\n"); return 0; } mhidx=malloc(nmh*sizeof(int)); mhname=malloc(nmh*sizeof(char *)); if (!mhidx || !mhname) abort(); for (i=0;inum_mhs,&l); m->message_id=malloc(l+1); if (!m->message_id) abort(); fread(m->message_id,1,l,f); m->message_id[l]=0; fgetc(f); m->mhs=malloc(sizeof(meta_header_t)*m->num_mhs); if (!m->mhs) abort(); for (j=0;jnum_mhs;j++) { fscanf(f,"%i %i",&k,&l); fgetc(f); if (mhidx[k]==-1) mhidx[k]=[self getMetaHeaderNum: mhname[k]]; m->mhs[j].mhid=mhidx[k]; m->mhs[j].value=malloc(l+1); if (!m->mhs[j].value) abort(); fread(m->mhs[j].value,1,l,f); m->mhs[j].value[l]=0; fgetc(f); } } { char **folder_names=NULL; MsgDB_Folder **f=NULL; int nfn=0; char *c,*d; int j; int idx=[self getMetaHeaderNum: "Folder"]; for (i=1,m=messages+1;inum_mhs;j++) if (m->mhs[j].mhid==idx) break; if (j==m->num_mhs) continue; for (c=m->mhs[j].value;*c;) { for (d=c;*d && *d!=' ';d++) ; for (j=0;jdstatus==dstatus) return; m->dstatus=dstatus; [ncenter postNotification: [MidNotification notificationWithName: MsgDB_MsgDStatusNotification object: self mid: mid folder: nil header: -1]]; } -(id) msg_source: (msg_id_t)mid { NSNumber *n; id src; const char *c; c=[self msg_getMetaHeader: "Source" : mid]; if (!c) return nil; n=[[NSNumber alloc] initWithInt: atoi(c)]; src=[sources objectForKey: n]; [n release]; if (!src) return nil; return src; } -(void)msg_wantData: (msg_id_t)mid priority: (int)pri { id src; message_t *m=&messages[mid]; if (m->dstatus==DSTATUS_DATA) return; if (m->dstatus==DSTATUS_PENDING) return; src=[self msg_source: mid]; if (!src) { [self msgp_setDStatus: DSTATUS_NOSOURCE : mid]; return; } [self msgp_setDStatus: DSTATUS_PENDING : mid]; m->src_get_id=[src getMessage: mid priority: pri]; } -(void) msg_cancelWantData: (msg_id_t)mid { id src; message_t *m=&messages[mid]; if (!m->src_get_id) return; src=[self msg_source: mid]; if ([src cancelGetMessage: mid id: m->src_get_id]) { m->src_get_id=0; m->dstatus=DSTATUS_NODATA; } } -(void)msg_needData: (msg_id_t)mid { [self msg_wantData: mid priority: 10]; } -(int)msg_getData: (const unsigned char **)data length: (int *)length : (msg_id_t)mid { message_t *m=&messages[mid]; if (m->dstatus==DSTATUS_DATA) { *data=m->data; *length=m->data_length; return 1; } if (m->dstatus==DSTATUS_NODATA) { // [self msg_needData: mid]; /* TODO? */ *data=NULL; *length=0; } return m->dstatus; } -(int)msg_dstatus: (msg_id_t)mid { return messages[mid].dstatus; } -(void) setMessageData: (unsigned char *)d length: (int)l : (msg_id_t) mid { message_t *m=&messages[mid]; m->src_get_id=0; if (l==-1) { /* if we already have data we keep it and ignore the error */ if (m->dstatus==DSTATUS_DATA) return; [self msgp_setDStatus: DSTATUS_ERROR : mid]; return; } if (m->dstatus==DSTATUS_DATA) { free(m->data); m->data=NULL; m->data_length=0; m->dstatus=DSTATUS_NODATA; } m->data=d; m->data_length=l; [self msgp_setDStatus: DSTATUS_DATA : mid]; /* If we don't already have headers for this message we parse them from the data. We're only interested in some headers, though, and we want the raw header data, so we parse manually. It won't be less accurate than xover headers. */ /* TODO: re-parse anyway? */ // if (![self msg_getMetaHeader: "Real-Message-ID" : mid]) { unsigned char *b,*end; const char *c; NSMutableData *header=[[NSMutableData alloc] initWithCapacity: 100]; NSMutableData *value=[[NSMutableData alloc] initWithCapacity: 100]; b=m->data; end=b+m->data_length; while (b=end) break; while (1) { if (*b=='\n' && b+132 || b[1]=='\n')) break; if (*b=='\n' || *b=='\r') { b++; continue; } [value appendBytes: b length: 1]; b++; } b++; [header appendBytes: "\x0" length: 1]; [value appendBytes: "\x0" length: 1]; c=[header bytes]; if (!strcmp(c,"Real-Subject") || !strcmp(c,"Real-Message-ID") || !strcmp(c,"Real-From") || !strcmp(c,"Real-Date") || !strcmp(c,"Real-References") || !strcmp(c,"Real-Bytes") || !strcmp(c,"Real-Lines") ) { [self msg_setMetaHeader: c value: [value bytes] : mid]; } } DESTROY(header); DESTROY(value); } } -(void) createFromScratch { main_id=[self createMessageWithId: "main" source: nil]; } -(NSArray *) sources { return [sources allValues]; } -(NSObject *) addSourceOfType: (Class)c { msg_id_t mid; int idx; const char *b; char buf[64]; NSObject *src; NSNumber *n; b=[self msg_getMetaHeader: "Max-source" : main_id]; if (!b) idx=1; else idx=atoi(b)+1; sprintf(buf,"%i",idx); [self msg_setMetaHeader: "Max-source" value: buf : main_id]; [self msg_setMetaHeader: "Next-source" value: buf : last_source_id]; sprintf(buf,"source-%i",idx); last_source_id=mid=[self createMessageWithId: buf source: nil]; if (!mid) { /* TODO */ abort(); } [self msg_setMetaHeader: "Type" value: [NSStringFromClass(c) cString] : mid]; n=[[NSNumber alloc] initWithInt: idx]; src=[[c alloc] initWithMsg: mid db: self]; [sources setObject: src forKey: n]; [src autorelease]; [n release]; [ncenter postNotificationName: MsgDB_SourceAddNotification object: self]; return src; } -(void) removeSource: (NSObject *)src { msg_id_t mid,next_mid,src_mid; const char *next; int i; char buf[64]; [src retain]; [sources removeObjectForKey: [sources keyForObject: src]]; src_mid=[src mid]; mid=main_id; while (1) { next=[self msg_getMetaHeader: "Next-source" : mid]; if (!next) break; i=atoi(next); if (i==-1) break; snprintf(buf,sizeof(buf),"source-%i",i); next_mid=[self midForId: buf]; if (!next_mid) { fprintf(stderr,"Warning: Broken source chain!\n"); break; } if (next_mid==src_mid) { if ([self msg_getMetaHeader: "Next-source" : src_mid]) [self msg_setMetaHeader: "Next-source" value: [self msg_getMetaHeader: "Next-source" : src_mid] : mid]; else [self msg_setMetaHeader: "Next-source" value: "-1" : mid]; if (last_source_id==src_mid) last_source_id=mid; break; } mid=next_mid; } /* TODO: delete actual message */ [src release]; [ncenter postNotificationName: MsgDB_SourceRemoveNotification object: self]; } @end