french -> French
[kdepim.git] / knode / kngroupmanager.cpp
blobd1847ac94d3d0973c56551975e15692b6f4384f8
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>
43 #include "knaccountmanager.h"
46 using namespace KNode;
47 using namespace KNode::Utilities;
50 //=================================================================================
52 // helper classes for the group selection dialog (getting the server's grouplist,
53 // getting recently created groups)
56 KNGroupInfo::KNGroupInfo()
61 KNGroupInfo::KNGroupInfo(const QString &n_ame, const QString &d_escription, bool n_ewGroup, bool s_ubscribed, KNGroup::Status s_tatus)
62 : name(n_ame), description(d_escription), newGroup(n_ewGroup), subscribed(s_ubscribed),
63 status(s_tatus)
68 KNGroupInfo::~KNGroupInfo()
73 bool KNGroupInfo::operator== (const KNGroupInfo &gi2) const
75 return (name == gi2.name);
79 bool KNGroupInfo::operator< (const KNGroupInfo &gi2) const
81 return (name < gi2.name);
85 //===============================================================================
88 KNGroupListData::KNGroupListData()
89 : codecForDescriptions(0)
91 groups = new QList<KNGroupInfo>;
96 KNGroupListData::~KNGroupListData()
98 delete groups;
103 bool KNGroupListData::readIn(KNJobData *job)
105 QFile f( path + "groups" );
106 QByteArray line;
107 int sepPos1,sepPos2;
108 QString name,description;
109 bool sub;
110 KNGroup::Status status=KNGroup::unknown;
111 QTime timer;
112 uint size=f.size()+2;
114 timer.start();
115 if(job) {
116 job->setProgress(0);
119 if(f.open(QIODevice::ReadOnly)) {
120 while(!f.atEnd()) {
121 line = f.readLine();
122 sepPos1 = line.indexOf( ' ' );
124 if (sepPos1==-1) { // no description
125 name = QString::fromUtf8(line);
126 description.clear();
127 status = KNGroup::unknown;
128 } else {
129 name = QString::fromUtf8(line.left(sepPos1));
131 sepPos2 = line.indexOf( ' ', sepPos1 + 1 );
132 if (sepPos2==-1) { // no status
133 description = QString::fromUtf8(line.right(line.length()-sepPos1-1));
134 status = KNGroup::unknown;
135 } else {
136 description = QString::fromUtf8( line.right( line.length() - sepPos2 - 1 ).trimmed() );
137 switch (line[sepPos1+1]) {
138 case 'u': status = KNGroup::unknown;
139 break;
140 case 'n': status = KNGroup::readOnly;
141 break;
142 case 'y': status = KNGroup::postingAllowed;
143 break;
144 case 'm': status = KNGroup::moderated;
145 break;
150 if (subscribed.contains(name)) {
151 subscribed.removeAll( name ); // group names are unique, we wont find it again anyway...
152 sub = true;
153 } else
154 sub = false;
156 groups->append(KNGroupInfo(name,description,false,sub,status));
158 if (timer.elapsed() > 200) { // don't flicker
159 timer.restart();
160 if(job) {
161 job->setProgress( (f.pos()*100)/size );
166 f.close();
167 return true;
168 } else {
169 kWarning(5003) <<"unable to open" << f.fileName() <<" reason" << f.error();
170 return false;
176 bool KNGroupListData::writeOut()
178 QFile f(path+"groups");
179 QByteArray temp;
181 if(f.open(QIODevice::WriteOnly)) {
182 Q_FOREACH(const KNGroupInfo& i, *groups) {
183 temp = i.name.toUtf8();
184 switch (i.status) {
185 case KNGroup::unknown: temp += " u ";
186 break;
187 case KNGroup::readOnly: temp += " n ";
188 break;
189 case KNGroup::postingAllowed: temp += " y ";
190 break;
191 case KNGroup::moderated: temp += " m ";
192 break;
194 temp += i.description.toUtf8() + '\n';
195 f.write(temp.data(),temp.length());
197 f.close();
198 return true;
199 } else {
200 kWarning(5003) <<"unable to open" << f.fileName() <<" reason" << f.error();
201 return false;
207 // merge in new groups, we want to preserve the "subscribed"-flag
208 // of the loaded groups and the "new"-flag of the new groups.
209 void KNGroupListData::merge(QList<KNGroupInfo>* newGroups)
211 bool subscribed;
213 Q_FOREACH(const KNGroupInfo& i, *newGroups) {
214 int current;
215 if ( (current=groups->indexOf(i)) != -1) {
216 subscribed = groups->at(current).subscribed;
217 groups->removeAt(current); // avoid duplicates
218 } else
219 subscribed = false;
220 groups->append(KNGroupInfo(i.name,i.description,true,subscribed,i.status));
225 QList<KNGroupInfo>* KNGroupListData::extractList()
227 QList<KNGroupInfo>* temp = groups;
228 groups = 0;
229 return temp;
233 //===============================================================================
236 KNGroupManager::KNGroupManager( QObject * parent )
237 : QObject( parent )
239 a_rticleMgr = knGlobals.articleManager();
243 KNGroupManager::~KNGroupManager()
248 void KNGroupManager::syncGroups()
250 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
251 (*it)->syncDynamicData();
252 (*it)->writeConfig();
257 void KNGroupManager::loadGroups( KNNntpAccount::Ptr a )
259 KNGroup::Ptr group;
261 QString dir(a->path());
262 if (dir.isNull())
263 return;
264 QDir d(dir);
266 QStringList entries(d.entryList(QStringList("*.grpinfo")));
267 for(QStringList::Iterator it=entries.begin(); it != entries.end(); ++it) {
268 group = KNGroup::Ptr( new KNGroup( a ) );
269 if (group->readInfo(dir+(*it))) {
270 mGroupList.append( group );
271 emit groupAdded(group);
272 } else {
273 kError(5003) <<"Unable to load" << (*it) <<"!";
279 void KNGroupManager::getSubscribed( KNNntpAccount::Ptr a, QStringList &l )
281 l.clear();
282 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it )
283 if ( (*it)->account() == a )
284 l.append( (*it)->groupname() );
288 KNGroup::List KNGroupManager::groupsOfAccount( KNNntpAccount::Ptr a )
290 KNGroup::List ret;
291 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it )
292 if ( (*it)->account() == a )
293 ret.append( (*it) );
294 return ret;
298 bool KNGroupManager::loadHeaders( KNGroup::Ptr g )
300 if (!g)
301 return false;
303 if (g->isLoaded())
304 return true;
306 // we want to delete old stuff first => reduce vm fragmentation
307 knGlobals.memoryManager()->prepareLoad(g);
309 if (g->loadHdrs()) {
310 knGlobals.memoryManager()->updateCacheEntry( boost::static_pointer_cast<KNArticleCollection>( g ) );
311 return true;
314 return false;
318 bool KNGroupManager::unloadHeaders( KNGroup::Ptr g, bool force )
320 if(!g || g->isLocked())
321 return false;
323 if(!g->isLoaded())
324 return true;
326 if (!force && (c_urrentGroup == g))
327 return false;
329 if (g->unloadHdrs(force))
330 knGlobals.memoryManager()->removeCacheEntry( boost::static_pointer_cast<KNArticleCollection>( g ) );
331 else
332 return false;
334 return true;
338 KNGroup::Ptr KNGroupManager::group( const QString &gName, const KNServerInfo::Ptr s )
340 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it )
341 if ( (*it)->account() == s && (*it)->groupname() == gName )
342 return (*it);
344 return KNGroup::Ptr();
348 KNGroup::Ptr KNGroupManager::firstGroupOfAccount( const KNServerInfo::Ptr s )
350 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it )
351 if ( (*it)->account() == s )
352 return (*it);
354 return KNGroup::Ptr();
358 void KNGroupManager::expireAll(KNCleanUp *cup)
360 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
361 if( (*it)->isLocked() || (*it)->lockedArticles() > 0 )
362 continue;
363 if ( !(*it)->activeCleanupConfig()->expireToday() )
364 continue;
365 cup->appendCollection( *(it) );
370 void KNGroupManager::expireAll( KNNntpAccount::Ptr a )
372 KNCleanUp *cup = new KNCleanUp();
374 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
375 if( (*it)->account() != a || (*it)->isLocked() || (*it)->lockedArticles() > 0 )
376 continue;
378 ArticleWindow::closeAllWindowsForCollection( (*it) );
379 cup->appendCollection( (*it) );
382 cup->start();
384 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
385 if( (*it)->account() != a || (*it)->isLocked() || (*it)->lockedArticles() > 0 )
386 continue;
388 emit groupUpdated( (*it) );
389 if ( (*it) == c_urrentGroup ) {
390 if ( loadHeaders( (*it) ) )
391 a_rticleMgr->showHdrs();
392 else
393 a_rticleMgr->setGroup( KNGroup::Ptr() );
397 delete cup;
401 void KNGroupManager::showGroupDialog( KNNntpAccount::Ptr a, QWidget *parent )
403 KNGroupDialog* gDialog=new KNGroupDialog((parent!=0)? parent:knGlobals.topWidget, a);
405 connect( gDialog, SIGNAL(loadList(KNNntpAccount::Ptr)), this, SLOT(slotLoadGroupList(KNNntpAccount::Ptr)) );
406 connect( gDialog, SIGNAL(fetchList(KNNntpAccount::Ptr)), this, SLOT(slotFetchGroupList(KNNntpAccount::Ptr)) );
407 connect( gDialog, SIGNAL(checkNew(KNNntpAccount::Ptr,QDate)), this, SLOT(slotCheckForNewGroups(KNNntpAccount::Ptr,QDate)) );
408 connect( this, SIGNAL(newListReady(KNGroupListData::Ptr)), gDialog, SLOT(slotReceiveList(KNGroupListData::Ptr)) );
410 QWidget *oldTopWidget = knGlobals.topWidget;
411 // if the list of groups is empty, the parent of the message box
412 // asking whether to fetch will be "knGlobals.topWidget"
413 knGlobals.topWidget = gDialog;
414 int accept = gDialog->exec();
415 knGlobals.topWidget = oldTopWidget;
416 if(accept) {
417 KNGroup::Ptr g;
419 QStringList lst;
420 gDialog->toUnsubscribe(&lst);
421 if (lst.count()>0) {
422 if (KMessageBox::Yes == KMessageBox::questionYesNoList((parent!=0)? parent:knGlobals.topWidget,i18n("Do you really want to unsubscribe\nfrom these groups?"),
423 lst, QString(), KGuiItem(i18n("Unsubscribe")), KStandardGuiItem::cancel())) {
424 for ( QStringList::Iterator it = lst.begin(); it != lst.end(); ++it ) {
425 if((g=group(*it, a)))
426 unsubscribeGroup(g);
431 QList<KNGroupInfo> lst2;
432 gDialog->toSubscribe(&lst2);
433 Q_FOREACH( const KNGroupInfo& var, lst2) {
434 subscribeGroup(&var, a);
438 delete gDialog;
442 void KNGroupManager::subscribeGroup( const KNGroupInfo *gi, KNNntpAccount::Ptr a )
444 KNGroup::Ptr grp = KNGroup::Ptr( new KNGroup( a ) );
445 grp->setGroupname(gi->name);
446 grp->setDescription(gi->description);
447 grp->setStatus(gi->status);
448 grp->writeConfig();
449 mGroupList.append( grp );
450 emit groupAdded(grp);
454 bool KNGroupManager::unsubscribeGroup( KNGroup::Ptr g )
456 KNNntpAccount::Ptr acc;
457 if(!g) g=c_urrentGroup;
458 if(!g) return false;
460 if((g->isLocked()) || (g->lockedArticles()>0)) {
461 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()));
462 return false;
465 ArticleWindow::closeAllWindowsForCollection( g );
466 ArticleWidget::collectionRemoved( g );
468 acc=g->account();
470 QDir dir( acc->path(), g->groupname() + '*' );
471 if (dir.exists()) {
472 if (unloadHeaders(g, true)) {
473 if(c_urrentGroup==g) {
474 setCurrentGroup( KNGroup::Ptr() );
475 a_rticleMgr->updateStatusString();
478 QFileInfoList list = dir.entryInfoList(); // get list of matching files and delete all
479 Q_FOREACH( const QFileInfo &it, list ) {
480 if ( it.fileName() == g->groupname()+".dynamic" ||
481 it.fileName() == g->groupname()+".static" ||
482 it.fileName() == g->groupname()+".grpinfo" )
483 dir.remove( it.fileName() );
485 kDebug(5003) <<"Files deleted!";
487 emit groupRemoved(g);
488 mGroupList.removeAll( g );
490 return true;
494 return false;
498 void KNGroupManager::showGroupProperties( KNGroup::Ptr g )
500 if(!g) g=c_urrentGroup;
501 if(!g) return;
502 g->showProperties();
506 void KNGroupManager::checkGroupForNewHeaders( KNGroup::Ptr g )
508 if(!g) g=c_urrentGroup;
509 if(!g) return;
510 if(g->isLocked()) {
511 kDebug(5003) <<"KNGroupManager::checkGroupForNewHeaders() : group locked - returning";
512 return;
515 g->setMaxFetch( knGlobals.settings()->maxToFetch() );
516 emitJob( new ArticleListJob( this, g->account(), g ) );
520 void KNGroupManager::expireGroupNow( KNGroup::Ptr g )
522 if(!g) return;
524 if((g->isLocked()) || (g->lockedArticles()>0)) {
525 KMessageBox::sorry(knGlobals.topWidget,
526 i18n("This group cannot be expired because it is currently being updated.\n Please try again later."));
527 return;
530 ArticleWindow::closeAllWindowsForCollection( g );
532 KNCleanUp cup;
533 cup.expireGroup(g, true);
535 emit groupUpdated(g);
536 if(g==c_urrentGroup) {
537 if( loadHeaders(g) )
538 a_rticleMgr->showHdrs();
539 else
540 a_rticleMgr->setGroup( KNGroup::Ptr() );
545 void KNGroupManager::reorganizeGroup( KNGroup::Ptr g )
547 if(!g) g=c_urrentGroup;
548 if(!g) return;
549 g->reorganize();
550 if(g==c_urrentGroup)
551 a_rticleMgr->showHdrs();
555 void KNGroupManager::setCurrentGroup( KNGroup::Ptr g )
557 c_urrentGroup=g;
558 a_rticleMgr->setGroup(g);
559 kDebug(5003) <<"KNGroupManager::setCurrentGroup() : group changed";
561 if(g) {
562 if( !loadHeaders(g) ) {
563 //KMessageBox::error(knGlobals.topWidget, i18n("Cannot load saved headers"));
564 return;
566 a_rticleMgr->showHdrs();
567 if ( knGlobals.settings()->autoCheckGroups() )
568 checkGroupForNewHeaders(g);
573 void KNGroupManager::checkAll( KNNntpAccount::Ptr a, bool silent )
575 if(!a) return;
577 for ( KNGroup::List::Iterator it = mGroupList.begin(); it != mGroupList.end(); ++it ) {
578 if ( (*it)->account() == a ) {
579 (*it)->setMaxFetch( knGlobals.settings()->maxToFetch() );
580 emitJob( new ArticleListJob( this, (*it)->account(), boost::shared_ptr<KNJobItem>( *it ), silent ) );
585 void KNGroupManager::checkAll( int id, bool silent )
587 KNNntpAccount::Ptr account = KNGlobals::self()->accountManager()->account( id );
588 checkAll( account, silent );
592 void KNGroupManager::processJob(KNJobData *j)
594 if ( j->type()==KNJobData::JTLoadGroups || j->type()==KNJobData::JTFetchGroups ) {
595 KNGroupListData::Ptr d = boost::static_pointer_cast<KNGroupListData>( j->data() );
597 if (!j->canceled()) {
598 if (j->success()) {
599 if ( j->type() == KNJobData::JTFetchGroups ) {
600 // update the descriptions of the subscribed groups
601 foreach ( const KNGroup::Ptr &grp, mGroupList ) {
602 if ( grp->account() == j->account() ) {
603 foreach ( const KNGroupInfo &inf, *(d->groups) ) {
604 if ( inf.name == grp->groupname() ) {
605 grp->setDescription( inf.description );
606 grp->setStatus( inf.status );
607 break;
613 emit( newListReady( d ) );
614 } else {
615 KMessageBox::error(knGlobals.topWidget, j->errorString());
616 emit( newListReady( KNGroupListData::Ptr() ) );
618 } else {
619 emit( newListReady( KNGroupListData::Ptr() ) );
622 delete j;
624 } else { //KNJobData::JTfetchNewHeaders
625 KNGroup::Ptr group = boost::static_pointer_cast<KNGroup>( j->data() );
627 if (!j->canceled()) {
628 if (j->success()) {
629 if(group->lastFetchCount()>0) {
630 group->scoreArticles();
631 group->processXPostBuffer(true);
632 emit groupUpdated( group );
633 group->writeConfig();
634 knGlobals.memoryManager()->updateCacheEntry( boost::static_pointer_cast<KNArticleCollection>( group ) );
636 } else {
637 // ok, hack (?):
638 // stop all other active fetch jobs, this prevents that
639 // we show multiple error dialogs if a server is unavailable
640 knGlobals.scheduler()->cancelJobs( KNJobData::JTfetchNewHeaders );
641 ArticleListJob *lj = static_cast<ArticleListJob*>( j );
642 if ( !lj->silent() ) {
643 QString errorMsg = j->errorString();
644 if( j->error() == KIO::ERR_DOES_NOT_EXIST ) {
645 errorMsg = i18n( "The group %1 does not appear to exist anymore on the server.\n"
646 "You may unsubscribe.",
647 group->name() );
649 KMessageBox::error( knGlobals.topWidget, errorMsg );
653 if( group == c_urrentGroup ) {
654 a_rticleMgr->showHdrs(false);
657 delete j;
662 // load group list from disk (if this fails: ask user if we should fetch the list)
663 void KNGroupManager::slotLoadGroupList( KNNntpAccount::Ptr a )
665 KNGroupListData::Ptr d = KNGroupListData::Ptr( new KNGroupListData() );
666 d->path = a->path();
668 if(!QFileInfo(d->path+"groups").exists()) {
669 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")))) {
670 slotFetchGroupList(a);
671 return;
672 } else {
673 emit( newListReady( d ) );
674 return;
678 getSubscribed(a,d->subscribed);
679 d->getDescriptions = a->fetchDescriptions();
681 emitJob( new GroupLoadJob( this, a, d ) );
685 // fetch group list from server
686 void KNGroupManager::slotFetchGroupList( KNNntpAccount::Ptr a )
688 KNGroupListData::Ptr d = KNGroupListData::Ptr( new KNGroupListData() );
689 d->path = a->path();
690 getSubscribed(a,d->subscribed);
691 d->getDescriptions = a->fetchDescriptions();
692 d->codecForDescriptions = KGlobal::charsets()->codecForName( Locale::defaultCharset() );
694 emitJob( new GroupListJob( this, a, d ) );
698 // check for new groups (created after the given date)
699 void KNGroupManager::slotCheckForNewGroups( KNNntpAccount::Ptr a, QDate date )
701 KNGroupListData::Ptr d = KNGroupListData::Ptr( new KNGroupListData() );
702 d->path = a->path();
703 getSubscribed(a,d->subscribed);
704 d->getDescriptions = a->fetchDescriptions();
705 d->fetchSince = date;
706 d->codecForDescriptions = KGlobal::charsets()->codecForName( Locale::defaultCharset() );
708 emitJob( new GroupListJob( this, a, d, true ) );
712 //--------------------------------