ftp.nice.ch/pub/next/connectivity/news/Alexandra-0.9.s.tar.gz#/alex/NewsgroupSet.m

This is NewsgroupSet.m in view mode; [Download] [Up]

#include <stdio.h>
#include <assert.h>
#include <math.h>
#import "Alexandra.h"
#import "NewsgroupSet.h"
#import "readline.h"
#import "Newsgroup.h"
#import "ArticleSetMatrix.h"
#import "NNTP.h"
#import "ArticleSet.h"
#import "ArticleViewControl.h"
#import <misckit/MiscSortedList.h>
#import "descriptors.h"
#import "response_codes.h"
#import "NewsGroupSetBrowser.h"
#import <misckit/MiscAppDefaults.h>
#import "MatrixScroller.h"

@implementation NewsgroupSet


- init
{
   [super init];
   myMList=[[MiscSortedList alloc] init];
   [myMList setSortEnabled:FALSE];
   [myMList setSortOrder:Misc_ASCENDING];
   [ERROR_MANAGER addObserver:self 
   			selector:@selector(dumpNewsrc:) forError:S15];
   [ERROR_MANAGER addObserver:self 
   			selector:@selector(updateNewsgroups) 
				forError:ENOTEPrefsChanged2];
					
   unselAction=@selector(clearMatrix);
   return self;
}

- free
{
   [myMList makeObjectsPerform:@selector(free)];
   [myMList free];
   [ERROR_MANAGER removeObserver:self forError:S15];
   [ERROR_MANAGER removeObserver:self forError:ENOTEPrefsChanged2];
	
   return [super free];
}

