/* copyright 2002 Alexander Malmberg */ #include #include #include #include #include "FolderThreader.h" #include static NSDate *parse_date(const char *date_header) { char *buf; char *b,*c; const char *d; NSDate *date; NSTimeZone *tz; int day,month,year,hour,minute,second,ofs; buf=malloc(strlen(date_header)+1); if (!buf) return [[NSDate alloc] initWithTimeIntervalSince1970: 0]; { int comma=0,paren=0; for (b=buf,d=date_header;*d;d++) { /* TODO: escaped '(' and ')'? */ if (*d=='(') paren++; else if (*d==')') { if (paren) paren--; } else if (paren) continue; else if (*d==',' && !comma) { comma=1; b=buf; } else *b++=*d; } } *b=0; b=buf; while (*b && !isdigit(*b)) b++; if (!*b) goto error; day=strtol(b,&b,10); while (*b && !isalpha(*b)) b++; if (!*b) goto error; for (c=b;isalpha(*c);c++) ; *c=0; if (!strcasecmp(b,"jan")) month=1; else if (!strcasecmp(b,"feb")) month=2; else if (!strcasecmp(b,"mar")) month=3; else if (!strcasecmp(b,"apr")) month=4; else if (!strcasecmp(b,"may")) month=5; else if (!strcasecmp(b,"jun")) month=6; else if (!strcasecmp(b,"jul")) month=7; else if (!strcasecmp(b,"aug")) month=8; else if (!strcasecmp(b,"sep")) month=9; else if (!strcasecmp(b,"oct")) month=10; else if (!strcasecmp(b,"nov")) month=11; else if (!strcasecmp(b,"dec")) month=12; else month=1; *c=' '; /* there had to be whitespace there before */ while (!isdigit(*b)) b++; if (!*b) goto error; year=strtol(b,&c,10); if (c-b==2) { if (year<50) year+=2000; else year+=1900; } else if (c-b==3) year+=1900; b=c; while (*b && !isdigit(*b)) b++; if (!*b) goto error; hour=strtol(b,&b,10); while (*b && !isdigit(*b)) b++; if (!*b) goto error; minute=strtol(b,&b,10); if (*b==':') { while (*b && !isdigit(*b)) b++; if (!*b) goto error; second=strtol(b,&b,10); } else second=0; while (isspace(*b)) b++; c=b; while (*c && !isspace(*c)) c++; *c=0; if ((b[0]=='+' || b[0]=='-')) { ofs=strtol(&b[1],&c,10); ofs=ofs%100+(ofs/100)*60; if (b[0]=='-') ofs=-ofs; } else ofs=0; /* TODO: handle others? */ if (month<1 || month>12 || day<1 || day>31 || hour<0 || hour>23 || minute<0 || minute>59 || second<0 || second>65) /* margin for leap seconds, probably too much */ goto error; free(buf); tz=[NSTimeZone timeZoneForSecondsFromGMT: ofs*60]; date=[[NSCalendarDate alloc] initWithYear: year month: month day: day hour: hour minute: minute second: second timeZone: tz]; return date; error: free(buf); /* let NSDate have a go at it, maybe it'll extract something useful */ return RETAIN([NSDate dateWithNaturalLanguageString: [NSString stringWithCString: c]]); } #define CACHE_SIZE 100 /* can't go higher than 255; entry 0 isn't used */ static int cache_hits,cache_misses; typedef struct msg_header_cache_s { NSString *subject,*from; NSDate *date; int row; unsigned short next,prev; /* Entries are in a linked list. Used entries are moved to the front, entries at the end are replaced as necessary. Entry #0 is the head of the list (and is included in the list). */ } header_cache_t; @implementation FolderThreader -(void) _sanity { int i,j; for (i=0;icache map!\n"); *((int *)-1)=0; } } for (i=0;imsg map!\n"); *((int *)-1)=0; } } for (i=header_cache[0].next,j=0;i;i=header_cache[i].next) j++; if (j!=CACHE_SIZE-1) { fprintf(stderr,"header cache messed up, linked list next!\n"); *((int *)-1)=0; } for (i=header_cache[0].prev,j=0;i;i=header_cache[i].prev) j++; if (j!=CACHE_SIZE-1) { fprintf(stderr,"header cache messed up, linked list prev!\n"); *((int *)-1)=0; } } -(void) _moveFirst: (int)e { if (!e) abort(); if (e==header_cache[0].next) return; /* remove from old place */ header_cache[header_cache[e].prev].next=header_cache[e].next; header_cache[header_cache[e].next].prev=header_cache[e].prev; /* update entry */ header_cache[e].next=header_cache[0].next; header_cache[e].prev=0; /* insert in new */ header_cache[header_cache[0].next].prev=e; header_cache[0].next=e; } -(void) _moveLast: (int)e { if (!e) abort(); if (e==header_cache[0].prev) return; /* remove from old place */ header_cache[header_cache[e].prev].next=header_cache[e].next; header_cache[header_cache[e].next].prev=header_cache[e].prev; /* update entry */ header_cache[e].prev=header_cache[0].prev; header_cache[e].next=0; /* insert in new */ header_cache[header_cache[0].prev].next=e; header_cache[0].prev=e; } -(void) _clearCacheEntry: (MidNotification *)n { header_cache_t *h; int r=[self indexOf: [n mid]]; if (r==-1) return; if (!msgs[r].cached) return; h=&header_cache[msgs[r].cached]; msgs[r].cached=0; DESTROY(h->subject); DESTROY(h->from); DESTROY(h->date); h->row=-1; [self _moveLast: h-header_cache]; } - initWithMsgDB: (MsgDB *)m target: (id)target { if (!(self=[super init])) return nil; ASSIGN(mdb,m); t=target; msgs=NULL; num_msgs=0; header_cache=malloc(sizeof(header_cache_t)*CACHE_SIZE); if (!header_cache) { RELEASE(self); return nil; } memset(header_cache,0,sizeof(header_cache_t)*CACHE_SIZE); { int i; for (i=0;isubject); DESTROY(h->from); DESTROY(h->date); } free(header_cache); header_cache=NULL; } msgs=NULL; num_msgs=0; [super dealloc]; // printf(" hits=%8i\nmisses=%8i\n",cache_hits,cache_misses); } -(int) indexOf: (msg_id_t)mid { /* TODO: this is probably a bottleneck */ int i; msg_info_t *m; for (i=0,m=msgs;imid==mid) return i; return -1; } /* TODO: think hard about how to handle cross posts and references to articles from other groups. */ -(int) addMsg: (msg_id_t)mid { /* Keep track of adds in progress so circular references can be detected. */ static msg_id_t *adding_stack; static int adding_stack_num,adding_stack_size; msg_info_t *m; char *c,*d; const char *mh; char *buf; int after; msg_id_t amid; { int i; for (i=0;i'); if (!d) break; /* broken headers */ d[1]=0; amid=[mdb midForId: c]; if (amid==0) { amid=[mdb createMessageWithId: c source: nil]; if (amid!=0) { *c=0; if (strchr(buf,'<')) [mdb msg_setMetaHeader: "Real-References" value: buf : amid]; } } if (amid!=0) { after=[self indexOf: amid]; if (after==-1) { [self addMsg: amid]; after=[self indexOf: amid]; } if (after!=-1) break; } } free(buf); } else after=-1; msgs=realloc(msgs,sizeof(msg_info_t)*(num_msgs+1)); if (!msgs) abort(); { int i,l; if (after==-1) { after=num_msgs-1; l=0; } else { l=msgs[after].level+1; while (after=l) after++; } for (i=num_msgs;i>after+1;i--) msgs[i]=msgs[i-1]; m=&msgs[after+1]; m->level=l; m->cached=0; /* now adjust the rows in the header cache entries */ for (i=0;iafter) header_cache[i].row++; } } { const char *c=[mdb msg_getMetaHeader: "RStatus" : mid]; if (c) m->rstatus=atoi(c); else m->rstatus=0; } m->mid=mid; num_msgs++; adding_stack_num--; return after+1; } /*-(void) addMsg: (msg_id_t)mid { msg_info_t *m; if ([self indexOf: mid]!=-1) return; msgs=realloc(msgs,sizeof(msg_info_t)*(num_msgs+1)); if (!msgs) abort(); m=&msgs[num_msgs]; { const char *c=[mdb msg_getMetaHeader: "RStatus" : mid]; if (c) m->rstatus=atoi(c); else m->rstatus=0; } m->mid=mid; num_msgs++; [list reloadData]; }*/ -(BOOL) selectFirstUnreadFrom: (int)i { for (;i0;i--) if (msgs[i].level<=max) break; return i; } -(void) markAsRead: (int)f : (int)to; { if (f<0) f=0; for (;f<=to;f++) { if (msgs[f].rstatus!=1) [mdb msg_setMetaHeader: "RStatus" value: "1" : msgs[f].mid]; } } -(NSString *) header: (const char *)h forMessage: (int)msg { const char *c; NSData *d; NSString *s2; c=[mdb msg_getHeader: h : msgs[msg].mid]; if (!c) return @""; d=[[NSData alloc] initWithBytes: c length: strlen(c)]; s2=[CWMIMEUtility decodeHeader: d charset: nil]; [d release]; return s2; } -(void) _fixCache: (int)msg { header_cache_t *h; int index; if (msgs[msg].cached) { cache_hits++; return; } cache_misses++; index=header_cache[0].prev; h=&header_cache[index]; if (h->row!=-1) { DESTROY(h->subject); DESTROY(h->from); DESTROY(h->date); msgs[h->row].cached=0; } h->row=msg; msgs[msg].cached=index; [self _moveFirst: index]; { const char *c; NSString *s2; NSData *d; c=[mdb msg_getHeader: "Subject" : msgs[msg].mid]; if (!c) h->subject=[[NSString alloc] initWithCString: [mdb msg_getMessageID: msgs[msg].mid]]; else { d=[[NSData alloc] initWithBytes: c length: strlen(c)]; s2=[CWMIMEUtility decodeHeader: d charset: nil]; [d release]; h->subject=[s2 retain]; } c=[mdb msg_getHeader: "From" : msgs[msg].mid]; if (c) { d=[[NSData alloc] initWithBytes: c length: strlen(c)]; s2=[CWMIMEUtility decodeHeader: d charset: nil]; [d release]; h->from=[s2 retain]; } else h->from=@""; c=[mdb msg_getHeader: "Date" : msgs[msg].mid]; if (c) h->date=parse_date(c); else h->date=nil; } [self _sanity]; } -(NSString *) subjectForMessage: (int)msg { // if (!msgs[msg].cached) [self _fixCache: msg]; return AUTORELEASE(RETAIN(header_cache[msgs[msg].cached].subject)); } -(NSString *) fromForMessage: (int)msg { // if (!msgs[msg].cached) [self _fixCache: msg]; return AUTORELEASE(RETAIN(header_cache[msgs[msg].cached].from)); } -(NSDate *) dateForMessage: (int)msg { // if (!msgs[msg].cached) [self _fixCache: msg]; return AUTORELEASE(RETAIN(header_cache[msgs[msg].cached].date)); } @end