ftp.nice.ch/pub/next/connectivity/news/NewsBase.3.02.s.tar.gz#/NewsBase302.source/NNTP/INewsGroupTreeControl.m

This is INewsGroupTreeControl.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.
$*/
/*
 *    INewsGroupTreeControl is a subclass of the INNTP class.
 *    It initializes and maintains the newsgroup tree.
 *     It also handles the posting of articles.
 *    It handles all communication with the NNTP server.
 */
 
#import <appkit/appkit.h>
#import <streams/streams.h>
#import <objc/zone.h>
#import <mach/mach.h>
#import <defaults/defaults.h>
#import <strings.h>
#import <libc.h>
#import <ctype.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <errno.h>
#import <dpsclient/dpsNeXT.h>

#import "INewsGroupTreeControl.h"
#import "data_types.h"
#import "response_codes.h"
#import "InfoD.h"
#import "INewsRc.h"
#import "INewsgroupInfoD.h"
#import "ITreeNodeD.h"
#import "IReceiveSpeaker.h"
#import "errdebug.h"

#import "Localization.h"

#define LoStr(key)      doLocalString(NULL,key,NULL)

#define OWNER "NewsBase"
#define NNTPSERVER "NNTPServer"
//#define MAX_NO_OF_TOKENS 256
//#define        OK_GROUPS       215     /* Newsgroups follow */

id currentNewsGroup;
id currentArticle;

@implementation INewsGroupTreeControl

- initServer:(char *)nntpHost allNews:(BOOL)allNewsFlag
{    
    /* connect to nntp server */
    if ([self openServer:nntpHost]==nil) {
	return nil;
    }
    /* set rootName to IOmodule, this will be used for the root name */
    /* on newsgroup browser */
    [self setRootName:nntpHost];
    
    // set flag for reading all news group or subscribed only
    iAllNewsFlag = allNewsFlag;
    
    /* make newsgroup tree zone */
    newsgroupTreeZone = NXCreateZone (vm_page_size, vm_page_size, YES);

    /* read .newsrc file and make string table */
    if ([self readNewsRcFile] ==NULL) {
    	return NULL;
    }
    iNewsGroupTreeRoot = [[ITreeNodeD allocFromZone:
    			(NXZone *)newsgroupTreeZone] initWithKey:"root"];
    
    // make news group tree
    [self listAndMakeNewsgroupTree];
    
    // start auto reconnecting timed entry event
    [self setTimedEntryForReconnect];
    return self;
}