- scanNewsrcAndActive
{
   char aGroup[255];
   int i,j;
   FILE *newsrc;
   Newsgroup *aNewsgroup;
   HashTable *positionInList;
   const char *home;
   const char *nntp_server;
   char *filename;
   char *buffer;
   int position;
   BOOL exists;
   Storage *array;
   newsgroupDesc *ngDesc;
   int statusCode;

   
   // compose name of newsrc file
   home=NXHomeDirectory();
   nntp_server=[nntpServer serverName];
   filename=(char *)malloc((strlen(home)+strlen(nntp_server)+12)*sizeof(char));
   strcpy(filename,home);
   strcat(filename,"/.");
   strcat(filename,nntp_server);
   strcat(filename,".newsrc");

   // open newsrc file, parse all lines and make newsgroup list
   exists=FALSE;
   newsrc=fopen(filename,"r");
   if(newsrc!=NULL){
     exists=TRUE;
     while(!feof(newsrc)){
        readline(newsrc);
        buffer=get_buffer();
        i=0;
        while(buffer[i]!='\0' && buffer[i]!=':' && buffer[i]!='!'){
         i++;
        }
        if(buffer[i]!='\0'){
           strncpy(aGroup,buffer,i);
           aGroup[i]='\0';
           aNewsgroup=[[[[Newsgroup alloc] initTextCell:aGroup] setBogus:TRUE] unsetTag];
           if((buffer[i+1]!='\0')&&(buffer[i+2]!='\0'))
              [aNewsgroup setReadList:(buffer+i+2)];
           else
              [aNewsgroup setReadList:""];
           if(buffer[i]==':')
              [aNewsgroup setSubscribed];
           [myMList addObject:aNewsgroup];
        }
     }           
   }
   fclose(newsrc);
   free(filename);

   //put every newsgroupname in a hashtable
   j=[myMList count];
   positionInList=[[HashTable alloc] initKeyDesc:"*" valueDesc:"i" capacity:(int)floor(j*1.3)];
   for(i=0;i<j;i++)
      [positionInList insertKey:[[myMList objectAt:i] stringValue] value:(void *)i+1];

   //ISSUE LIST COMMAND
   //Slow link feature means don't do the "list" command
   if(([nntpServer slowLink])&&([nntpServer timeTag])&&(exists)){
      j=[myMList count];
      for(i=0;i<j;i++){
         aNewsgroup=[myMList objectAt:i];
         [[[aNewsgroup unsetTag] setBogus:FALSE] setPostable:'y'];
         if([aNewsgroup isSubscribed]==TRUE)
            if((statusCode=[nntpServer requestGroup:aNewsgroup])!=OK_GROUP){ //get min+max article
                [aNewsgroup setBogus:TRUE];
            }
      }
      
      //see if any new groups
      array=[[Storage alloc] initCount:0 elementSize:sizeof(newsgroupDesc) description:"{*llc}"];
      [nntpServer scanNewGroups:array];
      j=[array count];
      if(j>0) //new groups?
         for(i=0;i<j;i++){
            BOOL found;
            newsgroupDesc *aNGDesc=(newsgroupDesc *)[array elementAt:(unsigned)i];
            if([self lookupGroup:aNGDesc->groupname found:&found],found==FALSE){
               aNewsgroup=[[Newsgroup alloc] initTextCell:aNGDesc->groupname];
               [[[[[aNewsgroup setBogus:FALSE] setMin:aNGDesc->min] setMax:aNGDesc->max] setPostable:aNGDesc->post] setTag];  //set tag-> new group
               [myMList addObject:aNewsgroup];
            }
         }
      for(i=0;i<j;i++)
         free(((newsgroupDesc *)[array elementAt:(unsigned)i])->groupname);
      [array free];
   }
   else{ //NO SLOW LINK OR FIRST TIME PROGRAM WAS STARTED (NO TIMETAG)
      //get active newsgroups from nntp server
      array=[[Storage alloc] initCount:0 elementSize:sizeof(newsgroupDesc) description:"{*llc}"];
      [nntpServer scanActive:array];
      // parse active newsgroups
      j=[array count];
      for(i=0;i<j;i++){
         ngDesc=(newsgroupDesc *)[array elementAt:(unsigned)i];   
         position=(int)[positionInList valueForKey:ngDesc->groupname];
         //new group?
         if(position==0){
            aNewsgroup=[[[Newsgroup alloc] initTextCell:ngDesc->groupname] setTag];
            [myMList addObject:aNewsgroup];
		   }
         else
            aNewsgroup=[myMList objectAt:(position-1)]; // subtract one of position
         [[[[aNewsgroup setBogus:FALSE] setMin:ngDesc->min] setMax:ngDesc->max] setPostable:ngDesc->post];
         free(ngDesc->groupname);
      }
      [array free];
   }

   [positionInList free];

   //remove bogus and unvalid Newsgroups
   for(i=[myMList count]-1;i>=0;i--){
		id aNewsgroup=[myMList objectAt:i];
      if([aNewsgroup bogus] || strchr([aNewsgroup stringValue],':'))
         [[myMList removeObjectAt:i] free];
	}

   //approximate the number of unread articles in every group
   j=[myMList count];
   for(i=0;i<j;i++)
      [[myMList objectAt:i] approxNumUnread];

   //check for new newsgroups in newsgroup list (new ng <-> ng is taged)
   exists=FALSE;
   j=[myMList count];
   for(i=0;i<j;i++)
      if([[myMList objectAt:i] isTaged]==TRUE){
         exists=TRUE;
         break;
      }

   if(exists==TRUE)
      [myMatrix setButtonTitle:"New Newsgroups"]; //some new groups
   else{
      [myMatrix setButtonTitle:"Subscribed Newsgroups"]; //no new groups
      j=[myMList count];
      for(i=0;i<j;i++){
         aNewsgroup=[myMList objectAt:i];
         // setTag <-> newsgroup is visible in matrix <-> newsgroup is subscribed
         if([aNewsgroup isSubscribed]==TRUE)
            [aNewsgroup setTag];
         else
            [aNewsgroup unsetTag];
      }
   }

   return self;
}
       
