summaryrefslogtreecommitdiffstats
path: root/knode/kngroup.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'knode/kngroup.cpp')
-rw-r--r--knode/kngroup.cpp1112
1 files changed, 1112 insertions, 0 deletions
diff --git a/knode/kngroup.cpp b/knode/kngroup.cpp
new file mode 100644
index 000000000..1c8bdfbf1
--- /dev/null
+++ b/knode/kngroup.cpp
@@ -0,0 +1,1112 @@
+/*
+ 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 <klocale.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;
+}
+
+
+QString KNGroup::path()
+{
+ return p_arent->path();
+}
+
+
+const QString& KNGroup::name()
+{
+ static QString 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 QString &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();
+ QString 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 QString &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()
+{
+ QString 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", QString::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;
+ QCString buff, hdrValue;
+ KNFile f;
+ KQCStringSplitter split;
+ int cnt=0, id, lines, fileFormatVersion, artNumber;
+ unsigned int timeT;
+ KNRemoteArticle *art;
+
+ QString 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(':');
+ QCString 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(QStrList *hdrs, QStrList *hdrfmt, KNProtocolClient *client)
+{
+ KNRemoteArticle *art=0, *art2=0;
+ QCString data, hdr, hdrName;
+ KQCStringSplitter split;
+ split.setIncludeSep(false);
+ int new_cnt=0, added_cnt=0, todo=hdrs->count();
+ QTime 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 QCString::copy() ?
+
+ // Bytes
+ split.next();
+
+ //Lines
+ split.next();
+ art->lines()->setNumberOfLines(split.string().toInt());
+
+ // optinal additional headers
+ mOptionalHeaders = *hdrfmt;
+ for (hdr = hdrfmt->first(); hdr; 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;
+
+ QString dir(path());
+ if (dir.isNull())
+ return 0;
+
+ QFile f(dir+g_roupname+".static");
+
+ if(ovr) mode=IO_WriteOnly;
+ else mode=IO_WriteOnly | IO_Append;
+
+ if(f.open(mode)) {
+
+ QTextStream ts(&f);
+ ts.setEncoding(QTextStream::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 (QCString hdrName = mOptionalHeaders.first(); hdrName; 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) {
+ QString dir(path());
+ if (dir.isNull())
+ return;
+
+ QFile 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) {
+
+ QString dir(path());
+ if (dir.isNull())
+ return;
+
+ QFile 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 QString &id)
+{
+ c_rosspostIDBuffer.append(id);
+}
+
+
+void KNGroup::processXPostBuffer(bool deleteAfterwards)
+{
+ QStringList remainder;
+ KNRemoteArticle *xp;
+ KNRemoteArticle::List al;
+
+ for (QStringList::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;
+ QTime timer;
+
+ timer.start();
+
+ // this method is called from the nntp-thread!!!
+#ifndef NDEBUG
+ qDebug("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
+ qDebug("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
+ qDebug("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
+ qDebug("knode: Sorting : %d headers resorted", resortCnt);
+ qDebug("knode: Sorting : %d references of %d found", foundCnt, refCnt);
+ qDebug("knode: Sorting : %d references of %d sorted by subject", bySubCnt, refCnt);
+#endif
+}
+
+
+KNRemoteArticle* KNGroup::findReference(KNRemoteArticle *a)
+{
+ int found=false;
+ QCString 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(QString::null);
+ 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(QString::null);
+ 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;
+}
+
+QString KNGroup::prepareForExecution()
+{
+ if (knGlobals.groupManager()->loadHeaders(this))
+ return QString::null;
+ 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();
+}