- listAndMakeNewsgroupTree
{
    int		statusCode;
    id		groupInfoD;
    char	groupName[256], canPost[2];
//    char	groupnameTmp[256];
    char	*groupnameTmp;
    int		last, first;
    char	ch, tempbuf[LINE_BUFFER_SIZE];
    char	*key[MAX_NO_OF_TOKENS];
    ITreeNodeD	*node;
    void	makeTreeKey(const char *, const char **, const char *);

    statusCode = [self issueCommand:"list"];
    if (statusCode != OK_GROUPS) {
	NXRunAlertPanel([self name],LoStr("list command failed"),
		LoStr("OK"),NULL,NULL);
        [NXApp terminate:self];
    }
    canPost[1] = '\0';
    while ((ch = fgetc(inntpFile)) !='.') {
	ungetc (ch, inntpFile);
	fscanf (inntpFile, "%[^\r]\r\n", tempbuf);
	sscanf (tempbuf, "%255s %d %d %c", groupName, &last, &first, &canPost);
	
	/* check canPost is '='(alias) */
	if (canPost[0] == '=') {
	    /* if the newsgroup is aliased, skip this group */
	    continue;
	}
	/* check group name */
	/* Subscribe and NotExist -> make node for news group tree */
	/* UnSubscribe 		  -> not to make into tree */
	switch ((NewsStat)[iNewsRc isSubscribe:groupName]) {
	    case UnSubscribe:
		if (iAllNewsFlag == NO) {
		/* unsubscribed newsgroup is marked as not-active */
		/* only subscribed newsgroup, so skip unsubscribed one */
		    continue;
		}
		/* iAllNewsFlag == YES */
		break;
	    case NotExist:
		fprintf (stderr, 
			"WARNING: new group \"%s\" is added\n", groupName);
	    case Subscribe:
		break;
	}
	
	// if iNewsGroupTreeRoot already had the node for key, 
	// the node has already been created. if not, make it.
	//strncpy(groupnameTmp, groupName, sizeof(groupnameTmp)-1);
	groupnameTmp = NXCopyStringBuffer(groupName);
	makeTreeKey(groupnameTmp, key, ".");
	if ((node=[iNewsGroupTreeRoot nodeForKey:key]) == nil) {
	    node = [iNewsGroupTreeRoot addNodeForKey:key];
	}
	free(groupnameTmp);
	
	if((groupInfoD = [node dataForKey:GROUPINFO]) == nil) {
	    /* node does not have groupInfoD, so initialize it */
	    /* when launching NewsBase first, this routine will be called, */
	    /* but not entering when reconnecting. */
	    groupInfoD = [[INewsgroupInfoD allocFromZone:
			(NXZone *)newsgroupTreeZone] initWithKey:GROUPINFO];
	    [groupInfoD addInfo:groupName key:GROUPNAME];
	    [node insertKeyedObject:groupInfoD];

	    /* set other info. from header field */
	    // set FIRST, CANPOST, ART_NUM_SET when launched
	    // skip these when reconnecting
	    [groupInfoD addInfoInt:first key:FIRST];
	    [groupInfoD addInfo:canPost key:CANPOST];
	    /* set ART_NUM_SET */
	    /* "addInfoInt: key:FIRST" will clean up ART_NUM_SET, */
	    /* but response of list command includes poor infomation about */
	    /* FIRST, so locate ART_NUM_SET after "addInfoInt: key:FIRST" */
	    /* not to waste cpu */
	    [iNewsRc setGroupInfo:groupInfoD];
	}

	// LAST will be reset when reconnecting
	[groupInfoD addInfoInt:last key:LAST];
    }

    fgetc (inntpFile);					/* get \r */
    fgetc (inntpFile);					/* get \n */
    /* move file pointer to the end of stream */
    fseek (inntpFile, (long)0, SEEK_END);

    return self;
}

- newsGroupTreeRoot
{
    return iNewsGroupTreeRoot;
}

- (const char *)currentNewsgroupName
{
//    return [[iCurrentNewsgroup link] key];
    return [[iCurrentNewsgroup dataForKey:GROUPINFO] infoForKey:GROUPNAME];
}

- currentArticleItem
{
    return iCurrentArticleItem;
}