- newGroups:sender
{
   Storage *array;
   int i,j;
   newsgroupDesc *aNGDesc;
   Newsgroup *aNewsgroup;
   BOOL found;

   array=[[Storage alloc] initCount:0 elementSize:sizeof(newsgroupDesc) description:"{*llc}"];

   [nntpServer scanNewGroups:array];

   j=[array count];
   if(j==0){
      NXBeep();
      [myMatrix updateButtonTitle];
   }
   else{
      [myMList makeObjectsPerform:@selector(unsetTag)];
      [myMatrix setButtonTitle:"New Newsgroups"];
      for(i=0;i<j;i++){
         aNGDesc=(newsgroupDesc *)[array elementAt:(unsigned)i];
         if([self lookupGroup:aNGDesc->groupname found:&found],found==FALSE){
            aNewsgroup=[[Newsgroup alloc] initTextCell:aNGDesc->groupname];
            [[[[aNewsgroup setBogus:FALSE] setMin:aNGDesc->min] setMax:aNGDesc->max] setPostable:aNGDesc->post];
            [[aNewsgroup approxNumUnread] setTag];
            [myMList addObject:aNewsgroup];
         }
      }
      [[myMatrix reloadMatrix] display];
      [self sync];
   }

   j=[array count];
   for(i=0;i<j;i++)
      free(((newsgroupDesc *)[array elementAt:(unsigned)i])->groupname);
   [array free];

   return self;
}

-newNews:sender
{
   int i,j;
   int statusCode;
   BOOL slowLink;
   id oldSelection;
   BOOL oldSelNewNews=FALSE;
   BOOL cellsRemoved=FALSE;
	
   j=[myMList count];
   if(j==0)
      return self;
   oldSelection=currentSelection;

   slowLink=[nntpServer slowLink];
   for(i=0;i<j;i++){
      Newsgroup *aGroup=[myMList objectAt:i];
      if(aGroup==nil) continue;
      if(([aGroup isSubscribed]==TRUE)||(aGroup==currentSelection)){
         long oldMax=[aGroup maxNumber];
         statusCode=[nntpServer requestGroup:aGroup];
         if(statusCode!=OK_GROUP){
            [myMatrix removeInvalidCell:aGroup andUpdate:FALSE];
            i--; //list length gets smaller
            j--;
            if(oldSelection==aGroup)
               oldSelection=nil;
            cellsRemoved=TRUE;
            }
         else{
            //BOOL setBack=TRUE;
            long newMax=[aGroup maxNumber];
            long diff=newMax-oldMax;
            if(diff>0){
               if(aGroup==oldSelection)
                  oldSelNewNews=TRUE;
               if((slowLink==TRUE)&&([aGroup isDelayed]==TRUE)){
                  [aGroup approxNumUnread];
                  //setBack=FALSE;
               }
               else
                  [aGroup incNumberUnreadArticles:(int)diff];
            }
         }
      }
   }
   if(cellsRemoved==TRUE)
      [myMatrix update];

	if(!strcmp([[myMatrix selectionButton] title],"Groups with Articles"))
		[self displayGroupsWithUnreadNews:self];

   if(currentSelection!=nil){
      statusCode=[nntpServer requestGroup:currentSelection];
      if(statusCode!=OK_GROUP){
         [myMatrix removeInvalidCell:currentSelection];
         return self;
      }
      if((oldSelNewNews==TRUE) && (currentSelection==oldSelection)){
         id artMatrix=[theArticleSet theMatrix];
         [currentSelection scanArticles:nntpServer visibleIn:artMatrix];
         [artMatrix reloadMatrix];
         [[[artMatrix display] window] flushWindow];
         [theArticleSet sync];
      }
      if(currentSelection!=oldSelection){
         currentSelection=nil;
         [self selectNewsgroup:self];
      }
   }
   if(cellsRemoved==FALSE)
      [myMatrix display];
   
   return self;
}

