/* 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 #include #include #include #include #include #include #include #include "articlewidget.h" #include "knmainwidget.h" #include "knarticlemanager.h" #include "kngroupdialog.h" #include "knnntpaccount.h" #include "knprotocolclient.h" #include "kncleanup.h" #include "knnetaccess.h" #include "knglobals.h" #include "knconfigmanager.h" #include "resource.h" #include "utilities.h" #include "knarticlewindow.h" #include "knmemorymanager.h" using namespace KNode; //================================================================================= // helper classes for the group selection dialog (getting the server's grouplist, // getting recently created groups) KNGroupInfo::KNGroupInfo() { } KNGroupInfo::KNGroupInfo(const TQString &n_ame, const TQString &d_escription, bool n_ewGroup, bool s_ubscribed, KNGroup::tqStatus s_tatus) : name(n_ame), description(d_escription), newGroup(n_ewGroup), subscribed(s_ubscribed), status(s_tatus) { } KNGroupInfo::~KNGroupInfo() { } bool KNGroupInfo::operator== (const KNGroupInfo &gi2) { return (name == gi2.name); } bool KNGroupInfo::operator< (const KNGroupInfo &gi2) { return (name < gi2.name); } //=============================================================================== KNGroupListData::KNGroupListData() : codecForDescriptions(0) { groups = new TQSortedList; groups->setAutoDelete(true); } KNGroupListData::~KNGroupListData() { delete groups; } bool KNGroupListData::readIn(KNProtocolClient *client) { KNFile f(path+"groups"); TQCString line; int sepPos1,sepPos2; TQString name,description; bool sub; KNGroup::tqStatus status=KNGroup::unknown; TQTime timer; uint size=f.size()+2; timer.start(); if (client) client->updatePercentage(0); if(f.open(IO_ReadOnly)) { while(!f.atEnd()) { line = f.readLine(); sepPos1 = line.tqfind(' '); if (sepPos1==-1) { // no description name = TQString::fromUtf8(line); description = TQString(); status = KNGroup::unknown; } else { name = TQString::fromUtf8(line.left(sepPos1)); sepPos2 = line.tqfind(' ',sepPos1+1); if (sepPos2==-1) { // no status description = TQString::fromUtf8(line.right(line.length()-sepPos1-1)); status = KNGroup::unknown; } else { description = TQString::fromUtf8(line.right(line.length()-sepPos2-1)); switch (line[sepPos1+1]) { case 'u': status = KNGroup::unknown; break; case 'n': status = KNGroup::readOnly; break; case 'y': status = KNGroup::postingAllowed; break; case 'm': status = KNGroup::moderated; break; } } } if (subscribed.tqcontains(name)) { subscribed.remove(name); // group names are unique, we wont find it again anyway... sub = true; } else sub = false; groups->append(new KNGroupInfo(name,description,false,sub,status)); if (timer.elapsed() > 200) { // don't flicker timer.restart(); if (client) client->updatePercentage((f.at()*100)/size); } } f.close(); return true; } else { kdWarning(5003) << "unable to open " << f.name() << " reason " << f.status() << endl; return false; } } bool KNGroupListData::writeOut() { TQFile f(path+"groups"); TQCString temp; if(f.open(IO_WriteOnly)) { for (KNGroupInfo *i=groups->first(); i; i=groups->next()) { temp = i->name.utf8(); switch (i->status) { case KNGroup::unknown: temp += " u "; break; case KNGroup::readOnly: temp += " n "; break; case KNGroup::postingAllowed: temp += " y "; break; case KNGroup::moderated: temp += " m "; break; } temp += i->description.utf8() + "\n"; f.writeBlock(temp.data(),temp.length()); } f.close(); return true; } else { kdWarning(5003) << "unable to open " << f.name() << " reason " << f.status() << endl; return false; } } // merge in new groups, we want to preserve the "subscribed"-flag // of the loaded groups and the "new"-flag of the new groups. void KNGroupListData::merge(TQSortedList* newGroups) { bool subscribed; for (KNGroupInfo *i=newGroups->first(); i; i=newGroups->next()) { if (groups->tqfind(i)>=0) { subscribed = groups->current()->subscribed; groups->remove(); // avoid duplicates } else subscribed = false; groups->append(new KNGroupInfo(i->name,i->description,true,subscribed,i->status)); } groups->sort(); } TQSortedList* KNGroupListData::extractList() { TQSortedList* temp = groups; groups = 0; return temp; } //=============================================================================== KNGroupManager::KNGroupManager(TQObject * tqparent, const char * name) : TQObject(tqparent,name) { c_urrentGroup=0; a_rticleMgr = knGlobals.articleManager(); } KNGroupManager::~KNGroupManager() { for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) delete (*it); } void KNGroupManager::syncGroups() { for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { (*it)->syncDynamicData(); (*it)->saveInfo(); } } void KNGroupManager::loadGroups(KNNntpAccount *a) { KNGroup *group; TQString dir(a->path()); if (dir.isNull()) return; TQDir d(dir); TQStringList entries(d.entryList("*.grpinfo")); for(TQStringList::Iterator it=entries.begin(); it != entries.end(); ++it) { group=new KNGroup(a); if (group->readInfo(dir+(*it))) { mGroupList.append( group ); emit groupAdded(group); } else { delete group; kdError(5003) << "Unable to load " << (*it) << "!" << endl; } } } void KNGroupManager::getSubscribed(KNNntpAccount *a, TQStringList &l) { l.clear(); for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) if ( (*it)->account() == a ) l.append( (*it)->groupname() ); } TQValueList KNGroupManager::groupsOfAccount( KNNntpAccount *a ) { TQValueList ret; for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) if ( (*it)->account() == a ) ret.append( (*it) ); return ret; } bool KNGroupManager::loadHeaders(KNGroup *g) { if (!g) return false; if (g->isLoaded()) return true; // we want to delete old stuff first => reduce vm fragmentation knGlobals.memoryManager()->prepareLoad(g); if (g->loadHdrs()) { knGlobals.memoryManager()->updateCacheEntry( g ); return true; } return false; } bool KNGroupManager::unloadHeaders(KNGroup *g, bool force) { if(!g || g->isLocked()) return false; if(!g->isLoaded()) return true; if (!force && (c_urrentGroup == g)) return false; if (g->unloadHdrs(force)) knGlobals.memoryManager()->removeCacheEntry(g); else return false; return true; } KNGroup* KNGroupManager::group(const TQString &gName, const KNServerInfo *s) { for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) if ( (*it)->account() == s && (*it)->groupname() == gName ) return (*it); return 0; } KNGroup* KNGroupManager::firstGroupOfAccount(const KNServerInfo *s) { for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) if ( (*it)->account() == s ) return (*it); return 0; } void KNGroupManager::expireAll(KNCleanUp *cup) { for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { if( (*it)->isLocked() || (*it)->lockedArticles() > 0 ) continue; if ( !(*it)->activeCleanupConfig()->expireToday() ) continue; cup->appendCollection( *(it) ); } } void KNGroupManager::expireAll(KNNntpAccount *a) { KNCleanUp *cup = new KNCleanUp(); for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { if( (*it)->account() != a || (*it)->isLocked() || (*it)->lockedArticles() > 0 ) continue; KNArticleWindow::closeAllWindowsForCollection( (*it) ); cup->appendCollection( (*it) ); } cup->start(); for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { if( (*it)->account() != a || (*it)->isLocked() || (*it)->lockedArticles() > 0 ) continue; emit groupUpdated( (*it) ); if ( (*it) == c_urrentGroup ) { if ( loadHeaders( (*it) ) ) a_rticleMgr->showHdrs(); else a_rticleMgr->setGroup(0); } } delete cup; } void KNGroupManager::showGroupDialog(KNNntpAccount *a, TQWidget *tqparent) { KNGroupDialog* gDialog=new KNGroupDialog((tqparent!=0)? tqparent:knGlobals.topWidget, a); connect(gDialog, TQT_SIGNAL(loadList(KNNntpAccount*)), this, TQT_SLOT(slotLoadGroupList(KNNntpAccount*))); connect(gDialog, TQT_SIGNAL(fetchList(KNNntpAccount*)), this, TQT_SLOT(slotFetchGroupList(KNNntpAccount*))); connect(gDialog, TQT_SIGNAL(checkNew(KNNntpAccount*,TQDate)), this, TQT_SLOT(slotCheckForNewGroups(KNNntpAccount*,TQDate))); connect(this, TQT_SIGNAL(newListReady(KNGroupListData*)), gDialog, TQT_SLOT(slotReceiveList(KNGroupListData*))); if(gDialog->exec()) { KNGroup *g=0; TQStringList lst; gDialog->toUnsubscribe(&lst); if (lst.count()>0) { if (KMessageBox::Yes == KMessageBox::questionYesNoList((tqparent!=0)? tqparent:knGlobals.topWidget,i18n("Do you really want to unsubscribe\nfrom these groups?"), lst, TQString(), i18n("Unsubscribe"), KStdGuiItem::cancel())) { for ( TQStringList::Iterator it = lst.begin(); it != lst.end(); ++it ) { if((g=group(*it, a))) unsubscribeGroup(g); } } } TQSortedList lst2; gDialog->toSubscribe(&lst2); for(KNGroupInfo *var=lst2.first(); var; var=lst2.next()) { subscribeGroup(var, a); } } delete gDialog; } void KNGroupManager::subscribeGroup(const KNGroupInfo *gi, KNNntpAccount *a) { KNGroup *grp; grp=new KNGroup(a); grp->setGroupname(gi->name); grp->setDescription(gi->description); grp->setqStatus(gi->status); grp->saveInfo(); mGroupList.append( grp ); emit groupAdded(grp); } bool KNGroupManager::unsubscribeGroup(KNGroup *g) { KNNntpAccount *acc; if(!g) g=c_urrentGroup; if(!g) return false; if((g->isLocked()) || (g->lockedArticles()>0)) { KMessageBox::sorry(knGlobals.topWidget, i18n("The group \"%1\" is being updated currently.\nIt is not possible to unsubscribe from it at the moment.").arg(g->groupname())); return false; } KNArticleWindow::closeAllWindowsForCollection(g); ArticleWidget::collectionRemoved( g ); acc=g->account(); TQDir dir(acc->path(),g->groupname()+"*"); if (dir.exists()) { if (unloadHeaders(g, true)) { if(c_urrentGroup==g) { setCurrentGroup(0); a_rticleMgr->updateStatusString(); } const TQFileInfoList *list = dir.entryInfoList(); // get list of matching files and delete all if (list) { TQFileInfoListIterator it( *list ); while (it.current()) { if (it.current()->fileName() == g->groupname()+".dynamic" || it.current()->fileName() == g->groupname()+".static" || it.current()->fileName() == g->groupname()+".grpinfo") dir.remove(it.current()->fileName()); ++it; } } kdDebug(5003) << "Files deleted!" << endl; emit groupRemoved(g); mGroupList.remove( g ); delete g; return true; } } return false; } void KNGroupManager::showGroupProperties(KNGroup *g) { if(!g) g=c_urrentGroup; if(!g) return; g->showProperties(); } void KNGroupManager::checkGroupForNewHeaders(KNGroup *g) { if(!g) g=c_urrentGroup; if(!g) return; if(g->isLocked()) { kdDebug(5003) << "KNGroupManager::checkGroupForNewHeaders() : group locked - returning" << endl; return; } g->setMaxFetch(knGlobals.configManager()->readNewsGeneral()->maxToFetch()); emitJob( new KNJobData(KNJobData::JTfetchNewHeaders, this, g->account(), g) ); } void KNGroupManager::expireGroupNow(KNGroup *g) { if(!g) return; if((g->isLocked()) || (g->lockedArticles()>0)) { KMessageBox::sorry(knGlobals.topWidget, i18n("This group cannot be expired because it is currently being updated.\n Please try again later.")); return; } KNArticleWindow::closeAllWindowsForCollection(g); KNCleanUp cup; cup.expireGroup(g, true); emit groupUpdated(g); if(g==c_urrentGroup) { if( loadHeaders(g) ) a_rticleMgr->showHdrs(); else a_rticleMgr->setGroup(0); } } void KNGroupManager::reorganizeGroup(KNGroup *g) { if(!g) g=c_urrentGroup; if(!g) return; g->reorganize(); if(g==c_urrentGroup) a_rticleMgr->showHdrs(); } void KNGroupManager::setCurrentGroup(KNGroup *g) { c_urrentGroup=g; a_rticleMgr->setGroup(g); kdDebug(5003) << "KNGroupManager::setCurrentGroup() : group changed" << endl; if(g) { if( !loadHeaders(g) ) { //KMessageBox::error(knGlobals.topWidget, i18n("Cannot load saved headers")); return; } a_rticleMgr->showHdrs(); if(knGlobals.configManager()->readNewsGeneral()->autoCheckGroups()) checkGroupForNewHeaders(g); } } void KNGroupManager::checkAll(KNNntpAccount *a, bool silent) { if(!a) return; for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { if ( (*it)->account() == a ) { (*it)->setMaxFetch( knGlobals.configManager()->readNewsGeneral()->maxToFetch() ); if ( silent ) emitJob( new KNJobData(KNJobData::JTsilentFetchNewHeaders, this, (*it)->account(), (*it) ) ); else emitJob( new KNJobData(KNJobData::JTfetchNewHeaders, this, (*it)->account(), (*it) ) ); } } } void KNGroupManager::processJob(KNJobData *j) { if((j->type()==KNJobData::JTLoadGroups)||(j->type()==KNJobData::JTFetchGroups)||(j->type()==KNJobData::JTCheckNewGroups)) { KNGroupListData *d=static_cast(j->data()); if (!j->canceled()) { if (j->success()) { if ((j->type()==KNJobData::JTFetchGroups)||(j->type()==KNJobData::JTCheckNewGroups)) { // update the descriptions of the subscribed groups for ( TQValueList::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) { if ( (*it)->account() == j->account() ) { for ( KNGroupInfo* inf = d->groups->first(); inf; inf = d->groups->next() ) if ( inf->name == (*it)->groupname() ) { (*it)->setDescription( inf->description ); (*it)->setqStatus( inf->status ); break; } } } } emit(newListReady(d)); } else { KMessageBox::error(knGlobals.topWidget, j->errorString()); emit(newListReady(0)); } } else emit(newListReady(0)); delete j; delete d; } else { //KNJobData::JTfetchNewHeaders or KNJobData::JTsilentFetchNewHeaders KNGroup *group=static_cast(j->data()); if (!j->canceled()) { if (j->success()) { if(group->lastFetchCount()>0) { group->scoreArticles(); group->processXPostBuffer(true); emit groupUpdated(group); group->saveInfo(); knGlobals.memoryManager()->updateCacheEntry(group); } } else { // ok, hack (?): // stop all other active fetch jobs, this prevents that // we show multiple error dialogs if a server is unavailable knGlobals.netAccess()->stopJobsNntp(KNJobData::JTfetchNewHeaders); knGlobals.netAccess()->stopJobsNntp(KNJobData::JTsilentFetchNewHeaders); if(!(j->type()==KNJobData::JTsilentFetchNewHeaders)) { KMessageBox::error(knGlobals.topWidget, j->errorString()); } } } if(group==c_urrentGroup) a_rticleMgr->showHdrs(false); delete j; } } // load group list from disk (if this fails: ask user if we should fetch the list) void KNGroupManager::slotLoadGroupList(KNNntpAccount *a) { KNGroupListData *d = new KNGroupListData(); d->path = a->path(); if(!TQFileInfo(d->path+"groups").exists()) { if (KMessageBox::Yes==KMessageBox::questionYesNo(knGlobals.topWidget,i18n("You do not have any groups for this account;\ndo you want to fetch a current list?"), TQString(), i18n("Fetch List"), i18n("Do Not Fetch"))) { delete d; slotFetchGroupList(a); return; } else { emit(newListReady(d)); delete d; return; } } getSubscribed(a,d->subscribed); d->getDescriptions = a->fetchDescriptions(); emitJob( new KNJobData(KNJobData::JTLoadGroups, this, a, d) ); } // fetch group list from server void KNGroupManager::slotFetchGroupList(KNNntpAccount *a) { KNGroupListData *d = new KNGroupListData(); d->path = a->path(); getSubscribed(a,d->subscribed); d->getDescriptions = a->fetchDescriptions(); d->codecForDescriptions=KGlobal::charsets()->codecForName(knGlobals.configManager()->postNewsTechnical()->charset()); emitJob( new KNJobData(KNJobData::JTFetchGroups, this, a, d) ); } // check for new groups (created after the given date) void KNGroupManager::slotCheckForNewGroups(KNNntpAccount *a, TQDate date) { KNGroupListData *d = new KNGroupListData(); d->path = a->path(); getSubscribed(a,d->subscribed); d->getDescriptions = a->fetchDescriptions(); d->fetchSince = date; d->codecForDescriptions=KGlobal::charsets()->codecForName(knGlobals.configManager()->postNewsTechnical()->charset()); emitJob( new KNJobData(KNJobData::JTCheckNewGroups, this, a, d) ); } //-------------------------------- #include "kngroupmanager.moc" // kate: space-indent on; indent-width 2;