- initNewsGroup:newsGroup readFlag:(ReadFlag)rflag
{
    char        combuf[LINE_CHR_MAX];
    id groupInfoD;
    int		statusCode;
    int         dum, estNum, first, last, tmpint;
    int i;
    id articleDB;
    int		artWillGetNum, artNumAmount;
       
    /* headersZone is for ItemHeaderBrowser */
    /* when newsgroup is switched, headers are no longer needed */
    if (headersZone != NULL) {
        NXDestroyZone(headersZone);
    }
    headersZone = NXCreateZone (vm_page_size, vm_page_size, YES);
    DBG(1, fprintf(stderr, "headersZone = %x\n", (unsigned int)headersZone););

    articleDB = [[IOrderedListD allocFromZone:headersZone]
        			initWithKey:"articleDB"];
    groupInfoD = [newsGroup dataForKey:GROUPINFO];

    strncpy (combuf, "group ", 7);
    strncat (combuf, (char *)[groupInfoD infoForKey:GROUPNAME], (LINE_CHR_MAX-7));

    statusCode = [self issueCommand:combuf];
    if (statusCode != OK_GROUP) {
        if (statusCode == ERR_NOGROUP) {
            NXRunAlertPanel(LoStr("WARNING"),
		LoStr("InntpTalk: no such newsgroup"),LoStr("OK"),NULL,NULL);
        } else {
            NXRunAlertPanel(LoStr("ERROR"),
		LoStr("InntpTalk: group command failed"),LoStr("OK"),NULL,NULL);
	    fseek (inntpFile, (long)0, SEEK_END);
	    return nil;
        }
    }
    sscanf (iresponse, "%d %d %d %d", &dum, &estNum, &first, &last);
                                                /* dum is status code */
    tmpint = (int)[groupInfoD infoForKey:LAST];
    if (tmpint != last) {
        [groupInfoD addInfoInt:last key:LAST];
    }
    tmpint = (int)[groupInfoD infoForKey:FIRST];
    if (tmpint != first) {
        [groupInfoD addInfoInt:first key:FIRST];
    }
    [groupInfoD addInfoInt:estNum key:ESTIMATENUM];
   
    fseek (inntpFile, (long)0, SEEK_END);

    /* # of articles on server */
    
    /* read check */   
    switch ( rflag ) {
    case ALL:
	for (i=first; i<=last; i++) {
	    [self commandHead:i articleDB:articleDB];
	}
	break;
    case UNMARKEDONLY:
    default:
	/* default # of getting article from server is in NUM_ARTTOGET */
	if ((artNumAmount=[groupInfoD countArticleUnMarked])
		    > (int)atoi(NXGetDefaultValue(OWNER,NUM_ARTTOGET))) {

	    // a lot of article is to be read in server,
	    // make alert panel and run modal roop
	    NXModalSession theSession;
	    int modalStatus;
	    
	    [oGetArticleNumField setIntValue:artNumAmount];
	    [iPercentageView resetValue];
	    [oGetArticleNumWindow makeKeyAndOrderFront:self];

	    [NXApp beginModalSession:&theSession for:oGetArticleNumWindow];
	    while ((modalStatus = [NXApp runModalSession:&theSession]) 
	    						== NX_RUNCONTINUES) {
		;
	    }
	    if (modalStatus != 1) {
		// cancel button was clicked
		goto endModal;
	    }
	    artWillGetNum = [oGetArticleNumField intValue];
	    if (artWillGetNum == 0) {
		goto endModal;
	    }
	    
	    // once stopModal is issued we have to restart modal loop?????
	    [NXApp endModalSession:&theSession];
	    [NXApp beginModalSession:&theSession for:oGetArticleNumWindow];
	    modalStatus = [NXApp runModalSession:&theSession];

	    first = [groupInfoD firstArticleForAmount:artWillGetNum];
	    [iPercentageView setMin:(float)first max:(float)last];
	    DBG(1,fprintf(stderr,"    first=%d\n",first));
	    for (i=first; i<=last && modalStatus == NX_RUNCONTINUES; i++) {
		// cancel or ok button will break modal loop
		modalStatus = [NXApp runModalSession:&theSession];

		if ([groupInfoD checkArticleIsRead:i]==NO) {
		    [self commandHead:i articleDB:articleDB];
		}
		[iPercentageView displayValue:(float)i];
	    }

endModal:
	    [oGetArticleNumWindow orderOut:self];
	    [NXApp endModalSession:&theSession];
	} else {
	    // article is not so much in server, so will not make any panel
	    // for alert the user
	    DPSStartWaitCursorTimer();
	    DPSFlush();
	    for (i=first; i<=last; i++) {
		if ([groupInfoD checkArticleIsRead:i]==NO) {
		    [self commandHead:i articleDB:articleDB];
		}
	    }
	}
	break;
    }
//    /* add groupInfoD to each articleItem */
//    for (i=0; i<[iArticleDB dataCount]; i++) {
//	[[iArticleDB objectAt:i] insertKeyedObject:groupInfoD];
//    }

    [self saveNewsRcFile];
    return articleDB;
}

- okArticleNumWindow:sender
{
    [NXApp stopModal:1];
    return self;
}