- updateNewsgroups
{
   int i,j=[myMList count];
	for(i=0;i<j;i++)
		[[myMList objectAt:i] updateArticles];

	[theArticleSet redisplayMatrix];		
	return self;
}
- dumpNewsrc:sender
{
   int i,j;
   Newsgroup *aNewsgroup;
   char *filename,*oldfile;
   const char *home;
   const char *nntp_server;
   NXStream *nrcStream;
   const char *aString;
   char aChar;

   if([myMList count]==0)
      return self;

   nrcStream=NXOpenMemory(NULL,0,NX_READWRITE);

   j=[myMList count];
   for(i=0;i<j;i++){
      aNewsgroup=[myMList objectAt:i];
      aString=[aNewsgroup stringValue];
      NX_ASSERT([aNewsgroup stringValue]!=NULL,"Newsgroup without name");
      NXWrite(nrcStream,aString,strlen(aString));
      if([aNewsgroup isSubscribed]==TRUE)
         NXWrite(nrcStream,": ",2);
      else
         NXWrite(nrcStream,"! ",2);
      [aNewsgroup dumpReadList:nrcStream];
      NXSeek(nrcStream,(-1)*sizeof(char),NX_FROMCURRENT);
      if(NXRead(nrcStream,&aChar,sizeof(char)),aChar==' ')
         NXSeek(nrcStream,(-1)*sizeof(char),NX_FROMCURRENT);
      NXWrite(nrcStream,"\n",1);
      }
	
	//rename + save new
	home=NXHomeDirectory();
   i=strlen(home);

   nntp_server=[nntpServer serverName];
   j=strlen(nntp_server);

   filename=(char *)calloc(i+j+12,sizeof(char));
	sprintf(filename,"%s/.%s.newsrc",home,nntp_server);

   oldfile=(char *)malloc((strlen(filename)+2)*sizeof(char));
   strcpy(oldfile,filename);
   strcat(oldfile,"~");
   rename(filename,oldfile);
   free(oldfile);

   NXSaveToFile(nrcStream,filename);
   NXCloseMemory(nrcStream,NX_FREEBUFFER);
   free(filename);
	
   return self;
}

- displayAllGroups:sender
{
   int i,j;

   j=[myMList count];
   for(i=0;i<j;i++)
      [[myMList objectAt:i] setTag];
   [[myMatrix reloadMatrix] display];

   [myMatrix setButtonTitle:"All Newsgroups"];

   return self;
}

- displaySubscribedGroups:sender
{
   int i,j;
   Newsgroup *oldNG;

   j=[myMList count];
   for(i=0;i<j;i++){
      Newsgroup* aNewsgroup;
      aNewsgroup=[myMList objectAt:i];
      if([aNewsgroup isSubscribed])
         [aNewsgroup setTag];
      else
         [aNewsgroup unsetTag];
   }
   [[myMatrix reloadMatrix] display];

   oldNG=currentSelection;
   [self sync];
   if((numSelCells==1)&&(oldNG!=currentSelection)){
      currentSelection=oldNG;
      [self selectNewsgroup:self];
   }

   [myMatrix setButtonTitle:"Subscribed Newsgroups"];

   return self;
}

- displayGroupsWithUnreadNews:sender
{
   int i,j;
   Newsgroup *oldNG;

   j=[myMList count];
   for(i=0;i<j;i++){
      Newsgroup* aNewsgroup;
      aNewsgroup=[myMList objectAt:i];
      if([aNewsgroup isSubscribed] && [aNewsgroup numberUnreadArticles]>0)
         [aNewsgroup setTag];
      else
         [aNewsgroup unsetTag];
   }
	if(currentSelection!=nil)
		[currentSelection setTag];
		
   [[myMatrix reloadMatrix] display];

   oldNG=currentSelection;
   [self sync];
   if((numSelCells==1)&&(oldNG!=currentSelection)){
      currentSelection=oldNG;
      [self selectNewsgroup:self];
   }

   [myMatrix setButtonTitle:"Groups with Articles"];

   return self;
}

- selectNewsgroup:sender
{
   Newsgroup* oldNG;

   [[myMatrix window] makeFirstResponder:myMatrix];
   oldNG=currentSelection;
   [self sync];

   if(oldNG!=currentSelection)
      if(numSelCells==1){
         int statusCode=[nntpServer requestGroup:currentSelection];
         if(statusCode!=OK_GROUP){
            [myMatrix perform:@selector(removeInvalidCell:) with:currentSelection afterDelay:0.0 cancelPrevious:YES];
            return self;
         }
        [currentSelection scanArticles:nntpServer];

        [[theArticleSet theMatrix] setButtonTitle:"Unread Articles"];
        [theArticleSet loadGroup:currentSelection];
        [myMatrix display];
		  [self dumpNewsrc:self];
      }
      else
         [nntpServer unselectCurrentGroup];
   
   return self;
}

