/* KNode, the KDE newsreader Copyright (c) 1999-2005 the KNode authors. See file AUTHORS for details This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US */ #include <ksimpleconfig.h> #include <tdelocale.h> #include <kdebug.h> #include <kqcstringsplitter.h> #include "knprotocolclient.h" #include "knglobals.h" #include "kncollectionviewitem.h" #include "kngrouppropdlg.h" #include "utilities.h" #include "knconfigmanager.h" #include "knmainwidget.h" #include "knscoring.h" #include "knarticlemanager.h" #include "kngroupmanager.h" #include "knnntpaccount.h" #include "headerview.h" #define SORT_DEPTH 5 KNGroup::KNGroup(KNCollection *p) : KNArticleCollection(p), n_ewCount(0), l_astFetchCount(0), r_eadCount(0), i_gnoreCount(0), l_astNr(0), m_axFetch(0), d_ynDataFormat(1), f_irstNew(-1), l_ocked(false), u_seCharset(false), s_tatus(unknown), i_dentity(0) { mCleanupConf = new KNConfig::Cleanup( false ); } KNGroup::~KNGroup() { delete i_dentity; delete mCleanupConf; } TQString KNGroup::path() { return p_arent->path(); } const TQString& KNGroup::name() { static TQString ret; if(n_ame.isEmpty()) ret=g_roupname; else ret=n_ame; return ret; } void KNGroup::updateListItem() { if(!l_istItem) return; l_istItem->setTotalCount( c_ount ); l_istItem->setUnreadCount( c_ount - r_eadCount - i_gnoreCount ); l_istItem->repaint(); } bool KNGroup::readInfo(const TQString &confPath) { KSimpleConfig info(confPath); g_roupname = info.readEntry("groupname"); d_escription = info.readEntry("description"); n_ame = info.readEntry("name"); c_ount = info.readNumEntry("count",0); r_eadCount = info.readNumEntry("read",0); if (r_eadCount > c_ount) r_eadCount = c_ount; f_irstNr = info.readNumEntry("firstMsg",0); l_astNr = info.readNumEntry("lastMsg",0); d_ynDataFormat = info.readNumEntry("dynDataFormat",0); u_seCharset = info.readBoolEntry("useCharset", false); d_efaultChSet = info.readEntry("defaultChSet").latin1(); TQString s = info.readEntry("status","unknown"); if (s=="readOnly") s_tatus = readOnly; else if (s=="postingAllowed") s_tatus = postingAllowed; else if (s=="moderated") s_tatus = moderated; else s_tatus = unknown; c_rosspostIDBuffer = info.readListEntry("crosspostIDBuffer"); i_dentity=new KNConfig::Identity(false); i_dentity->loadConfig(&info); if(!i_dentity->isEmpty()) { kdDebug(5003) << "KNGroup::readInfo(const TQString &confPath) : using alternative user for " << g_roupname << endl; } else { delete i_dentity; i_dentity=0; } mCleanupConf->loadConfig( &info ); return (!g_roupname.isEmpty()); } void KNGroup::saveInfo() { TQString dir(path()); if (!dir.isNull()) { KSimpleConfig info(dir+g_roupname+".grpinfo"); info.writeEntry("groupname", g_roupname); info.writeEntry("description", d_escription); info.writeEntry("firstMsg", f_irstNr); info.writeEntry("lastMsg", l_astNr); info.writeEntry("count", c_ount); info.writeEntry("read", r_eadCount); info.writeEntry("dynDataFormat", d_ynDataFormat); info.writeEntry("name", n_ame); info.writeEntry("useCharset", u_seCharset); info.writeEntry("defaultChSet", TQString::fromLatin1(d_efaultChSet)); switch (s_tatus) { case unknown: info.writeEntry("status","unknown"); break; case readOnly: info.writeEntry("status","readOnly"); break; case postingAllowed: info.writeEntry("status","postingAllowed"); break; case moderated: info.writeEntry("status","moderated"); break; } info.writeEntry("crosspostIDBuffer", c_rosspostIDBuffer); if(i_dentity) i_dentity->saveConfig(&info); else if(info.hasKey("Email")) { info.deleteEntry("Name", false); info.deleteEntry("Email", false); info.deleteEntry("Reply-To", false); info.deleteEntry("Mail-Copies-To", false); info.deleteEntry("Org", false); info.deleteEntry("UseSigFile", false); info.deleteEntry("UseSigGenerator", false); info.deleteEntry("sigFile", false); info.deleteEntry("sigText", false); } mCleanupConf->saveConfig( &info ); } } KNNntpAccount* KNGroup::account() { KNCollection *p=parent(); while(p->type()!=KNCollection::CTnntpAccount) p=p->parent(); return (KNNntpAccount*)p_arent; } bool KNGroup::loadHdrs() { if(isLoaded()) { kdDebug(5003) << "KNGroup::loadHdrs() : nothing to load" << endl; return true; } kdDebug(5003) << "KNGroup::loadHdrs() : loading headers" << endl; TQCString buff, hdrValue; KNFile f; KTQCStringSplitter split; int cnt=0, id, lines, fileFormatVersion, artNumber; unsigned int timeT; KNRemoteArticle *art; TQString dir(path()); if (dir.isNull()) return false; f.setName(dir+g_roupname+".static"); if(f.open(IO_ReadOnly)) { if(!resize(c_ount)) { f.close(); return false; } while(!f.atEnd()) { buff=f.readLine(); if(buff.isEmpty()){ if (f.status() == IO_Ok) { kdWarning(5003) << "Found broken line in static-file: Ignored!" << endl; continue; } else { kdError(5003) << "Corrupted static file, IO-error!" << endl; clear(); return false; } } split.init(buff, "\t"); art=new KNRemoteArticle(this); split.first(); art->messageID()->from7BitString(split.string()); split.next(); art->subject()->from7BitString(split.string()); split.next(); art->from()->setEmail(split.string()); split.next(); if(split.string()!="0") art->from()->setNameFrom7Bit(split.string()); buff=f.readLine(); if(buff!="0") art->references()->from7BitString(buff.copy()); buff=f.readLine(); if (sscanf(buff,"%d %d %u %d", &id, &lines, &timeT, &fileFormatVersion) < 4) fileFormatVersion = 0; // KNode <= 0.4 had no version number art->setId(id); art->lines()->setNumberOfLines(lines); art->date()->setUnixTime(timeT); if (fileFormatVersion > 0) { buff=f.readLine(); sscanf(buff,"%d", &artNumber); art->setArticleNumber(artNumber); } // optional headers if (fileFormatVersion > 1) { // first line is the number of addiotion headers buff = f.readLine(); // following lines contain one header per line for (uint i = buff.toUInt(); i > 0; --i) { buff = f.readLine(); int pos = buff.find(':'); TQCString hdrName = buff.left( pos ); // skip headers we already set above and which we actually never should // find here, but however it still happens... (eg. #101355) if ( hdrName == "Subject" || hdrName == "From" || hdrName == "Date" || hdrName == "Message-ID" || hdrName == "References" || hdrName == "Bytes" || hdrName == "Lines" ) continue; hdrValue = buff.right( buff.length() - (pos + 2) ); if (hdrValue.length() > 0) art->setHeader( new KMime::Headers::Generic( hdrName, art, hdrValue ) ); } } if(append(art)) cnt++; else { f.close(); clear(); return false; } } setLastID(); f.close(); } else { clear(); return false; } f.setName(dir+g_roupname+".dynamic"); if (f.open(IO_ReadOnly)) { dynDataVer0 data0; dynDataVer1 data1; int readCnt=0,byteCount,dataSize; if (d_ynDataFormat==0) dataSize = sizeof(data0); else dataSize = sizeof(data1); while(!f.atEnd()) { if (d_ynDataFormat==0) byteCount = f.readBlock((char*)(&data0), dataSize); else byteCount = f.readBlock((char*)(&data1), dataSize); if ((byteCount == -1)||(byteCount!=dataSize)) if (f.status() == IO_Ok) { kdWarning(5003) << "Found broken entry in dynamic-file: Ignored!" << endl; continue; } else { kdError(5003) << "Corrupted dynamic file, IO-error!" << endl; clear(); return false; } if (d_ynDataFormat==0) art=byId(data0.id); else art=byId(data1.id); if(art) { if (d_ynDataFormat==0) data0.getData(art); else data1.getData(art); if (art->isRead()) readCnt++; } } f.close(); r_eadCount=readCnt; } else { clear(); return false; } kdDebug(5003) << cnt << " articles read from file" << endl; c_ount=length(); // convert old data files into current format: if (d_ynDataFormat!=1) { saveDynamicData(length(), true); d_ynDataFormat=1; } // restore "New" - flags if( f_irstNew > -1 ) { for( int i = f_irstNew; i < length(); i++ ) { at(i)->setNew(true); } } updateThreadInfo(); processXPostBuffer(false); return true; } bool KNGroup::unloadHdrs(bool force) { if(l_ockedArticles>0) return false; if (!force && isNotUnloadable()) return false; KNRemoteArticle *a; for(int idx=0; idx<length(); idx++) { a=at(idx); if (a->hasContent() && !knGlobals.articleManager()->unloadArticle(a, force)) return false; } syncDynamicData(); clear(); return true; } // Attention: this method is called from the network thread! void KNGroup::insortNewHeaders(TQStrList *hdrs, TQStrList *hdrfmt, KNProtocolClient *client) { KNRemoteArticle *art=0, *art2=0; TQCString data, hdr, hdrName; KTQCStringSplitter split; split.setIncludeSep(false); int new_cnt=0, added_cnt=0, todo=hdrs->count(); TQTime timer; l_astFetchCount=0; if(!hdrs || hdrs->count()==0) return; timer.start(); //resize the list if(!resize(size()+hdrs->count())) return; // recreate msg-ID index syncSearchIndex(); // remember index of first new if(f_irstNew == -1) f_irstNew = length(); // index of last + 1 for(char *line=hdrs->first(); line; line=hdrs->next()) { split.init(line, "\t"); //new Header-Object art=new KNRemoteArticle(this); art->setNew(true); //Article Number split.first(); art->setArticleNumber(split.string().toInt()); //Subject split.next(); art->subject()->from7BitString(split.string()); if(art->subject()->isEmpty()) art->subject()->fromUnicodeString(i18n("no subject"), art->defaultCharset()); //From and Email split.next(); art->from()->from7BitString(split.string()); //Date split.next(); art->date()->from7BitString(split.string()); //Message-ID split.next(); art->messageID()->from7BitString(split.string().simplifyWhiteSpace()); //References split.next(); if(!split.string().isEmpty()) art->references()->from7BitString(split.string()); //use TQCString::copy() ? // Bytes split.next(); //Lines split.next(); art->lines()->setNumberOfLines(split.string().toInt()); // optinal additional headers mOptionalHeaders = *hdrfmt; for (hdr = hdrfmt->first(); !hdr.isNull(); hdr = hdrfmt->next()) { if (!split.next()) break; data = split.string(); int pos = hdr.find(':'); hdrName = hdr.left( pos ); // if the header format is 'full' we have to strip the header name if (hdr.findRev("full") == (int)(hdr.length() - 4)) data = data.right( data.length() - (hdrName.length() + 2) ); // add header art->setHeader( new KMime::Headers::Generic( hdrName, art, data ) ); } // check if we have this article already in this group, // if so mark it as new (useful with leafnodes delay-body function) art2=byMessageId(art->messageID()->as7BitString(false)); if(art2) { // ok, we already have this article art2->setNew(true); art2->setArticleNumber(art->articleNumber()); delete art; new_cnt++; } else if (append(art)) { added_cnt++; new_cnt++; } else { delete art; return; } if (timer.elapsed() > 200) { // don't flicker timer.restart(); if (client) client->updatePercentage((new_cnt*30)/todo); } } // now we build the threads syncSearchIndex(); // recreate the msgId-index so it contains the appended headers buildThreads(added_cnt, client); updateThreadInfo(); // save the new headers saveStaticData(added_cnt); saveDynamicData(added_cnt); // update group-info c_ount=length(); n_ewCount+=new_cnt; l_astFetchCount=new_cnt; updateListItem(); saveInfo(); } int KNGroup::saveStaticData(int cnt,bool ovr) { int idx, savedCnt=0, mode; KNRemoteArticle *art; TQString dir(path()); if (dir.isNull()) return 0; TQFile f(dir+g_roupname+".static"); if(ovr) mode=IO_WriteOnly; else mode=IO_WriteOnly | IO_Append; if(f.open(mode)) { TQTextStream ts(&f); ts.setEncoding(TQTextStream::Latin1); for(idx=length()-cnt; idx<length(); idx++) { art=at(idx); if(art->isExpired()) continue; ts << art->messageID()->as7BitString(false) << '\t'; ts << art->subject()->as7BitString(false) << '\t'; ts << art->from()->email() << '\t'; if(art->from()->hasName()) ts << art->from()->nameAs7Bit() << '\n'; else ts << "0\n"; if(!art->references()->isEmpty()) ts << art->references()->as7BitString(false) << "\n"; else ts << "0\n"; ts << art->id() << ' '; ts << art->lines()->numberOfLines() << ' '; ts << art->date()->unixTime() << ' '; ts << "2\n"; // version number to achieve backward compatibility easily ts << art->articleNumber() << '\n'; // optional headers ts << mOptionalHeaders.count() << '\n'; for (TQCString hdrName = mOptionalHeaders.first(); !hdrName.isNull(); hdrName = mOptionalHeaders.next()) { hdrName = hdrName.left( hdrName.find(':') ); KMime::Headers::Base *hdr = art->getHeaderByType( hdrName ); if ( hdr ) ts << hdrName << ": " << hdr->asUnicodeString() << '\n'; else ts << hdrName << ": \n"; } savedCnt++; } f.close(); } return savedCnt; } void KNGroup::saveDynamicData(int cnt,bool ovr) { dynDataVer1 data; int mode; KNRemoteArticle *art; if(length()>0) { TQString dir(path()); if (dir.isNull()) return; TQFile f(dir+g_roupname+".dynamic"); if(ovr) mode=IO_WriteOnly; else mode=IO_WriteOnly | IO_Append; if(f.open(mode)) { for(int idx=length()-cnt; idx<length(); idx++) { art=at(idx); if(art->isExpired()) continue; data.setData(art); f.writeBlock((char*)(&data), sizeof(data)); art->setChanged(false); } f.close(); } else KNHelper::displayInternalFileError(); } } void KNGroup::syncDynamicData() { dynDataVer1 data; int cnt=0, readCnt=0, sOfData; KNRemoteArticle *art; if(length()>0) { TQString dir(path()); if (dir.isNull()) return; TQFile f(dir+g_roupname+".dynamic"); if(f.open(IO_ReadWrite)) { sOfData=sizeof(data); for(int i=0; i<length(); i++) { art=at(i); if(art->hasChanged() && !art->isExpired()) { data.setData(art); f.at(i*sOfData); f.writeBlock((char*) &data, sOfData); cnt++; art->setChanged(false); } if(art->isRead() && !art->isExpired()) readCnt++; } f.close(); kdDebug(5003) << g_roupname << " => updated " << cnt << " entries of dynamic data" << endl; r_eadCount=readCnt; } else KNHelper::displayInternalFileError(); } } void KNGroup::appendXPostID(const TQString &id) { c_rosspostIDBuffer.append(id); } void KNGroup::processXPostBuffer(bool deleteAfterwards) { TQStringList remainder; KNRemoteArticle *xp; KNRemoteArticle::List al; for (TQStringList::Iterator it = c_rosspostIDBuffer.begin(); it != c_rosspostIDBuffer.end(); ++it) { if ((xp=byMessageId((*it).local8Bit()))) al.append(xp); else remainder.append(*it); } knGlobals.articleManager()->setRead(al, true, false); if (!deleteAfterwards) c_rosspostIDBuffer = remainder; else c_rosspostIDBuffer.clear(); } void KNGroup::buildThreads(int cnt, KNProtocolClient *client) { int end=length(), start=end-cnt, foundCnt=0, bySubCnt=0, refCnt=0, resortCnt=0, idx, oldRef; // idRef; KNRemoteArticle *art, *ref; TQTime timer; timer.start(); // this method is called from the nntp-thread!!! #ifndef NDEBUG tqDebug("knode: KNGroup::buildThreads() : start = %d end = %d",start,end); #endif //resort old hdrs if(start>0) for(idx=0; idx<start; idx++) { art=at(idx); if(art->threadingLevel()>1) { oldRef=art->idRef(); ref=findReference(art); if(ref) { // this method is called from the nntp-thread!!! #ifndef NDEBUG tqDebug("knode: %d: old %d new %d",art->id(), oldRef, art->idRef()); #endif resortCnt++; art->setChanged(true); } } } for(idx=start; idx<end; idx++) { art=at(idx); if(art->idRef()==-1 && !art->references()->isEmpty() ){ //hdr has references refCnt++; if(findReference(art)) foundCnt++; } else { if(art->subject()->isReply()) { art->setIdRef(0); //hdr has no references art->setThreadingLevel(0); } else if(art->idRef()==-1) refCnt++; } if (timer.elapsed() > 200) { // don't flicker timer.restart(); if(client) client->updatePercentage(30+((foundCnt)*70)/cnt); } } if(foundCnt<refCnt) { // some references could not been found //try to sort by subject KNRemoteArticle *oldest; KNRemoteArticle::List list; for(idx=start; idx<end; idx++) { art=at(idx); if(art->idRef()==-1) { //for all not sorted headers list.clear(); list.append(art); //find all headers with same subject for(int idx2=0; idx2<length(); idx2++) if(at(idx2)==art) continue; else if(at(idx2)->subject()==art->subject()) list.append(at(idx2)); if(list.count()==1) { art->setIdRef(0); art->setThreadingLevel(6); bySubCnt++; } else { //find oldest oldest=list.first(); for ( KNRemoteArticle::List::Iterator it = list.begin(); it != list.end(); ++it ) if ( (*it)->date()->unixTime() < oldest->date()->unixTime() ) oldest = (*it); //oldest gets idRef 0 if(oldest->idRef()==-1) bySubCnt++; oldest->setIdRef(0); oldest->setThreadingLevel(6); for ( KNRemoteArticle::List::Iterator it = list.begin(); it != list.end(); ++it ) { if ( (*it) == oldest ) continue; if ( (*it)->idRef() == -1 || ( (*it)->idRef() != -1 && (*it)->threadingLevel() == 6 ) ) { (*it)->setIdRef(oldest->id()); (*it)->setThreadingLevel(6); if ( (*it)->id() >= at(start)->id() ) bySubCnt++; } } } } if (timer.elapsed() > 200) { // don't flicker timer.restart(); if (client) client->updatePercentage(30+((bySubCnt+foundCnt)*70)/cnt); } } } //all not found items get refID 0 for (int idx=start; idx<end; idx++){ art=at(idx); if(art->idRef()==-1) { art->setIdRef(0); art->setThreadingLevel(6); //was 0 !!! } } //check for loops in threads int startId; bool isLoop; int iterationCount; for (int idx=start; idx<end; idx++){ art=at(idx); startId=art->id(); isLoop=false; iterationCount=0; while(art->idRef()!=0 && !isLoop && (iterationCount < end)) { art=byId(art->idRef()); isLoop=(art->id()==startId); iterationCount++; } if(isLoop) { // this method is called from the nntp-thread!!! #ifndef NDEBUG tqDebug("knode: Sorting : loop in %d",startId); #endif art=at(idx); art->setIdRef(0); art->setThreadingLevel(0); } } // propagate ignored/watched flags to new headers for(int idx=start; idx<end; idx++) { art=at(idx); int idRef=art->idRef(); int tmpIdRef; if(idRef!=0) { while(idRef!=0) { art=byId(idRef); tmpIdRef=art->idRef(); idRef = (idRef!=tmpIdRef)? tmpIdRef : 0; } if (art) { if (art->isIgnored()) { at(idx)->setIgnored(true); ++i_gnoreCount; } at(idx)->setWatched(art->isWatched()); } } } // this method is called from the nntp-thread!!! #ifndef NDEBUG tqDebug("knode: Sorting : %d headers resorted", resortCnt); tqDebug("knode: Sorting : %d references of %d found", foundCnt, refCnt); tqDebug("knode: Sorting : %d references of %d sorted by subject", bySubCnt, refCnt); #endif } KNRemoteArticle* KNGroup::findReference(KNRemoteArticle *a) { int found=false; TQCString ref_mid; int ref_nr=0; KNRemoteArticle *ref_art=0; ref_mid=a->references()->first(); while(!found && !ref_mid.isNull() && ref_nr < SORT_DEPTH) { ref_art=byMessageId(ref_mid); if(ref_art) { found=true; a->setThreadingLevel(ref_nr+1); a->setIdRef(ref_art->id()); } ref_nr++; ref_mid=a->references()->next(); } return ref_art; } void KNGroup::scoreArticles(bool onlynew) { kdDebug(5003) << "KNGroup::scoreArticles()" << endl; int len=length(), todo=(onlynew)? lastFetchCount():length(); if (todo) { // reset the notify collection delete KNScorableArticle::notifyC; KNScorableArticle::notifyC = 0; kdDebug(5003) << "scoring " << newCount() << " articles" << endl; kdDebug(5003) << "(total " << length() << " article in group)" << endl; knGlobals.top->setCursorBusy(true); knGlobals.setStatusMsg(i18n(" Scoring...")); int defScore; KScoringManager *sm = knGlobals.scoringManager(); sm->initCache(groupname()); for(int idx=0; idx<todo; idx++) { KNRemoteArticle *a = at(len-idx-1); if ( !a ) { kdWarning( 5003 ) << "found no article at " << len-idx-1 << endl; continue; } defScore = 0; if (a->isIgnored()) defScore = knGlobals.configManager()->scoring()->ignoredThreshold(); else if (a->isWatched()) defScore = knGlobals.configManager()->scoring()->watchedThreshold(); if (a->score() != defScore) { a->setScore(defScore); a->setChanged(true); } bool read = a->isRead(); KNScorableArticle sa(a); sm->applyRules(sa); if ( a->isRead() != read && !read ) incReadCount(); } knGlobals.setStatusMsg(TQString()); knGlobals.top->setCursorBusy(false); //kdDebug(5003) << KNScorableArticle::notifyC->collection() << endl; if (KNScorableArticle::notifyC) KNScorableArticle::notifyC->displayCollection(knGlobals.topWidget); } } void KNGroup::reorganize() { kdDebug(5003) << "KNGroup::reorganize()" << endl; knGlobals.top->setCursorBusy(true); knGlobals.setStatusMsg(i18n(" Reorganizing headers...")); for(int idx=0; idx<length(); idx++) { KNRemoteArticle *a = at(idx); Q_ASSERT( a ); a->setId(idx+1); //new ids a->setIdRef(-1); a->setThreadingLevel(0); } buildThreads(length()); saveStaticData(length(), true); saveDynamicData(length(), true); knGlobals.top->headerView()->repaint(); knGlobals.setStatusMsg(TQString()); knGlobals.top->setCursorBusy(false); } void KNGroup::updateThreadInfo() { KNRemoteArticle *ref; bool brokenThread=false; for(int idx=0; idx<length(); idx++) { at(idx)->setUnreadFollowUps(0); at(idx)->setNewFollowUps(0); } for(int idx=0; idx<length(); idx++) { int idRef=at(idx)->idRef(); int tmpIdRef; int iterCount=1; // control iteration count to avoid infinite loops while((idRef!=0) && (iterCount <= length())) { ref=byId(idRef); if(!ref) { brokenThread=true; break; } if(!at(idx)->isRead()) { ref->incUnreadFollowUps(); if(at(idx)->isNew()) ref->incNewFollowUps(); } tmpIdRef=ref->idRef(); idRef= (idRef!=tmpIdRef) ? ref->idRef() : 0; iterCount++; } if(iterCount > length()) brokenThread=true; if(brokenThread) break; } if(brokenThread) { kdWarning(5003) << "KNGroup::updateThreadInfo() : Found broken threading infos! Restoring ..." << endl; reorganize(); updateThreadInfo(); } } void KNGroup::showProperties() { if(!i_dentity) i_dentity=new KNConfig::Identity(false); KNGroupPropDlg *d=new KNGroupPropDlg(this, knGlobals.topWidget); if(d->exec()) if(d->nickHasChanged()) l_istItem->setText(0, name()); if(i_dentity->isEmpty()) { delete i_dentity; i_dentity=0; } delete d; } int KNGroup::statThrWithNew() { int cnt=0; for(int i=0; i<length(); i++) if( (at(i)->idRef()==0) && (at(i)->hasNewFollowUps()) ) cnt++; return cnt; } int KNGroup::statThrWithUnread() { int cnt=0; for(int i=0; i<length(); i++) if( (at(i)->idRef()==0) && (at(i)->hasUnreadFollowUps()) ) cnt++; return cnt; } TQString KNGroup::prepareForExecution() { if (knGlobals.groupManager()->loadHeaders(this)) return TQString(); else return i18n("Cannot load saved headers: %1").arg(groupname()); } //*************************************************************************** void KNGroup::dynDataVer0::setData(KNRemoteArticle *a) { id=a->id(); idRef=a->idRef(); thrLevel=a->threadingLevel(); read=a->getReadFlag(); score=a->score(); } void KNGroup::dynDataVer0::getData(KNRemoteArticle *a) { a->setId(id); a->setIdRef(idRef); a->setRead(read); a->setThreadingLevel(thrLevel); a->setScore(score); } void KNGroup::dynDataVer1::setData(KNRemoteArticle *a) { id=a->id(); idRef=a->idRef(); thrLevel=a->threadingLevel(); read=a->getReadFlag(); score=a->score(); ignoredWatched = 0; if (a->isWatched()) ignoredWatched = 1; else if (a->isIgnored()) ignoredWatched = 2; } void KNGroup::dynDataVer1::getData(KNRemoteArticle *a) { a->setId(id); a->setIdRef(idRef); a->setRead(read); a->setThreadingLevel(thrLevel); a->setScore(score); a->setWatched(ignoredWatched==1); a->setIgnored(ignoredWatched==2); } KNConfig::Cleanup * KNGroup::activeCleanupConfig() { if (!cleanupConfig()->useDefault()) return cleanupConfig(); return account()->activeCleanupConfig(); }