- cancelArticleNumWindow:sender
{
    [NXApp stopModal:0];
    return self;
}

- setOGetArticleNumWindow:kwindow
{
    oGetArticleNumWindow = kwindow;
    return self;
}

- setOGetArticleNumField:kfield
{
    oGetArticleNumField = kfield;
    return self;
}

- setIPercentageView:kview
{
    iPercentageView = kview;
    return self;
}

- (InfoD *)getHeaderByMessageId:(const char *)messageId zone:(NXZone *)zone
{
    return([self getHeader:messageId zone:zone]);
}

- (InfoD *)commandHead:(int)knum articleDB:karticleDB
{
    char        	articleNoString[LINE_CHR_MAX];
    InfoD		*header;
    IOrderedListD	*articleItem;
   
    sprintf (articleNoString, "%d", knum);
    if ((header = [self getHeader:articleNoString zone:headersZone]) != nil) {
        [header addInfoInt:knum key:ARTICLE_NUM];
        sprintf (articleNoString, "%010d", knum);
        articleItem = [[IOrderedListD allocFromZone:headersZone]
            initWithKey:articleNoString];
        [karticleDB insertKeyedObject:(IKeyedObject *)articleItem];
        [articleItem insertKeyedObject:(IKeyedObject *)header];
        return(header);
    } else {
        return(nil);
    }
}

- (InfoD *)getHeader:(const char *)articleTag zone:(NXZone *)zone
{
    char	combuf[512];
    int		statusCode;
    InfoD	*header;
    char        key[256], value[512], *value_p;
    char        buf[512], *buf_p;
    char        ch;

    sprintf (combuf, "%s %.506s", "head ", articleTag);
    statusCode = [self issueCommand:combuf];
    switch (statusCode){
    case OK_HEAD:
        break;
    case ERR_NOARTIG:
        DBG(1, fprintf(stderr, "WARNING: InntpTalk: \"%s\" no such article"
            " in this group\n", articleTag););
        fseek (inntpFile, (long)0, SEEK_END);
        return(nil);
        break;
    case ERR_NOART:
        DBG(1, fprintf(stderr, "WARNING: InntpTalk: no such article at all"););
        fseek (inntpFile, (long)0, SEEK_END);
        return(nil);
        break;
    default:
        NXRunAlertPanel(LoStr("NewsBase"),
	LoStr("InntpTalk: head command failed, with status code + %d"),
	NULL,NULL,NULL, statusCode);
        exit(1);
    }
    header = [[InfoD allocFromZone:zone] initWithKey:HEADER_INFO];
    while ((ch = fgetc(inntpFile)) !='.') {
        ungetc (ch, inntpFile);
        fscanf (inntpFile, "%512[^\r]\r\n", buf);
        if ( strchr(buf,':') != NULL ) {
	    /* first line of each field */
            sscanf (buf, "%256[^:]", key);
	    buf_p = buf;
            while (*(buf_p++) != ':')
                ;
            buf_p++;                            /* skip one space */
            strncpy (value, buf_p, sizeof(value));
	    if ([header addInfoString:value key:key]==NULL) {
		NXRunAlertPanel(LoStr("ERROR"),
		LoStr("error occured during adding InfoD"),
		LoStr("OK"),NULL,NULL);
		continue;
	    }
	} else {
	    /* if field value has multiple lines, go into here */
	    /* try to add value to previous value */
	    if ((strlen(value)+strlen(buf)+1) > 511) {
		/* over 512, give up to add following value */
		continue;
	    }
	    if ((value_p=strchr(value, '\0')) == NULL) {
		NXRunAlertPanel(LoStr("ERROR"),
		LoStr("value has no NULL char"),LoStr("OK"),NULL,NULL);
		continue;
	    }
	    *(value_p++) = ' '; *value_p = '\0'; 		/*add space*/
	    strncpy (value_p, buf, (sizeof(value)-strlen(value)));
	    if ([header addInfoString:value key:key]==NULL) {
		NXRunAlertPanel(LoStr("ERROR"),
		LoStr("error occured during adding InfoD"),
		LoStr("OK"),NULL,NULL);
		continue;
	    }
	}	    
    }
    fgetc(inntpFile); fgetc(inntpFile);  /* consume '\r' and '\n' */
    fseek (inntpFile, (long)0, SEEK_END);
    return(header);
}