- (BOOL)selectNewsgroupNamed:(const char *)gname
{
	BOOL found;
	Newsgroup *aGroup;
	int position;
	
	position=[self lookupGroup:gname found:&found];
	if(!found)
		return FALSE;

	aGroup=[myMList objectAt:position];
	if(![aGroup isTaged])
		[self displayAllGroups:self];
		
	return [myMatrix selectNewsgroupNamed:gname];
}
	
- subscribeOrUnsubscribe:(BOOL)flag
{
   /*flag==TRUE <=> subscribe */
   int i,j;
   List *selectionList=nil;
   Newsgroup *aNewsgroup;

   selectionList=[myMatrix getCurrSelections];
   j=[selectionList count];
   for(i=0;i<j;i++){
      aNewsgroup=[selectionList objectAt:i];
      if(flag==TRUE)
         [aNewsgroup setSubscribed];
      else
         [aNewsgroup setUnsubscribed];
   }
   [selectionList free];
   [myMatrix display];
   return self;
}
  
- subscribe:sender
{
   [self subscribeOrUnsubscribe:TRUE];
   return self;
}

- unsubscribe:sender
{
   [self subscribeOrUnsubscribe:FALSE];
   return self;
}

- markAllRead:sender
{
   int i,j;
   id ng=[myMatrix getCurrSelections];

   j=[ng count];
   if(j==0){
      NXRunAlertPanel("ALEXANDRA","No newsgroup(s) selected.",NULL,NULL,NULL);
      return self;
   }
	else if(j>1){
		int r=NXRunAlertPanel("ALEXANDRA",
						"Catchup %d selected groups?",NULL,"Cancel",NULL,j);
		if(r==NX_ALERTALTERNATE)
			return self;
	}
	
   for(i=0;i<j;i++)
      [[ng objectAt:i] markAllReadUntil:nil];

   if(j==1)
      [theArticleSet redisplayMatrix];
   [myMatrix display];

   return self;
}

- markAllUntilSelRead:sender;
{
   id ng=[myMatrix getCurrSelections];

   if([ng count]!=1)
      return self;

   [[ng objectAt:0] markAllReadUntil:[theArticleSet currentSelection]];
   [theArticleSet redisplayMatrix];
   [myMatrix display];

   return self;
}

- markAllClever:sender;
{
	id selectedArticle;
	static time_t lastCatchup=0;	

	if(!currentSelection)
		return [self markAllRead:self];
		
	selectedArticle=[theArticleSet currentSelection];
	if(!selectedArticle)
		[self markAllRead:sender];
	else{
		time_t now=time(NULL);
		if(now>lastCatchup+3)
			[self markAllUntilSelRead:self];
		else
			[self markAllRead:sender];
		lastCatchup=now;
	}
	
	return self;
}

- (int)lookupGroup:(const char *)aGroup found:(BOOL *)f
{
   int cmpResult,j;
   Newsgroup *aNewsgroup;

   for(j=[myMList count]-1;j>=0;j--){
      aNewsgroup=[myMList objectAt:j];
      cmpResult=strcmp([aNewsgroup stringValue],aGroup);
      if(cmpResult==0){
         *f=TRUE;
         return j;
      }
   }
   *f=FALSE;
   return(0);
}

- shuffleList:(id)sourceCell to:(id)destCell
{
   unsigned int from,to;

   from=[myMList indexOf:sourceCell];
   to=[myMList indexOf:destCell];

   NX_ASSERT(from!=NX_NOT_IN_LIST,"Internal DS mismatch");
   NX_ASSERT(to!=NX_NOT_IN_LIST,"Internal DS mismatch");

   [myMList removeObjectAt:from];
   [myMList insertObject:sourceCell at:to];
  
   return self;
}

- upOrDown:(int)delta
{
   while([[theArticleSet theMatrix] selectNextCell:delta]==FALSE)
      if([myMatrix selectNextCell:delta]==FALSE){
         NXRunAlertPanel("ALEXANDRA","No more articles.",NULL,NULL,NULL);
         break;
      }
   return self;
}

