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"
28 #include "knarticlewindow.h"
29 #include "knmemorymanager.h"
31 #include "utils/locale.h"
39 #include <kmessagebox.h>
40 #include <kiconloader.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
),
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()
103 bool KNGroupListData::readIn(KNJobData
*job
)
105 QFile
f( path
+ "groups" );
108 QString name
,description
;
110 KNGroup::Status status
=KNGroup::unknown
;
112 uint size
=f
.size()+2;
119 if(f
.open(QIODevice::ReadOnly
)) {
122 sepPos1
= line
.indexOf( ' ' );
124 if (sepPos1
==-1) { // no description
125 name
= QString::fromUtf8(line
);
127 status
= KNGroup::unknown
;
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
;
136 description
= QString::fromUtf8( line
.right( line
.length() - sepPos2
- 1 ).trimmed() );
137 switch (line
[sepPos1
+1]) {
138 case 'u': status
= KNGroup::unknown
;
140 case 'n': status
= KNGroup::readOnly
;
142 case 'y': status
= KNGroup::postingAllowed
;
144 case 'm': status
= KNGroup::moderated
;
150 if (subscribed
.contains(name
)) {
151 subscribed
.removeAll( name
); // group names are unique, we wont find it again anyway...
156 groups
->append(KNGroupInfo(name
,description
,false,sub
,status
));
158 if (timer
.elapsed() > 200) { // don't flicker
161 job
->setProgress( (f
.pos()*100)/size
);
169 kWarning(5003) <<"unable to open" << f
.fileName() <<" reason" << f
.error();
176 bool KNGroupListData::writeOut()
178 QFile
f(path
+"groups");
181 if(f
.open(QIODevice::WriteOnly
)) {
182 Q_FOREACH(const KNGroupInfo
& i
, *groups
) {
183 temp
= i
.name
.toUtf8();
185 case KNGroup::unknown
: temp
+= " u ";
187 case KNGroup::readOnly
: temp
+= " n ";
189 case KNGroup::postingAllowed
: temp
+= " y ";
191 case KNGroup::moderated
: temp
+= " m ";
194 temp
+= i
.description
.toUtf8() + '\n';
195 f
.write(temp
.data(),temp
.length());
200 kWarning(5003) <<"unable to open" << f
.fileName() <<" reason" << f
.error();
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
)
213 Q_FOREACH(const KNGroupInfo
& i
, *newGroups
) {
215 if ( (current
=groups
->indexOf(i
)) != -1) {
216 subscribed
= groups
->at(current
).subscribed
;
217 groups
->removeAt(current
); // avoid duplicates
220 groups
->append(KNGroupInfo(i
.name
,i
.description
,true,subscribed
,i
.status
));
225 QList
<KNGroupInfo
>* KNGroupListData::extractList()
227 QList
<KNGroupInfo
>* temp
= groups
;
233 //===============================================================================
236 KNGroupManager::KNGroupManager( 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
)
261 QString
dir(a
->path());
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
);
273 kError(5003) <<"Unable to load" << (*it
) <<"!";
279 void KNGroupManager::getSubscribed( KNNntpAccount::Ptr a
, QStringList
&l
)
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
)
291 for ( KNGroup::List::Iterator it
= mGroupList
.begin(); it
!= mGroupList
.end(); ++it
)
292 if ( (*it
)->account() == a
)
298 bool KNGroupManager::loadHeaders( KNGroup::Ptr g
)
306 // we want to delete old stuff first => reduce vm fragmentation
307 knGlobals
.memoryManager()->prepareLoad(g
);
310 knGlobals
.memoryManager()->updateCacheEntry( boost::static_pointer_cast
<KNArticleCollection
>( g
) );
318 bool KNGroupManager::unloadHeaders( KNGroup::Ptr g
, bool force
)
320 if(!g
|| g
->isLocked())
326 if (!force
&& (c_urrentGroup
== g
))
329 if (g
->unloadHdrs(force
))
330 knGlobals
.memoryManager()->removeCacheEntry( boost::static_pointer_cast
<KNArticleCollection
>( g
) );
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
)
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
)
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 )
363 if ( !(*it
)->activeCleanupConfig()->expireToday() )
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 )
378 ArticleWindow::closeAllWindowsForCollection( (*it
) );
379 cup
->appendCollection( (*it
) );
384 for ( KNGroup::List::Iterator it
= mGroupList
.begin(); it
!= mGroupList
.end(); ++it
) {
385 if( (*it
)->account() != a
|| (*it
)->isLocked() || (*it
)->lockedArticles() > 0 )
388 emit
groupUpdated( (*it
) );
389 if ( (*it
) == c_urrentGroup
) {
390 if ( loadHeaders( (*it
) ) )
391 a_rticleMgr
->showHdrs();
393 a_rticleMgr
->setGroup( KNGroup::Ptr() );
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
;
420 gDialog
->toUnsubscribe(&lst
);
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
)))
431 QList
<KNGroupInfo
> lst2
;
432 gDialog
->toSubscribe(&lst2
);
433 Q_FOREACH( const KNGroupInfo
& var
, lst2
) {
434 subscribeGroup(&var
, a
);
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
);
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
;
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()));
465 ArticleWindow::closeAllWindowsForCollection( g
);
466 ArticleWidget::collectionRemoved( g
);
470 QDir
dir( acc
->path(), g
->groupname() + '*' );
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
);
498 void KNGroupManager::showGroupProperties( KNGroup::Ptr g
)
500 if(!g
) g
=c_urrentGroup
;
506 void KNGroupManager::checkGroupForNewHeaders( KNGroup::Ptr g
)
508 if(!g
) g
=c_urrentGroup
;
511 kDebug(5003) <<"KNGroupManager::checkGroupForNewHeaders() : group locked - returning";
515 g
->setMaxFetch( knGlobals
.settings()->maxToFetch() );
516 emitJob( new ArticleListJob( this, g
->account(), g
) );
520 void KNGroupManager::expireGroupNow( KNGroup::Ptr g
)
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."));
530 ArticleWindow::closeAllWindowsForCollection( g
);
533 cup
.expireGroup(g
, true);
535 emit
groupUpdated(g
);
536 if(g
==c_urrentGroup
) {
538 a_rticleMgr
->showHdrs();
540 a_rticleMgr
->setGroup( KNGroup::Ptr() );
545 void KNGroupManager::reorganizeGroup( KNGroup::Ptr g
)
547 if(!g
) g
=c_urrentGroup
;
551 a_rticleMgr
->showHdrs();
555 void KNGroupManager::setCurrentGroup( KNGroup::Ptr g
)
558 a_rticleMgr
->setGroup(g
);
559 kDebug(5003) <<"KNGroupManager::setCurrentGroup() : group changed";
562 if( !loadHeaders(g
) ) {
563 //KMessageBox::error(knGlobals.topWidget, i18n("Cannot load saved headers"));
566 a_rticleMgr
->showHdrs();
567 if ( knGlobals
.settings()->autoCheckGroups() )
568 checkGroupForNewHeaders(g
);
573 void KNGroupManager::checkAll( KNNntpAccount::Ptr a
, bool silent
)
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()) {
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
);
613 emit( newListReady( d
) );
615 KMessageBox::error(knGlobals
.topWidget
, j
->errorString());
616 emit( newListReady( KNGroupListData::Ptr() ) );
619 emit( newListReady( KNGroupListData::Ptr() ) );
624 } else { //KNJobData::JTfetchNewHeaders
625 KNGroup::Ptr group
= boost::static_pointer_cast
<KNGroup
>( j
->data() );
627 if (!j
->canceled()) {
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
) );
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.",
649 KMessageBox::error( knGlobals
.topWidget
, errorMsg
);
653 if( group
== c_urrentGroup
) {
654 a_rticleMgr
->showHdrs(false);
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() );
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
);
673 emit( newListReady( d
) );
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() );
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() );
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 //--------------------------------