- (BOOL)sendArticle:(const char *)messageId
{
    char        combuf[LINE_CHR_MAX];
    int         statusCode;
    char        ch[5];
    NXStream    *stream;
    int         streamLength;
    const char  *streamBuffer;
    int         length, maxLength;
    port_t      port;
    int         returnCode;
    NXStream	*convertedStream;
    extern void	j2e_conv();

    if (messageId == NULL) {
        NXRunAlertPanel(LoStr("WARNING"),
	LoStr("InntpTalk: can't find message_id"),LoStr("OK"),NULL,NULL);
        fseek (inntpFile, (long)0, SEEK_END);
        return(NO);
    }

    sprintf (combuf, "article %.504s", messageId);
    statusCode = [self issueCommand:combuf];
    if (statusCode != OK_ARTICLE) {
        switch (statusCode){
            case ERR_NOARTIG:
                NXRunAlertPanel(LoStr("WARNING"),
		LoStr("InntpTalk: no such article in this group"),
		LoStr("OK"),NULL,NULL);
                fseek (inntpFile, (long)0, SEEK_END);
                return(NO);
                break;
            case ERR_NOART:
                NXRunAlertPanel(LoStr("WARNING"),
		LoStr("InntpTalk: no such article at all"),
		LoStr("OK"),NULL,NULL);
                fseek (inntpFile, (long)0, SEEK_END);
                return(NO);
                break;
            default:
                NXRunAlertPanel(LoStr("ERROR"),
		LoStr("InntpTalk: body command failed"),LoStr("OK"),NULL,NULL);
                DBG(1, {
                    char buffer[512];
                    fprintf(stderr, "dump of remaining stream from NNTP "
                        "server follows:\n");
                    while(fgets(buffer, sizeof(buffer), inntpFile) != NULL) {
                        fputs(buffer, stderr);
                    }
                });
                fseek (inntpFile, (long)0, SEEK_END);
                return(NO);
                break;
            }
    }
    stream = NXOpenMemory(NULL, 0, NX_READWRITE);
    ch[0] = '\r';   
    ch[1] = '\n';
    ch[2] = fgetc(inntpFile);
    ch[3] = fgetc(inntpFile);
    ch[4] = fgetc(inntpFile);
    while (!(ch[0]=='\r' && ch[1]=='\n' && ch[2]=='.' && ch[3]=='\r' && ch[4]=='\n')) {
        NXPutc (stream, ch[2]);
        ch[0] = ch[1];
        ch[1] = ch[2];
        ch[2] = ch[3];
        ch[3] = ch[4];
        ch[4] = fgetc (inntpFile);
    }
    
    // check kanji code on server and convert 
    if (strcmp(NXGetDefaultValue(OWNER,KANJICODE),"JIS")==0) {
        NXSeek(stream, (long)0,NX_FROMSTART);
	convertedStream = NXOpenMemory(NULL, 0, NX_READWRITE);
	j2e_conv(stream, convertedStream);
	NXCloseMemory(stream, NX_FREEBUFFER);
	stream = convertedStream;
    }
    
    NXPutc (stream, '\0');
    streamLength = NXTell(stream);
    streamLength -= 1;		// ### REWRITE ###  for making sure 
    				// '\0' is really not needed any more.
    NXGetMemoryBuffer(stream, &streamBuffer, &length, &maxLength);
    if ((port = NXPortFromName(MMEDITOR, NULL)) == PORT_NULL) {
        NXRunAlertPanel([self name],LoStr("%s is an unknown port."),
            LoStr("OK"), NULL, NULL, MMEDITOR);
    }
    [[NXApp appSpeaker] setSendPort:port];
    if ((returnCode = [[NXApp appSpeaker] receiveArticle:streamBuffer
        length:streamLength]) != 0) {
        NXRunAlertPanel([self name],LoStr("cannot contact port %s."),
            LoStr("OK"), NULL, NULL, MMEDITOR);
    }
    
    DBG(20, write(creat("readStream", 0666), streamBuffer, streamLength));

    NXCloseMemory(stream, NX_FREEBUFFER);
    fseek (inntpFile, (long)0, SEEK_END);
    return(YES);
}