- upOrDownOnePage:(int)delta
{
   NXRect wholeBox;
   NXRect visibleBox;
   id theText=[theArticleViewControl theText];

   [theText getVisibleRect:&visibleBox];
   [theText getFrame:&wholeBox];

   if(((delta==-1) && NX_Y(&visibleBox)<=3.0) || 
      ((delta==1) && (NX_HEIGHT(&wholeBox) - NX_Y(&visibleBox) - NX_HEIGHT(&visibleBox))<=3.0))
      [self upOrDown:delta];
   else{
      NX_Y(&visibleBox)+=delta*(NX_HEIGHT(&visibleBox)*0.75); 
      [theText scrollRectToVisible:&visibleBox];
   }

   return self;
}

- up:sender
{
   [self upOrDown:-1];
   return self;
}

- down:sender
{
   [self upOrDown:1];
   return self;
}

- upOnePage:sender
{
   [self upOrDownOnePage:-1];
   return self;
}

- downOnePage:sender
{
   [self upOrDownOnePage:1];
   return self;
}

- sortNewsgroupList:sender
{
	[myMList unsort];
   [myMList sort];
   [myMatrix reloadMatrix];
   [myMatrix display];

   return self;
}

- switchToView:view
{
   NXRect frame;
   id sview;
   id newOther,newMatrix;
   char *defaultViewerStr;
   const char *nntpname;

   if([myMatrix isKindOf:[NewsGroupSetBrowser class]]){
      newOther=myMatrix;
      newMatrix=[otherView docView];
   }
   else{
      newOther=[[myMatrix superview] superview];
      newMatrix=otherView;
   }
   sview=[newOther superview];
   [newOther getFrame:&frame];

   [newOther removeFromSuperview];
   [sview addSubview:otherView];
   [otherView setFrame:&frame];
   myMatrix=newMatrix;
   [myMatrix loadMatrix];

   if(currentSelection!=nil)
      if([myMatrix isKindOf:[NewsGroupSetBrowser class]]){
         const char *selName=[currentSelection stringValue];
         char *path=(char *)malloc((strlen(selName)+2)*sizeof(char));
         sprintf(path,".%s",selName);
         [[myMatrix window] disableFlushWindow];
         [myMatrix setAutodisplay:NO];
         [myMatrix setPath:path];
         [[myMatrix setAutodisplay:YES] display];
         [[[myMatrix window] reenableFlushWindow] flushWindow];
         free(path);
      }
      else{
         [otherView display];
         [myMatrix selectCell:currentSelection];
      }
   else
      [otherView display]; 
   otherView=newOther;
  
   nntpname=[nntpServer serverName];
   defaultViewerStr=(char *)malloc((strlen(nntpname)+21)*sizeof(char));
   sprintf(defaultViewerStr,"BrowserViewEnabled %s",nntpname);
   if([otherView isKindOf:[NewsGroupSetBrowser class]])
      [NXApp setDefault:defaultViewerStr toBool:NO];
   else
      [NXApp setDefault:defaultViewerStr toBool:YES];
   free(defaultViewerStr);
	
   return self;
}

- switchToListView:sender
{
   if([myMatrix isKindOf:[NewsGroupSetBrowser class]]){
      id aCell;
      [self switchToView:otherView];
      if((aCell=[myMatrix selectedCell])!=nil)
         [myMatrix scrollCellToVisible:[[myMatrix cellList] indexOf:aCell]
                   upperOffset:1.5 lowerOffset:1.5];
   }
   return self;
}

- switchToBrowserView:sender
{
   if(![myMatrix isKindOf:[NewsGroupSetBrowser class]]){
      [self switchToView:otherView];
      [myMatrix scrollSelectionToVisible];
   }
   return self;
}



- (BOOL)selectViewCellEnabled:menuCell
{
	if([menuCell action]==@selector(switchToListView:) && 
      ([myMatrix isKindOf:[NewsGroupSetBrowser class]]))
         return TRUE;
	if([menuCell action]==@selector(switchToBrowserView:) &&
      (![myMatrix isKindOf:[NewsGroupSetBrowser class]]))
         return TRUE;
   return FALSE;
}


