This is INntpIO.m in view mode; [Download] [Up]
/*$Copyright: * Copyright (C) 1992.5.22. Recruit Co.,Ltd. * Institute for Supercomputing Research * All rights reserved. * NewsBase by ISR, Kazuto MIYAI, Gary ARAKAKI, Katsunori SUZUKI, Kok-meng Lue * * You may freely copy, distribute and reuse the code in this program under * following conditions. * - to include this notice in the source code, if it is to be distributed * with source code. * - to add the file named "COPYING" within the code, which shall include * GNU GENERAL PUBLIC LICENSE(*). * - to display an acknowledgement in binary code as follows: "This product * includes software developed by Recruit Co.,Ltd., ISR." * - to display a notice which shall state that the users may freely copy, * distribute and reuse the code in this program under GNU GENERAL PUBLIC * LICENSE(*) * - to indicate the way to access the copy of GNU GENERAL PUBLIC LICENSE(*) * * (*)GNU GENERAL PUBLIC LICENSE is stored in the file named COPYING * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. $*/ #import "IOrderedListD.h" #import "InfoD.h" #import "INewsgroupInfoD.h" #import "IOrderedListD.h" #import "INntpIO.h" #import "ITreeNodeD.h" #import "errdebug.h" #import "data_types.h" #import "IUifNode.h" #import <appkit/appkit.h> #import <stdio.h> #import <ctype.h> #import <libc.h> #import <string.h> #import <mach/mach.h> #import <objc/zone.h> #import "Localization.h" #define LoStr(key) doLocalString(NULL,key,NULL) static HashTable *tableOfNoiseWords; static const char *noiseWords[] = { #include "noise_words.h" }; #define SIZEMARKONE "sizemarkOne" #define SIZEMARKTWO "sizemarkTwo" #define SIZEMARKTHREE "sizemarkThree" static const char *ikey[MAX_NO_OF_TOKENS]; @implementation INntpIO + initialize { char filename[512]; NXStream *stream; char *buffer; int len, maxlen; char *ptr; const char **word; tableOfNoiseWords = [[HashTable alloc] initKeyDesc:"*"]; sprintf(filename, "%.491s/.NewsBaseNoiseWords", getenv("HOME")); if ((stream = NXMapFile(filename, NX_READWRITE)) != NULL) { NXSeek(stream, (long)0, NX_FROMEND); NXPutc(stream, '\0'); NXGetMemoryBuffer(stream, &buffer, &len, &maxlen); for (ptr = strtok(buffer, " \n\r"); ptr != NULL; ptr = strtok(NULL, " \n\r")) { [tableOfNoiseWords insertKey:ptr value:nil]; } NXCloseMemory(stream, NX_SAVEBUFFER); return(self); } else { for (word = noiseWords; *word != NULL; ++word) { [tableOfNoiseWords insertKey:*word value:nil]; } return(self); } } - initServer:(char *)nntpHost allNews:(BOOL)allNewsFlag newsGroupMode:(BrowserMode)g_mode articleMode:(BrowserMode)a_mode { const char *buffer; iNewsGroupMode = g_mode; iArticleMode = a_mode; irFlag = UNMARKEDONLY; // set default iBoundaryForTwo = 100; iBoundaryForThree = 300; // read default database and set boundary Line # for line mark if ((buffer = NXGetDefaultValue(OWNER, ARTICLESIZEMARK)) != 0) { sscanf(buffer, "0:%d:%d", &iBoundaryForTwo, &iBoundaryForThree); } DBG(1, fprintf(stderr,"boundaryTwo = %d\t Tree = %d\n", iBoundaryForTwo, iBoundaryForThree)); return [super initServer:nntpHost allNews:allNewsFlag]; } - subDirectoryOf:knode { /* directory structure of newsgroup tree */ /* */ /* [iNewsGroupTreeRoot]-+-[tree node]-+-[newsgroupInfo] */ /* | +-[tree node]-[newsgroupInfo]*/ /* +-[tree node]+[tree node] */ if (iNewsGroupMode == Tree) { return [self _subDirectoryOf:knode]; } else { return [self _flatDirectoryOf:knode]; } } - _subDirectoryOf:knode { id dirNewsGroup, childNewsGroup; id listnode; id node; id n; int i, j; int count; char *subscribe; id groupInfo; char *newsgroupName; NXTreeState state; id tnode; /* set newsgroup Tree node */ if (knode == nil) { /* dirNode is rootNode */ dirNewsGroup = iNewsGroupTreeRoot; } else { dirNewsGroup = [knode linkedData]; } listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:"listnode"]; for (i=0; childNewsGroup=[dirNewsGroup objectAt:i]; ++i) { if ([childNewsGroup isMemberOf:[ITreeNodeD class]]==NO) { /* childNewsGroup is not a real newsgroup Tree node */ continue; } // set leaf or not count = 0; for (j=0; n=[childNewsGroup objectAt:j]; ++j) { if ([n isMemberOf:[ITreeNodeD class]]==NO) { continue; } ++count; } // a childNewsGroup is classified into 3 types // 1. directory // 2. leaf and directory // childNewsGroup has tree node and newsgroup info // 3. leaf // 1. directory only if ((count > 0) && [childNewsGroup dataForKey:GROUPINFO] == nil) { node = [[IUifNode allocFromZone:[self zone]] initWithKey:[childNewsGroup key]]; [listnode addObject:node]; [node setTitleForCell:[childNewsGroup key]]; [node setLinkedData:childNewsGroup]; [node setNodeType:DirOfSubDirs]; [node setLeaf:NO]; // check newsgroup is subscribed or not here to // set directory active or notactive [node setActive:NO]; // default is not active [childNewsGroup initState:&state]; while (tnode = [childNewsGroup nextState:&state]) { if ((strcmp([tnode key], GROUPINFO) == 0) && (strcmp([tnode infoForKey:SUBSCRIBE], "yes") == 0 )) { // found a active newsgroup under me [node setActive:YES]; break; } } } else if (count > 0) { // 2. leaf and directory // insert leaf cell // node = [[IUifNode allocFromZone:[self zone]] initWithKey:[childNewsGroup key]]; [listnode addObject:node]; [node setTitleForCell:[childNewsGroup key]]; [node setLinkedData:childNewsGroup nodeType:DirOfItems]; [node setLeaf:YES]; // set image if ((groupInfo=[childNewsGroup dataForKey:GROUPINFO]) != nil) { if ([groupInfo isAllArticleMarked] == NO) { [node setImageForCell: [NXImage findImageNamed:UNREADMARK]]; } } /* set newsgroup name for dataGroupName */ [node setDataGroupName:[[childNewsGroup dataForKey:GROUPINFO] infoForKey:GROUPNAME]]; DBG(10,fprintf(stderr,"newsgroupName=%s",newsgroupName)); /* set node active for subscribe, no active for unsubscribe */ subscribe = [[childNewsGroup dataForKey:GROUPINFO] infoForKey:SUBSCRIBE]; if(strncmp(subscribe,"y",1)==0) { [node setActive:YES]; } else { [node setActive:NO]; } // insert directory cell // node = [[IUifNode allocFromZone:[self zone]] initWithKey:[childNewsGroup key]]; [listnode addObject:node]; [node setTitleForCell:[childNewsGroup key]]; [node setLinkedData:childNewsGroup]; [node setNodeType:DirOfSubDirs]; [node setLeaf:NO]; // check newsgroup is subscribed or not here to // set directory active or notactive [node setActive:NO]; // default is not active [childNewsGroup initState:&state]; while (tnode = [childNewsGroup nextState:&state]) { if ((strcmp([tnode key], GROUPINFO) == 0) && (strcmp([tnode infoForKey:SUBSCRIBE], "yes") == 0 )) { [node setActive:YES]; break; } } } else { // 3. leaf node = [[IUifNode allocFromZone:[self zone]] initWithKey:[childNewsGroup key]]; [listnode addObject:node]; [node setTitleForCell:[childNewsGroup key]]; [node setLinkedData:childNewsGroup]; [node setNodeType:DirOfItems]; [node setLeaf:YES]; if ((groupInfo=[childNewsGroup dataForKey:GROUPINFO]) != nil) { if ([groupInfo isAllArticleMarked] == NO) { [node setImageForCell:[NXImage findImageNamed:UNREADMARK]]; } } [node setDataGroupName:[[childNewsGroup dataForKey:GROUPINFO] infoForKey:GROUPNAME]]; subscribe = [[childNewsGroup dataForKey:GROUPINFO] infoForKey:SUBSCRIBE]; if(strncmp(subscribe,"y",1)==0) { [node setActive:YES]; } else { [node setActive:NO]; } } } return listnode; } - _flatDirectoryOf:knode { id newsGroup; id listnode, node; NXTreeState state; // const char *key; char *subscribe; id treenode; char *newsgroupName; id groupInfo; /* set newsgroup Tree node */ /* only rebuild from root(==nil) for now */ if (knode == nil) { /* dirNode is rootNode */ newsGroup = iNewsGroupTreeRoot; } else { newsGroup = [knode linkedData]; } listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:"listnode"]; [newsGroup initState:&state]; while (treenode=[newsGroup nextState:&state]) { if ([treenode isMemberOf:[ITreeNodeD class]]==NO) { /* newsGroup is not a real newsgroup Tree node */ continue; } if ([treenode dataForKey:GROUPINFO]==nil) { /* this node is only for grouping node, not have newsgroup inof */ continue; } newsGroup = treenode; newsgroupName = [[newsGroup dataForKey:GROUPINFO] infoForKey:GROUPNAME]; node = [[IUifNode allocFromZone:[self zone]] initWithKey:newsgroupName]; [node setTitleForCell:newsgroupName]; [node setLinkedData:newsGroup nodeType:DirOfItems]; [node setDataGroupName:newsgroupName]; [node setLeaf:YES]; /* flat's cell is always leaf */ // set image if ((groupInfo=[newsGroup dataForKey:GROUPINFO]) != nil) { if ([groupInfo isAllArticleMarked] == NO) { [node setImageForCell:[NXImage findImageNamed:UNREADMARK]]; } } subscribe = [[newsGroup dataForKey:GROUPINFO] infoForKey:SUBSCRIBE]; if(strncmp(subscribe,"y",1)==0) { [node setActive:YES]; } else { [node setActive:NO]; } [listnode addObject:node]; } return listnode; } - itemHeadersOf:knode { /* itemHeadersOf */ /* from newsgroup browser: click on leaf cell of newsgroup browser */ /* from article browser: click on article directory */ id linkdata; if ((linkdata = [knode linkedData]) == nil) { return 0; } switch ([knode nodeType]) { case DirOfItems: /* linkdata has groupInfo, so coming from NewsGroup browser */ switch(iArticleMode) { case Flat: /* get headers from newsgroup info */ return [self _itemHeadersOf:knode]; case Tree: /* Tree mode */ /* clicked on newsgroup browser, create article reference tree */ /* make iArticleDB with article headers */ [self _itemHeadersOf:knode]; /* make articleTree from iArticleDB */ [self _makeArticleTree]; /* return listnode for column-0 for ReferenceMode */ return [self _itemTreeHeadersOf:knode]; case BySubject: /* return listnode for column-0 for BySubjectMode */ return([self _subjectHeadersOf:knode]); case ByKeyword: /* return listnode for column-0 for ByKeywordMode */ return([self _keywordHeadersOf:knode]); } case ReferenceGroup: /* Article Browser should be in Tree Mode */ return [self _itemSubTreeHeadersOf:knode]; case SubjectGroup: case KeywordGroup: /* Article Browser should be in BySubject or ByKeyword Mode */ return([self _itemHeadersOfSubject:knode]); default: return(0); } } - _itemTreeHeadersOf:knode { id listnode, node; int i, j, k; id childArticle, articleItem, g_child, gg_child; id last_articleGroup; char *subject; int tree_node_count; id groupInfo, headerInfo; const char *newsgroupName; char *groupnameTmp; char *cline; id image; BOOL isUnreadArticle; /* _itemTreeHeadersOf is for filling cell in colum-0 of article browser */ /* _itemSubTreeHeadersOf is for column-1 */ /* return listnode for column-0 */ listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:"listnode"]; // search groupInfo groupnameTmp = NXCopyStringBuffer([knode dataGroupName]); makeTreeKey (groupnameTmp, ikey, "."); groupInfo=[[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO]; free(groupnameTmp); /* iArticleReferenceTreeRoot sould be made befor here */ for (i=0;childArticle=[iArticleReferenceTreeRoot objectAt:i];++i) { /* iArticleReferenceTreeRoot strucrture */ /* (1) case 1 */ /*[root]--+-[tree_node]---[articleItem]-[header] */ /* |(childArticle) */ /* (2) case 2 */ /* +-[tree_node]-+-[articleItem]-[header] */ /* | | */ /* | +-[tree_node]-[articleItem]-[header] */ /* | +-[tree_node]-[articleItem]-[header] */ /* (3) case 3 */ /* (original article is already removed in server) */ /* +-[tree_node]-+ */ /* +-[tree_node]-[articleItem]-[header] */ /* +-[tree_node]-[articleItem]-[header] */ if ([childArticle isMemberOf:[ITreeNodeD class]]==NO) { /* childArticle must be ITreeNodeD */ /* if you go into here, something wrong */ fprintf (stderr,"INntpIO: error: childArticle is not a member of ITreeNodeD\n"); continue; } /* search articleItem, count tree_node, set title for cell */ /* tree_node does not */ /* have articleItem, use articleItem of last child of */ /* this node as articleItem for tree_node */ articleItem = nil; /* articleItem */ tree_node_count = 0; isUnreadArticle = NO; for (j=0; g_child=[childArticle objectAt:j]; ++j) { if ([g_child isMemberOf:[IOrderedListD class]] && ([g_child dataForKey:HEADER_INFO]!=nil)) { /* articleItem have header.tbl */ /* case (1) or (2) */ articleItem = g_child; node = [[IUifNode allocFromZone:[self zone]] initWithKey:[articleItem key]]; [listnode addObject:node]; [node setLeaf:YES]; [node setLinkedData:articleItem nodeType:Header]; newsgroupName = [knode dataGroupName]; [node setDataGroupName:newsgroupName]; headerInfo = [g_child dataForKey:HEADER_INFO]; subject = [headerInfo infoForKey:SUBJECT]; // set title and image [node setTitleForCell:subject]; if ((cline=(char *)[headerInfo infoForKey:LINES]) != NULL) { // article header has "Lines:", so set mark to cell image = [self getArticleMarkFor:(int)atoi(cline)]; [node setImageForCell:image]; } /* check article is already read or not */ if ([groupInfo checkArticleIsRead: (int)[headerInfo infoForKey:ARTICLE_NUM]]) { [node setActive:NO]; } else { [node setActive:YES]; } } else if ([g_child isMemberOf:[ITreeNodeD class]]==YES) { /* g_child is tree_node */ /* case (2) or (3) */ last_articleGroup = g_child; ++ tree_node_count; // check if this g_child's article is already read for (k=0; gg_child=[last_articleGroup objectAt:k]; ++k) { if ([gg_child isMemberOf:[IOrderedListD class]] && ((headerInfo=[gg_child dataForKey:HEADER_INFO])!=nil)) { // gg_child is articleItem if ([groupInfo checkArticleIsRead: (int)[headerInfo infoForKey:ARTICLE_NUM]] == NO) { // there is an unread article isUnreadArticle = YES; break; } } } } } if (articleItem == nil) { /* case (3), can't find right article */ /* g_child is a last article of this article group */ for (j=0; gg_child=[last_articleGroup objectAt:j]; ++j) { if ([gg_child isMemberOf:[IOrderedListD class]] && ([gg_child dataForKey:HEADER_INFO]!=nil)) { articleItem = gg_child; break; } } } subject = [[articleItem dataForKey:HEADER_INFO] infoForKey:SUBJECT]; if (tree_node_count >0 ) { node = [[IUifNode allocFromZone:[self zone]] initWithKey:[childArticle key]]; [listnode addObject:node]; [node setTitleForCell:subject]; [node setLeaf:NO]; [node setLinkedData:childArticle nodeType:ReferenceGroup]; [node setDataGroupName:[knode dataGroupName]]; // check if all article is read or not to set directory's active if (isUnreadArticle == NO) { // child node is all not active, so directory is not active [node setActive:NO]; } else { [node setActive:YES]; } } } DBG(20, {int i; id child; for(i=0;child=[listnode objectAt:i];++i) { fprintf(stderr,"child key=%s\n",[child key]);} }); return listnode; } - _itemSubTreeHeadersOf:knode { id listnode, node; id articleGroup; id articleItem, headerInfo, groupInfo; id childArticle, g_child; int i, j; const char *newsgroupName; // char groupnameTmp[256]; char *groupnameTmp; char *cline; id image; articleGroup = [knode linkedData]; /* articleGroup is [tree_node], represent article group node */ listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:"listnode"]; for (i=0; childArticle=[articleGroup objectAt:i]; ++i) { /* (articleItem sould be displayed in */ /* column-0) */ /* +-[tree_node]-+-([articleItem]-[header]) */ /* | */ /* +-[tree_node]-[articleItem]-[header] */ /* +-[tree_node]-[articleItem]-[header] */ if ([childArticle isMemberOf:[ITreeNodeD class]]==NO) { /* only [tree_node] are under article group node */ continue; } for (j=0; g_child=[childArticle objectAt:j]; ++j) { if ([g_child isMemberOf:[IOrderedListD class]] && ((headerInfo=[g_child dataForKey:HEADER_INFO])!=nil)) { /* g_child is article */ articleItem = g_child; node = [[IUifNode allocFromZone:[self zone]] initWithKey:[articleItem key]]; [listnode addObject:node]; [node setTitleForCell:[headerInfo infoForKey:MESSAGE_ID]]; if ((cline=(char *)[headerInfo infoForKey:LINES]) != NULL) { // article header has "Lines:", so set mark to cell image = [self getArticleMarkFor:(int)atoi(cline)]; [node setImageForCell:image]; } [node setLinkedData:articleItem nodeType:Header]; newsgroupName = [knode dataGroupName]; [node setDataGroupName:newsgroupName]; [node setLeaf:YES]; /* check article is already read or not */ //strncpy(groupnameTmp, newsgroupName, sizeof(groupnameTmp)-1); groupnameTmp = NXCopyStringBuffer(newsgroupName); makeTreeKey(groupnameTmp, ikey, "."); groupInfo=[[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO]; free(groupnameTmp); if ([groupInfo checkArticleIsRead: (int)[headerInfo infoForKey:ARTICLE_NUM]]) { [node setActive:NO]; } else { [node setActive:YES]; } } } } return listnode; } - _itemHeadersOf:knode { id newsGroup; id listnode; id node; id article; id headerInfo, groupInfo; int i; const char *newsgroupName; // char groupnameTmp[256]; char *groupnameTmp; char *message_id; id image; char *cline; newsGroup = [knode linkedData]; iArticleDB = [self initNewsGroup:newsGroup readFlag:(ReadFlag)irFlag]; listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:"listnode"]; for (i=0; article=[iArticleDB objectAt:i]; ++i) { headerInfo = [article dataForKey:HEADER_INFO]; message_id = [headerInfo infoForKey:MESSAGE_ID]; /* if no message_id, bring up alert panel and skip it */ if (message_id == NULL) { NXRunAlertPanel(LoStr("NewsBase"),LoStr("no Message-ID field") ,LoStr("OK"),NULL,NULL); continue; } node = [[IUifNode allocFromZone:[self zone]] initWithKey:message_id]; [listnode addObject:node]; [node setTitleForCell:[headerInfo infoForKey:SUBJECT]]; if ((cline=(char *)[headerInfo infoForKey:LINES]) != NULL) { // article header has "Lines:", so set mark to cell image = [self getArticleMarkFor:(int)atoi(cline)]; [node setImageForCell:image]; } [node setLinkedData:article nodeType:Header]; newsgroupName = [knode dataGroupName]; [node setDataGroupName:newsgroupName]; [node setLeaf:YES]; /* check article is already read or not */ //strncpy(groupnameTmp, newsgroupName, sizeof(groupnameTmp)-1); groupnameTmp = NXCopyStringBuffer(newsgroupName); makeTreeKey(groupnameTmp, ikey, "."); groupInfo=[[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO]; free(groupnameTmp); if ([groupInfo checkArticleIsRead: (int)[headerInfo infoForKey:ARTICLE_NUM]]) { [node setActive:NO]; } else { [node setActive:YES]; } } return listnode; } - getArticleMarkFor:(int)linenum { id image; if (linenum < iBoundaryForTwo) { image = [NXImage findImageNamed:SIZEMARKONE]; } else if (iBoundaryForTwo <= linenum && linenum < iBoundaryForThree) { image = [NXImage findImageNamed:SIZEMARKTWO]; } else if (iBoundaryForThree <= linenum ) { image = [NXImage findImageNamed:SIZEMARKTHREE]; } else { // linenum <= 0, and other image = NULL; } return image; } - itemOf:knode { IOrderedListD *article; InfoD *header; const char *messageId; if ((article = [knode linkedData]) == nil) { return(nil); } if ((header = [article dataForKey:HEADER_INFO]) == nil) { return(nil); } messageId = (const char *)[header infoForKey:MESSAGE_ID]; if ([self sendArticle:messageId] == YES) { return(self); } else { return(nil); } } - (BOOL)toggleActive:knode { id newsGroup, groupInfo; id headerInfo; int art_num; const char *newsgroupName; char *groupnameTmp; enum {ACTIVATE, NOTACTIVATE} activateAction; char *xref, buf[MAXPATHLEN], *bufptr[MAX_NO_OF_TOKENS]; char groupNameBuf[MAXPATHLEN]; int i; NXTreeState state; id rootnode, tnode; DBG(1, fprintf(stderr,"INntpIO: -- toggleActive fired\n")); switch ([knode nodeType]) { /* message is coming from newsgroup browser */ /* toggle subscribe */ case DirOfItems: // selected cell is a newsgroup leaf cell DBG(10,fprintf(stderr,"-- toggle SUBSCRIBE")); groupInfo = [[knode linkedData] dataForKey:GROUPINFO]; if ([knode active]) { /* knode is active, subscribe -> unsubscribe */ [groupInfo addInfoString:"no" key:SUBSCRIBE]; } else { /* knode is not active, unsubscribe -> subscribe */ [groupInfo addInfoString:"yes" key:SUBSCRIBE]; } return YES; break; case DirOfSubDirs: // selected cell is a directory of newsgroup // subscribe or unsubscribe all newsgroup under this directory activateAction = ([knode active]) ? NOTACTIVATE : ACTIVATE; rootnode = [knode linkedData]; [rootnode initState:&state]; while (tnode=[rootnode nextState:&state]) { if (strcmp([tnode key], GROUPINFO) == 0) { // tnode is groupInfo for newsgroup if (activateAction == NOTACTIVATE) { [tnode addInfoString:"no" key:SUBSCRIBE]; } else { [tnode addInfoString:"yes" key:SUBSCRIBE]; } } } return YES; break; /* message is coming from article header browser */ /* toggle article mark */ case ReferenceGroup: // selected cell is a directory of article // knode is obj. of "ITreeNodeD" activateAction = ([knode active]) ? NOTACTIVATE : ACTIVATE; rootnode = [knode linkedData]; [rootnode initState:&state]; while (tnode=[rootnode nextState:&state]) { if ([tnode isMemberOf:[IOrderedListD class]] && ((headerInfo=[tnode dataForKey:HEADER_INFO]) != nil)) { // tnode is article art_num = (int)[headerInfo infoForKey:ARTICLE_NUM]; groupnameTmp = NXCopyStringBuffer([knode dataGroupName]); makeTreeKey(groupnameTmp, ikey, "."); newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey]; free(groupnameTmp); groupInfo = [newsGroup dataForKey:GROUPINFO]; if (activateAction == NOTACTIVATE) { // this directory cell is active so mark all article // as read [groupInfo markReadArticle:art_num]; } else { [groupInfo unmarkReadArticle:art_num]; } } } return YES; break; case SubjectGroup: case KeywordGroup: // selected cell is a directory of article // knode is obj. of "IOrderedListD" activateAction = ([knode active]) ? NOTACTIVATE : ACTIVATE; rootnode = [knode linkedData]; for (i=0; tnode=[rootnode objectAt:i]; ++i) { if ([tnode isMemberOf:[IOrderedListD class]] && ((headerInfo=[tnode dataForKey:HEADER_INFO]) != nil)) { // tnode is article art_num = (int)[headerInfo infoForKey:ARTICLE_NUM]; groupnameTmp = NXCopyStringBuffer([knode dataGroupName]); makeTreeKey(groupnameTmp, ikey, "."); newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey]; free(groupnameTmp); groupInfo = [newsGroup dataForKey:GROUPINFO]; if (activateAction == NOTACTIVATE) { // selected cell is active // mark all article as read under this directory [groupInfo markReadArticle:art_num]; } else { [groupInfo unmarkReadArticle:art_num]; } } } return YES; break; case Header: // selected cell is a leaf cell of article DBG(1,fprintf(stderr,"-- toggle article mark")); headerInfo = [[knode linkedData] dataForKey:HEADER_INFO]; // mark this article first newsgroupName = [knode dataGroupName]; groupnameTmp = NXCopyStringBuffer(newsgroupName); makeTreeKey(groupnameTmp, ikey, "."); newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey]; free(groupnameTmp); groupInfo = [newsGroup dataForKey:GROUPINFO]; // set this article's article number and mark or unmark it // activateAction will be used for Xref: things art_num = (int)[headerInfo infoForKey:ARTICLE_NUM]; if ([groupInfo checkArticleIsRead:art_num]) { /* article is already read, mark -> unmark */ [groupInfo unmarkReadArticle:art_num]; activateAction = NOTACTIVATE; } else { [groupInfo markReadArticle:art_num]; activateAction = ACTIVATE; } // do Xref: things if ((xref=(char *)[headerInfo infoForKey:"Xref"]) == NULL) { // this article does not have cross reference return YES; } else { // article has Xref: strncpy(buf, xref, sizeof(buf)); makeTreeKey(buf, bufptr, " "); for (i=1; bufptr[i] != NULL; ++i) { // skip first one (xxxxx) // assume that Xref: field is like // xxxxx aaa.aaa.aaa:nnn bbb.bbb.bbb:mmm ..... // xxxxx: e.g. ISRNEWS // aaa.aaa.aaa, bbb.bbb.bbb: newsgroup // nnn, mmm: article number if (sscanf(bufptr[i],"%1023[^:]:%d",groupNameBuf,&art_num)==2){ // buf is newsgroup name DBG(1, fprintf(stderr,"newsgroup=%s\t art_num=%d\n", groupNameBuf, art_num)); makeTreeKey(groupNameBuf, ikey, "."); newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey]; groupInfo = [newsGroup dataForKey:GROUPINFO]; if (activateAction == NOTACTIVATE) { [groupInfo unmarkReadArticle:art_num]; } else { [groupInfo markReadArticle:art_num]; } } } return YES; } break; default: // should never coming here return NO; break; } return NO; } - (BrowserMode)toggleNewsGroupMode:sender { switch ((BrowserMode)[[sender selectedCell] tag]) { case Flat: iNewsGroupMode = Flat; break; case Tree: default: iNewsGroupMode = Tree; break; } return iNewsGroupMode; } - (BrowserMode)toggleArticleMode:sender { switch ((BrowserMode)[[sender selectedCell] tag]) { case Flat: iArticleMode = Flat; break; case Tree: iArticleMode = Tree; break; case BySubject: iArticleMode = BySubject; break; case ByKeyword: iArticleMode = ByKeyword; break; } return(iArticleMode); } - setArticleMode:(BrowserMode)articleMode { iArticleMode = articleMode; return(self); } - setReadFlag:(ReadFlag)rflag { irFlag = rflag; return self; } - _makeArticleTree { int i; id articleItem; iArticleHashTable = [[HashTable allocFromZone:headersZone] initKeyDesc:"*" valueDesc:"@"]; iArticleReferenceTreeRoot = [[ITreeNodeD allocFromZone:headersZone] initWithKey:"root"]; for (i=0; articleItem=[iArticleDB objectAt:i]; ++i) { [iArticleHashTable insertKey:[[articleItem dataForKey:HEADER_INFO] infoForKey:MESSAGE_ID] value:articleItem]; } [self _addArticleToTree]; return iArticleReferenceTreeRoot; } - (void)_addArticleToTree { #define TREELISTSIZE 128 char treeKeyList[TREELISTSIZE]; char treeKeyListTmp[TREELISTSIZE]; const char *mes_id; id node; NXHashState state; id articleItem; id org_article; char *org_art_messageID; int org_art_num; char *key[MAX_NO_OF_TOKENS]; state = [iArticleHashTable initState]; while ([iArticleHashTable nextState:&state key:(const void **)&mes_id value:(void **)&articleItem]) { /* look for original article's message_id */ if ((org_art_messageID=[self findOriginalArticle:articleItem])==NULL) { /* original article */ sprintf(treeKeyList, "%010d", (int)[[articleItem dataForKey:HEADER_INFO] infoForKey:ARTICLE_NUM]); } else { /* article has reference */ if ((org_article = [iArticleHashTable valueForKey:(char *)org_art_messageID]) == NULL) { /* if original aritlce is not in server, key = "0000000000_messageID 0000000yyy" */ sprintf(treeKeyList, "0000000000_%.105s %010d", (char *)org_art_messageID, (int)[[articleItem dataForKey:HEADER_INFO] infoForKey:ARTICLE_NUM]); } else { /* if original article is in server, key= "0000000xxx 0000000yyy" */ org_art_num = (int)[[org_article dataForKey:HEADER_INFO] infoForKey:ARTICLE_NUM]; sprintf(treeKeyList, "%010d %010d", org_art_num, (int)[[articleItem dataForKey:HEADER_INFO] infoForKey:ARTICLE_NUM]); } DBG(10,fprintf(stderr,"treeKeyList = %s\n org_subject = %s\n" "Subject = %s", treeKeyList, (char *)[[org_article dataForKey:HEADER_INFO] infoForKey:SUBJECT], (char *)[[articleItem dataForKey:HEADER_INFO] infoForKey:SUBJECT])); } strncpy(treeKeyListTmp, treeKeyList, sizeof(treeKeyListTmp)-1); makeTreeKey(treeKeyListTmp, key, " "); node = [iArticleReferenceTreeRoot addNodeForKey:key]; [node insertKeyedObject:articleItem]; } } - (char *)findOriginalArticle:articleItem { char *headRef, *references; char *ch; if (([[articleItem dataForKey:HEADER_INFO] infoForKey:REFERENCES]) == NULL) { /* if not responsed article */ return NULL; } do { if((references=[[articleItem dataForKey:HEADER_INFO] infoForKey:REFERENCES]) == NULL) { return headRef; } headRef = references; if ((ch=strchr(headRef,' ')) != NULL) { *ch = '\0'; } else { /* if not ' ' in headRef, only one message id is in references field , and that's original article */ return headRef; } } while (articleItem=[iArticleHashTable valueForKey:headRef]); return headRef; } - (BrowserMode)newsGroupMode { return iNewsGroupMode; } - (BrowserMode)articleMode { return(iArticleMode); } - (IOrderedListD *)_subjectHeadersOf:(IUifNode *)newsgroupNode { int i, k; IOrderedListD *articleItem; InfoD *header; const char *subject; int articleNo; char articleNoString[16]; IOrderedListD *listnode; HashTable *tableOfSubjectGroups; NXHashState tableState; IOrderedListD *subjectGroup; IUifNode *subjectNode; IUifNode *articleNode; const char *newsgroupName; char *groupnameTmp; char *cline; id image; BOOL isUnreadArticle; INewsgroupInfoD *groupInfo; id c_article, c_header; iArticleDB = [self initNewsGroup:[newsgroupNode linkedData] readFlag:(ReadFlag)irFlag]; // use a temporary hashtable to map subjects to subject groups tableOfSubjectGroups = [[HashTable allocFromZone:headersZone] initKeyDesc:"*" valueDesc:"@"]; for (i = 0; (articleItem = [iArticleDB objectAt:i]) != nil; ++i) { header = [articleItem objectWithKey:HEADER_INFO]; subject = [header infoForKey:SUBJECT]; // strip Re: from subject if present if (strncmp(subject, "Re:", 3) == 0 || strncmp(subject, "re:", 3) == 0 || strncmp(subject, "RE:", 3) == 0) { subject += 3; } if (subject[0] == ' ') { ++subject; } articleNo = (int)[header infoForKey:ARTICLE_NUM]; sprintf(articleNoString, "%010d", articleNo); if ((subjectGroup = [tableOfSubjectGroups valueForKey:subject]) == nil) { // create a subject group for this subject subjectGroup = [[IOrderedListD allocFromZone:headersZone] initWithKey:subject]; [tableOfSubjectGroups insertKey:[subjectGroup key] value:subjectGroup]; } // add article to this subject group [subjectGroup insertKeyedObject:(IKeyedObject *)articleItem]; } newsgroupName = [newsgroupNode dataGroupName]; groupnameTmp = NXCopyStringBuffer(newsgroupName); makeTreeKey(groupnameTmp, ikey, "."); groupInfo = [[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO]; free (groupnameTmp); tableState = [tableOfSubjectGroups initState]; listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:""]; while ([tableOfSubjectGroups nextState:&tableState key:(const void **)&subject value:(void **)&subjectGroup]) { articleItem = [subjectGroup objectAt:0]; header = [articleItem objectWithKey:HEADER_INFO]; articleNo = (int)[header infoForKey:ARTICLE_NUM]; sprintf(articleNoString, "%010d", articleNo); if ([subjectGroup count] == 1) { // create articleNode and link to article articleNode = [[IUifNode allocFromZone:[self zone]] initWithKey:articleNoString]; [articleNode setLinkedData:articleItem nodeType:Header]; // use subject as title [articleNode setTitleForCell:[header infoForKey:SUBJECT]]; // set image for line num if ((cline=(char *)[header infoForKey:LINES]) != NULL) { // article header has "Lines:", so set mark to cell image = [self getArticleMarkFor:(int)atoi(cline)]; [articleNode setImageForCell:image]; } [articleNode setDataGroupName:newsgroupName]; [articleNode setLeaf:YES]; if ([groupInfo checkArticleIsRead:articleNo] == YES) { [articleNode setActive:NO]; } else { [articleNode setActive:YES]; } [listnode insertKeyedObject:articleNode]; [subjectGroup removeObject:articleItem]; [subjectGroup free]; } else { // More than one article in this subject group so create a subject // node for this subject and link to subject group subjectNode = [[IUifNode allocFromZone:[self zone]] initWithKey:articleNoString]; [subjectNode setLinkedData:subjectGroup nodeType:SubjectGroup]; [subjectNode setTitleForCell:subject]; [subjectNode setDataGroupName:newsgroupName]; [subjectNode setLeaf:NO]; // check if there is unread article isUnreadArticle = NO; for (k=0; c_article=[subjectGroup objectAt:k]; ++k) { if ([c_article isMemberOf:[IOrderedListD class]] && ((c_header=[c_article dataForKey:HEADER_INFO]) != nil)) { if ([groupInfo checkArticleIsRead: (int)[c_header infoForKey:ARTICLE_NUM]] == NO) { // there is an unread article isUnreadArticle = YES; break; } } } if (isUnreadArticle == YES) { [subjectNode setActive:YES]; } else { [subjectNode setActive:NO]; } [listnode insertKeyedObject:subjectNode]; } } [tableOfSubjectGroups free]; return(listnode); } - _itemHeadersOfSubject:(IUifNode *)subjectNode { IOrderedListD *subjectGroup; int i; IOrderedListD *articleItem; InfoD *header; int articleNo; char articleNoString[16]; IOrderedListD *listnode; IUifNode *articleNode; const char *newsgroupName; // char groupnameTmp[256]; char *groupnameTmp; char *cline; id image; subjectGroup = [subjectNode linkedData]; listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:""]; for (i = 0; (articleItem = [subjectGroup objectAt:i]) != nil; ++i) { header = [articleItem objectWithKey:HEADER_INFO]; articleNo = (int)[header infoForKey:ARTICLE_NUM]; sprintf(articleNoString, "%010d", articleNo); // create articleNode and link to article articleNode = [[IUifNode allocFromZone:[self zone]] initWithKey:articleNoString]; [articleNode setLinkedData:articleItem nodeType:Header]; // use sender as title if ([subjectNode nodeType] == KeywordGroup) { [articleNode setTitleForCell:[header infoForKey:SUBJECT]]; } else { [articleNode setTitleForCell:[header infoForKey:FROM]]; } // set image by line num if ((cline=(char *)[header infoForKey:LINES]) != NULL) { // article header has "Lines:", so set mark to cell image = [self getArticleMarkFor:(int)atoi(cline)]; [articleNode setImageForCell:image]; } newsgroupName = [subjectNode dataGroupName]; [articleNode setDataGroupName:newsgroupName]; [articleNode setLeaf:YES]; // add article node to subject group //strncpy(groupnameTmp, newsgroupName, sizeof(groupnameTmp)-1); groupnameTmp = NXCopyStringBuffer(newsgroupName); makeTreeKey(groupnameTmp, ikey, "."); if ([[[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO] checkArticleIsRead:articleNo] == YES) { [articleNode setActive:NO]; } else { [articleNode setActive:YES]; // also make the subject group active [subjectNode setActive:YES]; } free(groupnameTmp); [listnode insertKeyedObject:articleNode]; } return(listnode); } #define KEYWORD_LIST_SIZE 4096 - (IOrderedListD *)_keywordHeadersOf:(IUifNode *)newsgroupNode { const char *newsgroupName; int i; IOrderedListD *articleItem; InfoD *header; const char *keywords; char keywordList[KEYWORD_LIST_SIZE], *ptr; const char *keyword; HashTable *tableOfKeywordGroups; IOrderedListD *keywordGroup; IOrderedListD *listnode; IUifNode *keywordNode; iArticleDB = [self initNewsGroup:[newsgroupNode linkedData] readFlag:(ReadFlag)irFlag]; newsgroupName = [newsgroupNode dataGroupName]; // use a temporary hashtable to map subjects to subject groups tableOfKeywordGroups = [[HashTable allocFromZone:headersZone] initKeyDesc:"*" valueDesc:"@"]; listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:""]; for (i = 0; (articleItem = [iArticleDB objectAt:i]) != nil; ++i) { header = [articleItem objectWithKey:HEADER_INFO]; strncpy(keywordList, [header infoForKey:SUBJECT], sizeof(keywordList)); keywordList[sizeof(keywordList) - 1] = '\0'; if ((keywords = [header infoForKey:KEYWORDS]) != NULL) { ptr = index(keywordList, '\0'); *ptr++ = ' '; strncpy(ptr, keywords, &keywordList[sizeof(keywordList)] - ptr); keywordList[sizeof(keywordList) - 1] = '\0'; } for (ptr = keywordList; *ptr != '\0'; ++ptr) { if (isalnum(*ptr) == 0) { *ptr = ' '; } else if (isupper(*ptr) != 0) { *ptr = tolower(*ptr); } } for (keyword = strtok(keywordList, " "); keyword != NULL; keyword = strtok(NULL, " ")) { if (keyword[1] == '\0') { continue; } if ([tableOfNoiseWords isKey:keyword] == YES) { continue; } if ((keywordGroup = [tableOfKeywordGroups valueForKey:keyword]) == nil) { // create a keyword group for this keyword keywordGroup = [[IOrderedListD allocFromZone:headersZone] initWithKey:keyword]; [tableOfKeywordGroups insertKey:[keywordGroup key] value:keywordGroup]; keywordNode = [[IUifNode allocFromZone:[self zone]] initWithKey:keyword]; [keywordNode setLinkedData:keywordGroup nodeType:KeywordGroup]; [keywordNode setTitleForCell:keyword]; [keywordNode setDataGroupName:newsgroupName]; [keywordNode setActive:YES]; [listnode insertKeyedObject:keywordNode]; } // add article to this keyword group [keywordGroup insertKeyedObject:(IKeyedObject *)articleItem]; } } [tableOfKeywordGroups free]; return(listnode); } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.