- (BOOL)canPost
{
//    if (*(char *)[[iCurrentNewsgroup link] infoForKey:CANPOST] == 'y') {
    if (*(char *)[[iCurrentNewsgroup dataForKey:GROUPINFO] 
    					infoForKey:CANPOST] == 'y') {    
        return YES;
    } else {
        return NO;
    }
}

- (BOOL)postArticle:(const char *)data length:(int)length
{
    int         statusCode;
    id postingAlertPanel;
    NXModalSession postingModalSession;
    NXStream	*stream, *convertedStream = NULL;
    const char	*buf;
    int		max, len;
    int		bytesSent;
    extern void e2j_conv_adj();

    DBG(1, fprintf(stderr, "length = %d\n*** start of article ***\n%.10000s\n"
        "*** end of article ***\n", length, data));
    DBG(20, write(creat("postStream", 0666), data, length));
    
    // check for duplicate message id
/*
    if ((header = [self getHeaderByMessageId:messageId
        zone:NXDefaultMallocZone()]) != nil) {
        NXRunAlertPanel(LoStr("NewsBase"),
                LoStr("Message-ID already exists on NNTP server"),
                NULL,NULL,NULL);
        free(header);
        return(NO);
    }
*/
    switch(NXRunAlertPanel(LoStr("NewsBase"),
	LoStr("Post to NNTP Server"),LoStr("Confirm"),LoStr("Cancel"),NULL)) {
    case NX_ALERTDEFAULT:
        break;
    case NX_ALERTALTERNATE:
        return(NO);
    }

    statusCode = [self issueCommand:"POST"];
    fseek(inntpFile, (long)0, SEEK_END);
    switch (statusCode) {
    case CONT_POST:
        break; 
    case ERR_NOPOST:
        NXRunAlertPanel(LoStr("NewsBase"),
		LoStr("POST command failed.  %d  posting not allowed."),
		NULL,NULL,NULL, statusCode);
        return(NO);
    default:
        // This should never occur!
        NXRunAlertPanel(LoStr("NewsBase"),
	LoStr("POST command failed. %d is status code returned by NNTP server.")	,NULL,NULL,NULL, statusCode);
        return(NO);
    }

    postingAlertPanel = NXGetAlertPanel(LoStr("NewsBase"),
        LoStr("Posting article to NNTP Server... Please wait.")
	,NULL, NULL, NULL);
    [NXApp beginModalSession:&postingModalSession for:postingAlertPanel];
    
    // copy input data
    stream = NXOpenMemory(data, length, NX_READONLY);
    NXSeek(stream, (long)0,NX_FROMSTART);
    convertedStream = NXOpenMemory(NULL, 0, NX_READWRITE);

    // check kanji code on server and convert 
    if (strcmp(NXGetDefaultValue(OWNER,KANJICODE),"JIS")==0) {
	e2j_conv_adj(stream, convertedStream);
    } else {
	e2e_adj(stream, convertedStream);
    }
    // convert stream to byte stream
    NXGetMemoryBuffer(convertedStream, &buf, &len, &max);
    NXCloseMemory(stream, NX_FREEBUFFER);

    while (len > 0) {
        bytesSent = send(nntpSocket, buf, len, 0);
        buf += bytesSent;
        len -= bytesSent;
    }
    if (convertedStream != NULL) {
	NXCloseMemory(convertedStream, NX_FREEBUFFER);
    }
    fseek(inntpFile, (long)0, SEEK_END);
    statusCode = [self _getResponse];
    fseek (inntpFile, (long)0, SEEK_END);
    [NXApp endModalSession:&postingModalSession];
    [postingAlertPanel orderOut:self];
    NXFreeAlertPanel(postingAlertPanel);
    
    switch (statusCode) {
    case OK_POSTED:
/*
        // check to make sure
        if ((header = [self getHeaderByMessageId:messageId
            zone:NXDefaultMallocZone()]) != nil) {
            NXRunAlertPanel(LoStr("NewsBase"),
		LoStr("Posting has been confirmed with NNTP server."),
		NULL,NULL,NULL);
            free(header);
            return(YES);
        } else {
//          B News Server will not return header
//          NXRunAlertPanel(LoStr("NewsBase"),LoStr("Posting failed! check size/newssgroup name."),NULL,NULL,NULL);
//          return(NO);
            return(YES);
        }
*/
        return(YES);
    case ERR_POSTFAIL:
        NXRunAlertPanel(LoStr("NewsBase"),
		LoStr("POST command failed.  %d  posting failed."),
		NULL,NULL,NULL, statusCode);
        return(NO);
    default:
        // This should never occur!
        NXRunAlertPanel(LoStr("NewsBase"),	
	LoStr("POST command failed. %d is status code returned by NNTP server.")	,NULL,NULL,NULL, statusCode);
        return(NO);
    }
}

