Fix typo found by Yuri Chornoivan
[kdepim.git] / knode / kngroupmanager.cpp
blobd6fab1d46dfc89277efcfa4a9f5961c78d1eeee8
1 /*
2 KNode, the KDE newsreader
3 Copyright (c) 1999-2006 the KNode authors.
4 See file AUTHORS for details
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 You should have received a copy of the GNU General Public License
11 along with this program; if not, write to the Free Software Foundation,
12 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US
15 #include "kngroupmanager.h"
17 #include "articlewidget.h"
18 #include "knmainwidget.h"
19 #include "knarticlemanager.h"
20 #include "kngroupdialog.h"
21 #include "knnntpaccount.h"
22 #include "kncleanup.h"
23 #include "scheduler.h"
24 #include "knglobals.h"
25 #include "knconfigmanager.h"
26 #include "nntpjobs.h"
27 #include "resource.h"
28 #include "knarticlewindow.h"
29 #include "knmemorymanager.h"
30 #include "settings.h"
31 #include "utils/locale.h"
34 #include <QByteArray>
35 #include <QDir>
36 #include <QFile>
38 #include <klocale.h>
39 #include <kmessagebox.h>
40 #include <kiconloader.h>
41 #include <kdebug.h>
42 #include <kcharsets.h>
45 using namespace KNode;
46 using namespace KNode::Utilities;
49 //=================================================================================
51 // helper classes for the group selection dialog (getting the server's grouplist,
52 // getting recently created groups)
55 KNGroupInfo::KNGroupInfo()
60 KNGroupInfo::KNGroupInfo(const QString &n_ame, const QString &d_escription, bool n_ewGroup, bool s_ubscribed, KNGroup::Status s_tatus)
61 : name(n_ame), description(d_escription), newGroup(n_ewGroup), subscribed(s_ubscribed),
62 status(s_tatus)
67 KNGroupInfo::~KNGroupInfo()
72 bool KNGroupInfo::operator== (const KNGroupInfo &gi2) const
74 return (name == gi2.name);
78 bool KNGroupInfo::operator< (const KNGroupInfo &gi2) const
80 return (name < gi2.name);
84 //===============================================================================
87 KNGroupListData::KNGroupListData()
88 : codecForDescriptions(0)
90 groups = new QList<KNGroupInfo>;
95 KNGroupListData::~KNGroupListData()
97 delete groups;
102 bool KNGroupListData::readIn(KNJobData *job)
104 QFile f( path + "groups" );
105 QByteArray line;
106 int sepPos1,sepPos2;
107 QString name,description;
108 bool sub;
109 KNGroup::Status status=KNGroup::unknown;
110 QTime timer;
111 uint size=f.size()+2;
113 timer.start();
114 if(job) {
115 job->setProgress(0);
118 if(f.open(QIODevice::ReadOnly)) {
119 while(!f.atEnd()) {
120 line = f.readLine();
121 sepPos1 = line.indexOf( ' ' );
123 if (sepPos1==-1) { // no description
124 name = QString::fromUtf8(line);
125 description.clear();
126 status = KNGroup::unknown;
127 } else {
128 name = QString::fromUtf8(line.left(sepPos1));
130 sepPos2 = line.indexOf( ' ', sepPos1 + 1 );
131 if (sepPos2==-1) { // no status
132 description = QString::fromUtf8(line.right(line.length()-sepPos1-1));
133 status = KNGroup::unknown;
134 } else {
135 description = QString::fromUtf8( line.right( line.length() - sepPos2 - 1 ).trimmed() );
136 switch (line[sepPos1+1]) {
137 case 'u': status = KNGroup::unknown;
138 break;
139 case 'n': status = KNGroup::readOnly;
140 break;
141 case 'y': status = KNGroup::postingAllowed;
142 break;
143 case 'm': status = KNGroup::moderated;
144 break;
149 if (subscribed.contains(name)) {
150 subscribed.removeAll( name ); // group names are unique, we wont find it again anyway...
151 sub = true;
152 } else
153 sub = false;
155 groups->append(KNGroupInfo(name,description,false,sub,status));
157 if (timer.elapsed() > 200) { // don't flicker
158 timer.restart();
159 if(job) {
160 job->setProgress( (f.pos()*100)/size );
165 f.close();
166 return true;
167 } else {
168 kWarning(5003) <<"unable to open" << f.fileName() <<" reason" << f.error();
169 return false;
175 bool KNGroupListData::writeOut()
177 QFile f(path+"groups");
178 QByteArray temp;
180 if(f.open(QIODevice::WriteOnly)) {
181 Q_FOREACH(const KNGroupInfo& i, *groups) {
182 temp = i.name.toUtf8();
183 switch (i.status) {
184 case KNGroup::unknown: temp += " u ";
185 break;
186 case KNGroup::readOnly: temp += " n ";
187 break;
188 case KNGroup::postingAllowed: temp += " y ";
189 break;
190 case KNGroup::moderated: temp += " m ";
191 break;
193 temp += i.description.toUtf8() + '\n';
194 f.write(temp.data(),temp.length());
196 f.close();
197 return true;
198 } else {
199 kWarning(5003) <<"unable to open" << f.fileName() <<" reason" << f.error();
200 return false;
206 // merge in new groups, we want to preserve the "subscribed"-flag
207 // of the loaded groups and the "new"-flag of the new groups.
208 void KNGroupListData::merge(QList<KNGroupInfo>* newGroups)
210 bool subscribed;
212 Q_FOREACH(const KNGroupInfo& i, *newGroups) {
213 int current;
214 if ( (current=groups->indexOf(i)) != -1) {
215 subscribed = groups->at(current).subscribed;
216 groups->removeAt(current); // avoid duplicates
217 } else
218 subscribed = false;
219 groups->append(KNGroupInfo(i.name,i.description,true,subscribed,i.status));
224 QList<KNGroupInfo>* KNGroupListData::extractList()
226 QList<KNGroupInfo>* temp = groups;
227 groups = 0;
228 return temp;
232 //===============================================================================
235 KNGroupManager::KNGroupManager( QObject * parent )
236 : QObject( parent )
238 c_urrentGroup=0;
239 a_rticleMgr = knGlobals.articleManager();
243 KNGroupManager::~KNGroupManager()
245 qDeleteAll( mGroupList );
249 void KNGroupManager::syncGroups()
251 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
252 (*it)->syncDynamicData();
253 (*it)->saveInfo();
258 void KNGroupManager::loadGroups(KNNntpAccount *a)
260 KNGroup *group;
262 QString dir(a->path());
263 if (dir.isNull())
264 return;
265 QDir d(dir);
267 QStringList entries(d.entryList(QStringList("*.grpinfo")));
268 for(QStringList::Iterator it=entries.begin(); it != entries.end(); ++it) {
269 group=new KNGroup(a);
270 if (group->readInfo(dir+(*it))) {
271 mGroupList.append( group );
272 emit groupAdded(group);
273 } else {
274 delete group;
275 kError(5003) <<"Unable to load" << (*it) <<"!";
281 void KNGroupManager::getSubscribed(KNNntpAccount *a, QStringList &l)
283 l.clear();
284 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it )
285 if ( (*it)->account() == a )
286 l.append( (*it)->groupname() );
290 KNGroup::List KNGroupManager::groupsOfAccount( KNNntpAccount *a )
292 KNGroup::List ret;
293 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it )
294 if ( (*it)->account() == a )
295 ret.append( (*it) );
296 return ret;
300 bool KNGroupManager::loadHeaders(KNGroup *g)
302 if (!g)
303 return false;
305 if (g->isLoaded())
306 return true;
308 // we want to delete old stuff first => reduce vm fragmentation
309 knGlobals.memoryManager()->prepareLoad(g);
311 if (g->loadHdrs()) {
312 knGlobals.memoryManager()->updateCacheEntry( g );
313 return true;
316 return false;
320 bool KNGroupManager::unloadHeaders(KNGroup *g, bool force)
322 if(!g || g->isLocked())
323 return false;
325 if(!g->isLoaded())
326 return true;
328 if (!force && (c_urrentGroup == g))
329 return false;
331 if (g->unloadHdrs(force))
332 knGlobals.memoryManager()->removeCacheEntry(g);
333 else
334 return false;
336 return true;
340 KNGroup* KNGroupManager::group(const QString &gName, const KNServerInfo *s)
342 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it )
343 if ( (*it)->account() == s && (*it)->groupname() == gName )
344 return (*it);
346 return 0;
350 KNGroup* KNGroupManager::firstGroupOfAccount(const KNServerInfo *s)
352 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it )
353 if ( (*it)->account() == s )
354 return (*it);
356 return 0;
360 void KNGroupManager::expireAll(KNCleanUp *cup)
362 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
363 if( (*it)->isLocked() || (*it)->lockedArticles() > 0 )
364 continue;
365 if ( !(*it)->activeCleanupConfig()->expireToday() )
366 continue;
367 cup->appendCollection( *(it) );
372 void KNGroupManager::expireAll(KNNntpAccount *a)
374 KNCleanUp *cup = new KNCleanUp();
376 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
377 if( (*it)->account() != a || (*it)->isLocked() || (*it)->lockedArticles() > 0 )
378 continue;
380 ArticleWindow::closeAllWindowsForCollection( (*it) );
381 cup->appendCollection( (*it) );
384 cup->start();
386 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
387 if( (*it)->account() != a || (*it)->isLocked() || (*it)->lockedArticles() > 0 )
388 continue;
390 emit groupUpdated( (*it) );
391 if ( (*it) == c_urrentGroup ) {
392 if ( loadHeaders( (*it) ) )
393 a_rticleMgr->showHdrs();
394 else
395 a_rticleMgr->setGroup(0);
399 delete cup;
403 void KNGroupManager::showGroupDialog(KNNntpAccount *a, QWidget *parent)
405 KNGroupDialog* gDialog=new KNGroupDialog((parent!=0)? parent:knGlobals.topWidget, a);
407 connect(gDialog, SIGNAL(loadList(KNNntpAccount*)), this, SLOT(slotLoadGroupList(KNNntpAccount*)));
408 connect(gDialog, SIGNAL(fetchList(KNNntpAccount*)), this, SLOT(slotFetchGroupList(KNNntpAccount*)));
409 connect(gDialog, SIGNAL(checkNew(KNNntpAccount*,QDate)), this, SLOT(slotCheckForNewGroups(KNNntpAccount*,QDate)));
410 connect(this, SIGNAL(newListReady(KNGroupListData*)), gDialog, SLOT(slotReceiveList(KNGroupListData*)));
412 QWidget *oldTopWidget = knGlobals.topWidget;
413 // if the list of groups is empty, the parent of the message box
414 // asking whether to fetch will be "knGlobals.topWidget"
415 knGlobals.topWidget = gDialog;
416 int accept = gDialog->exec();
417 knGlobals.topWidget = oldTopWidget;
418 if(accept) {
419 KNGroup *g=0;
421 QStringList lst;
422 gDialog->toUnsubscribe(&lst);
423 if (lst.count()>0) {
424 if (KMessageBox::Yes == KMessageBox::questionYesNoList((parent!=0)? parent:knGlobals.topWidget,i18n("Do you really want to unsubscribe\nfrom these groups?"),
425 lst, QString(), KGuiItem(i18n("Unsubscribe")), KStandardGuiItem::cancel())) {
426 for ( QStringList::Iterator it = lst.begin(); it != lst.end(); ++it ) {
427 if((g=group(*it, a)))
428 unsubscribeGroup(g);
433 QList<KNGroupInfo> lst2;
434 gDialog->toSubscribe(&lst2);
435 Q_FOREACH( const KNGroupInfo& var, lst2) {
436 subscribeGroup(&var, a);
440 delete gDialog;
444 void KNGroupManager::subscribeGroup(const KNGroupInfo *gi, KNNntpAccount *a)
446 KNGroup *grp;
448 grp=new KNGroup(a);
449 grp->setGroupname(gi->name);
450 grp->setDescription(gi->description);
451 grp->setStatus(gi->status);
452 grp->saveInfo();
453 mGroupList.append( grp );
454 emit groupAdded(grp);
458 bool KNGroupManager::unsubscribeGroup(KNGroup *g)
460 KNNntpAccount *acc;
461 if(!g) g=c_urrentGroup;
462 if(!g) return false;
464 if((g->isLocked()) || (g->lockedArticles()>0)) {
465 KMessageBox::sorry(knGlobals.topWidget, i18n("The group \"%1\" is being updated currently.\nIt is not possible to unsubscribe from it at the moment.", g->groupname()));
466 return false;
469 ArticleWindow::closeAllWindowsForCollection( g );
470 ArticleWidget::collectionRemoved( g );
472 acc=g->account();
474 QDir dir( acc->path(), g->groupname() + '*' );
475 if (dir.exists()) {
476 if (unloadHeaders(g, true)) {
477 if(c_urrentGroup==g) {
478 setCurrentGroup(0);
479 a_rticleMgr->updateStatusString();
482 QFileInfoList list = dir.entryInfoList(); // get list of matching files and delete all
483 Q_FOREACH( const QFileInfo &it, list ) {
484 if ( it.fileName() == g->groupname()+".dynamic" ||
485 it.fileName() == g->groupname()+".static" ||
486 it.fileName() == g->groupname()+".grpinfo" )
487 dir.remove( it.fileName() );
489 kDebug(5003) <<"Files deleted!";
491 emit groupRemoved(g);
492 mGroupList.removeAll( g );
493 delete g;
495 return true;
499 return false;
503 void KNGroupManager::showGroupProperties(KNGroup *g)
505 if(!g) g=c_urrentGroup;
506 if(!g) return;
507 g->showProperties();
511 void KNGroupManager::checkGroupForNewHeaders(KNGroup *g)
513 if(!g) g=c_urrentGroup;
514 if(!g) return;
515 if(g->isLocked()) {
516 kDebug(5003) <<"KNGroupManager::checkGroupForNewHeaders() : group locked - returning";
517 return;
520 g->setMaxFetch( knGlobals.settings()->maxToFetch() );
521 emitJob( new ArticleListJob( this, g->account(), g ) );
525 void KNGroupManager::expireGroupNow(KNGroup *g)
527 if(!g) return;
529 if((g->isLocked()) || (g->lockedArticles()>0)) {
530 KMessageBox::sorry(knGlobals.topWidget,
531 i18n("This group cannot be expired because it is currently being updated.\n Please try again later."));
532 return;
535 ArticleWindow::closeAllWindowsForCollection( g );
537 KNCleanUp cup;
538 cup.expireGroup(g, true);
540 emit groupUpdated(g);
541 if(g==c_urrentGroup) {
542 if( loadHeaders(g) )
543 a_rticleMgr->showHdrs();
544 else
545 a_rticleMgr->setGroup(0);
550 void KNGroupManager::reorganizeGroup(KNGroup *g)
552 if(!g) g=c_urrentGroup;
553 if(!g) return;
554 g->reorganize();
555 if(g==c_urrentGroup)
556 a_rticleMgr->showHdrs();
560 void KNGroupManager::setCurrentGroup(KNGroup *g)
562 c_urrentGroup=g;
563 a_rticleMgr->setGroup(g);
564 kDebug(5003) <<"KNGroupManager::setCurrentGroup() : group changed";
566 if(g) {
567 if( !loadHeaders(g) ) {
568 //KMessageBox::error(knGlobals.topWidget, i18n("Cannot load saved headers"));
569 return;
571 a_rticleMgr->showHdrs();
572 if ( knGlobals.settings()->autoCheckGroups() )
573 checkGroupForNewHeaders(g);
578 void KNGroupManager::checkAll(KNNntpAccount *a, bool silent)
580 if(!a) return;
582 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
583 if ( (*it)->account() == a ) {
584 (*it)->setMaxFetch( knGlobals.settings()->maxToFetch() );
585 if ( silent )
586 emitJob( new ArticleListJob( this, (*it)->account(), (*it), true ) );
587 else
588 emitJob( new ArticleListJob( this, (*it)->account(), (*it) ) );
594 void KNGroupManager::processJob(KNJobData *j)
596 if ( j->type()==KNJobData::JTLoadGroups || j->type()==KNJobData::JTFetchGroups ) {
597 KNGroupListData *d=static_cast<KNGroupListData*>(j->data());
599 if (!j->canceled()) {
600 if (j->success()) {
601 if ( j->type() == KNJobData::JTFetchGroups ) {
602 // update the descriptions of the subscribed groups
603 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
604 if ( (*it)->account() == j->account() ) {
605 Q_FOREACH( const KNGroupInfo& inf, *d->groups )
606 if ( inf.name == (*it)->groupname() ) {
607 (*it)->setDescription( inf.description );
608 (*it)->setStatus( inf.status );
609 break;
614 emit(newListReady(d));
615 } else {
616 KMessageBox::error(knGlobals.topWidget, j->errorString());
617 emit(newListReady(0));
619 } else
620 emit(newListReady(0));
622 delete j;
623 delete d;
626 } else { //KNJobData::JTfetchNewHeaders
627 KNGroup *group=static_cast<KNGroup*>(j->data());
629 if (!j->canceled()) {
630 if (j->success()) {
631 if(group->lastFetchCount()>0) {
632 group->scoreArticles();
633 group->processXPostBuffer(true);
634 emit groupUpdated(group);
635 group->saveInfo();
636 knGlobals.memoryManager()->updateCacheEntry(group);
638 } else {
639 // ok, hack (?):
640 // stop all other active fetch jobs, this prevents that
641 // we show multiple error dialogs if a server is unavailable
642 knGlobals.scheduler()->cancelJobs( KNJobData::JTfetchNewHeaders );
643 ArticleListJob *lj = static_cast<ArticleListJob*>( j );
644 if ( !lj->silent() ) {
645 KMessageBox::error(knGlobals.topWidget, j->errorString());
649 if(group==c_urrentGroup)
650 a_rticleMgr->showHdrs(false);
652 delete j;
657 // load group list from disk (if this fails: ask user if we should fetch the list)
658 void KNGroupManager::slotLoadGroupList(KNNntpAccount *a)
660 KNGroupListData *d = new KNGroupListData();
661 d->path = a->path();
663 if(!QFileInfo(d->path+"groups").exists()) {
664 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?"), QString(), KGuiItem(i18n("Fetch List")), KGuiItem(i18n("Do Not Fetch")))) {
665 delete d;
666 slotFetchGroupList(a);
667 return;
668 } else {
669 emit(newListReady(d));
670 delete d;
671 return;
675 getSubscribed(a,d->subscribed);
676 d->getDescriptions = a->fetchDescriptions();
678 emitJob( new GroupLoadJob( this, a, d ) );
682 // fetch group list from server
683 void KNGroupManager::slotFetchGroupList(KNNntpAccount *a)
685 KNGroupListData *d = new KNGroupListData();
686 d->path = a->path();
687 getSubscribed(a,d->subscribed);
688 d->getDescriptions = a->fetchDescriptions();
689 d->codecForDescriptions = KGlobal::charsets()->codecForName( Locale::defaultCharset() );
691 emitJob( new GroupListJob( this, a, d ) );
695 // check for new groups (created after the given date)
696 void KNGroupManager::slotCheckForNewGroups(KNNntpAccount *a, QDate date)
698 KNGroupListData *d = new KNGroupListData();
699 d->path = a->path();
700 getSubscribed(a,d->subscribed);
701 d->getDescriptions = a->fetchDescriptions();
702 d->fetchSince = date;
703 d->codecForDescriptions = KGlobal::charsets()->codecForName( Locale::defaultCharset() );
705 emitJob( new GroupListJob( this, a, d, true ) );
709 //--------------------------------
711 #include "kngroupmanager.moc"