- (BOOL)selectURL:(const char *)urlstring
{
	BOOL success=FALSE;
	BOOL apanelWasRunning=FALSE;
	
	char *groupname=NULL;
	long seqnumber=0;
	char *msgid=NULL;
	BOOL isNewsURL=FALSE;
	char *buf1,*buf2,*buf3;
	
	if(!urlstring)
		return FALSE;
		
	buf1=NXCopyStringBuffer(urlstring);
	
	// nntp or news url?
	buf2=strstr(buf1,"news:");
	if(buf2){
		success=TRUE;
		isNewsURL=TRUE;
		buf2+=5;
	}
	else{
		buf2=strstr(buf1,"nntp:");
		if(buf2){
			success=TRUE;
			isNewsURL=FALSE;
			buf2+=5;
		}
	}
	
	if(!success){
		NXRunAlertPanel("Service","No valid URL.",NULL,NULL,NULL);
		apanelWasRunning=TRUE;
	}
	
	if(success){
	
		[[myMatrix window] makeKeyAndOrderFront:self];
			
		//skip any / or < chars
		while(*buf2=='/' || *buf2=='<')
			buf2++;
			
		//get useful string
		buf3=strpbrk(buf2,"\t >\r\n");
		if(buf3)
			*buf3='\0';
			
		if(isNewsURL){
			if(strchr(buf2,'@')){ // message-id news url
				char **groups;
				
				// first compose real messageid
				msgid=malloc((strlen(buf2)+4)*sizeof(char));
				strcpy(msgid,"<");
				strcat(msgid,buf2);
				strcat(msgid,">");
				
				// get all groupnames
				success=[nntpServer findArticle:msgid inGroups:&groups];
				if(!success){
					NXRunAlertPanel("Service","Article not available.",NULL,NULL,NULL);
					apanelWasRunning=TRUE;
				}
				
				//find all group objects
				if(success){
					char **gp=groups;
					MiscSortedList *groupList=[[MiscSortedList alloc] init];
					[groupList setSortEnabled:NO];
					[groupList setSortOrder:Misc_ASCENDING];
					while(*gp){
						BOOL found=FALSE;
						int pos=[self lookupGroup:*gp found:&found];
						if(found)
							[groupList addObject:[myMList objectAt:pos]];
						free(*gp);
						gp++;
					}
					
					// find best group
					if([groupList count]==0){
						success=FALSE;
						NXRunAlertPanel("Service","There's no such article available.",NULL,NULL,NULL);
						apanelWasRunning=TRUE;
					}
					else{
						int oldStype=[Newsgroup sortType];
						[Newsgroup setSortType:SORT_BY_SELECT_PRIORITY];
						[groupList sort];
						[Newsgroup setSortType:oldStype];
						groupname=NXCopyStringBuffer([[groupList objectAt:0] stringValue]);
					}
					[groupList free];
				}
			}			
			else{
				groupname=NXCopyStringBuffer(buf2);
			}
		}
		else{
			char *t;
			if(t=strchr(buf2,'/')){
				*t='\0';
				groupname=NXCopyStringBuffer(buf2);
				seqnumber=atol(t+1);
			}
			else{
				success=FALSE;
				NXRunAlertPanel("Service","No valid URL.",NULL,NULL,NULL);
				apanelWasRunning=TRUE;
			}	
		}
	}
	
	if(success){

		// select group
		success=[self selectNewsgroupNamed:groupname];
		if(!success){
			NXRunAlertPanel("Service","There's no newsgroup named '%s'.",NULL,NULL,NULL,groupname);
			apanelWasRunning=TRUE;
		}
	}
	
	if(success){

		// select article
		if(isNewsURL && msgid)
			success=[theArticleSet selectArticleWithMsgid:msgid]; 
		else if(!isNewsURL && seqnumber>0)
			success=[theArticleSet selectArticleWithNumber:seqnumber];
		if(!success){
			NXRunAlertPanel("Service","Could not find article.",NULL,NULL,NULL);
			apanelWasRunning=TRUE;
		}
	}
	
	if(groupname) free(groupname);
	if(msgid) free(msgid);
	if(buf1) free(buf1);
	
	if(!success && !apanelWasRunning)
		NXRunAlertPanel("Service","Could not perform service.",NULL,NULL,NULL);
		
	return success;
}


@end

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