- readNewsRcFile
{
    const char	*newsrc_file;
					/* "" = $HOME/.newsrc */
    newsrc_file = NXGetDefaultValue(OWNER,NEWSRCFILE);
    if ((iNewsRc=[[INewsRc allocFromZone:
	    	(NXZone *)newsgroupTreeZone] initFile:newsrc_file])==NULL) {
	return NULL;
    }
    [iNewsRc readRcfile];
    return self;
}

- (void)saveNewsRcFile
{
    [iNewsRc saveToRcfileFrom:iNewsGroupTreeRoot];
}

- free
{
    NXDestroyZone(newsgroupTreeZone);
    return([super free]);
}

static void reconnectEvent(DPSTimedEntry teNum, double now, void * nntp)
{
#ifdef DEBUG
    fprintf(stderr," ++++reconnecting to nntp server\n");
#endif

    if ([(id)nntp reconnectServer] == nil) {
	// reconnectServer is inplemented in "INNTP"
	// only make new connection and not issue "list" command
	NXRunAlertPanel(LoStr("NewsBase"),
	    LoStr("Can not auto recconect to nntp server."),
	    LoStr("OK"),NULL,NULL);
	[NXApp terminate:nntp];
    }
    return;
}

- reconnectServer:sender
{
    // called from menu and make new tree by "list" command
    [super reconnectServer];
    [self listAndMakeNewsgroupTree];
    return self;
}

- setTimedEntryForReconnect
{
    double	interval;
    int		priority = 1;
    
    interval = (double)atof(NXGetDefaultValue(OWNER,RECONNECTTIME)) * 60.0;
    DPSAddTimedEntry (interval, reconnectEvent, (id)self, priority);
    return self;
}

- cancelArticleMessageID:(const char *)messageID from:(const char *)fromValue
{
    NXStream	*articlestream;
    int len,max,size;
    char *buff;
    const char 	*cancelHeader = "Newsgroups: control\r\nSubject: cancel\r\ncontrol: cancel ";
    
    // From field should have been checked before this method.
    
    articlestream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
    NXPrintf(articlestream,"%s %s\r\nFrom: %s\r\n.\r\n", 
    				cancelHeader, messageID, fromValue);
    NXSeek(articlestream, (long)0,NX_FROMEND);
    (long)size = NXTell(articlestream);
     NXGetMemoryBuffer(articlestream,&buff, &len,  &max);
    [self postArticle:(const char *)buff length:(int)size];
     NXCloseMemory(articlestream,NX_FREEBUFFER);
    
    return(self);
}
    
@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.