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.