Fix more broken iterations...
[kdepim.git] / kmail / kmfoldercachedimap.cpp
blobb7fc784f2dcd17bfd9747588e86f440d63e21619
1 /*
2 * kmfoldercachedimap.cpp
4 * Copyright (c) 2002-2004 Bo Thorsen <bo@sonofthor.dk>
5 * Copyright (c) 2002-2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; version 2 of the License
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give
21 * permission to link the code of this program with any edition of
22 * the Qt library by Trolltech AS, Norway (or with modified versions
23 * of Qt that use the same license as Qt), and distribute linked
24 * combinations including the two. You must obey the GNU General
25 * Public License in all respects for all of the code used other than
26 * Qt. If you modify this file, you may extend this exception to
27 * your version of the file, but you are not obligated to do so. If
28 * you do not wish to do so, delete this exception statement from
29 * your version.
32 #include "kmfoldercachedimap.h"
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
38 #include "globalsettings.h"
39 #include "kmkernel.h"
40 #include "undostack.h"
41 #include "kmfoldermgr.h"
42 #include "kmacctcachedimap.h"
43 #include "accountmanager.h"
44 using KMail::AccountManager;
45 #include "kmailicalifaceimpl.h"
46 #include "kmfolder.h"
47 #include "kmglobal.h"
48 #include "acljobs.h"
49 #include "broadcaststatus.h"
50 using KPIM::BroadcastStatus;
51 #include "progressmanager.h"
52 using KMail::CachedImapJob;
53 #include "imapaccountbase.h"
54 using KMail::ImapAccountBase;
55 #include "listjob.h"
56 using KMail::ListJob;
57 #include "folderselectiondialog.h"
58 #include "kmcommands.h"
59 #include "annotationjobs.h"
60 #include "quotajobs.h"
61 #include "groupwareadaptor.h"
62 using namespace KMail;
64 #include <kio/jobuidelegate.h>
65 #include <kio/global.h>
66 #include <kio/scheduler.h>
68 #include <kmessagebox.h>
69 #include <klocale.h>
70 #include <kdebug.h>
71 #include <kconfig.h>
72 #include <kconfiggroup.h>
73 #include <khbox.h>
75 #include <QByteArray>
76 #include <QTextStream>
77 #include <QTimerEvent>
78 #include <QVBoxLayout>
79 #include <QVector>
80 #include <QBuffer>
81 #include <QButtonGroup>
82 #include <QComboBox>
83 #include <QFile>
84 #include <QLabel>
85 #include <QLayout>
86 #include <QRadioButton>
88 #include <errno.h>
90 #define UIDCACHE_VERSION 1
92 static QString incidencesForToString( KMFolderCachedImap::IncidencesFor r )
94 switch ( r ) {
95 case KMFolderCachedImap::IncForNobody:
96 return "nobody";
97 case KMFolderCachedImap::IncForAdmins:
98 return "admins";
99 case KMFolderCachedImap::IncForReaders:
100 return "readers";
102 return QString(); // can't happen
105 static KMFolderCachedImap::IncidencesFor incidencesForFromString( const QString &str )
107 if ( str == "nobody" ) {
108 return KMFolderCachedImap::IncForNobody;
110 if ( str == "admins" ) {
111 return KMFolderCachedImap::IncForAdmins;
113 if ( str == "readers" ) {
114 return KMFolderCachedImap::IncForReaders;
116 return KMFolderCachedImap::IncForAdmins; // by default
119 DImapTroubleShootDialog::DImapTroubleShootDialog( QWidget *parent )
120 : KDialog( parent ), rc( None )
122 setCaption( i18n( "Troubleshooting IMAP Cache" ) );
123 setButtons( Ok | Cancel );
124 setDefaultButton( Cancel );
125 setModal( true );
127 QFrame *page = new QFrame( this );
128 setMainWidget( page );
129 QVBoxLayout *topLayout = new QVBoxLayout( page );
130 topLayout->setSpacing( 0 );
131 QString txt = i18n( "<p><b>Troubleshooting the IMAP cache.</b></p>"
132 "<p>If you have problems with synchronizing an IMAP "
133 "folder, you should first try rebuilding the index "
134 "file. This will take some time to rebuild, but will "
135 "not cause any problems.</p><p>If that is not enough, "
136 "you can try refreshing the IMAP cache. If you do this, "
137 "you will loose all your local changes for this folder "
138 "and all its subfolders.</p>" );
139 QLabel *label = new QLabel(txt, page );
140 label->setWordWrap(true);
141 topLayout->addWidget( label );
142 showButtonSeparator( true );
144 QButtonGroup *group = new QButtonGroup( 0 );
146 mIndexButton = new QRadioButton( page );
147 mIndexButton->setText( i18n( "Rebuild &Index" ) );
148 group->addButton( mIndexButton );
149 topLayout->addWidget( mIndexButton );
151 KHBox *hbox = new KHBox( page );
152 QLabel *scopeLabel = new QLabel( i18n( "Scope:" ), hbox );
153 scopeLabel->setEnabled( false );
154 mIndexScope = new QComboBox( hbox );
155 mIndexScope->addItem( i18n( "Only current folder" ) );
156 mIndexScope->addItem( i18n( "Current folder and all subfolders" ) );
157 mIndexScope->addItem( i18n( "All folders of this account" ) );
158 mIndexScope->setEnabled( false );
159 topLayout->addWidget( hbox );
161 mCacheButton = new QRadioButton( page );
162 mCacheButton->setText( i18n( "Refresh &Cache" ) );
163 group->addButton( mCacheButton );
164 topLayout->addWidget( mCacheButton );
166 connect ( mIndexButton, SIGNAL( toggled( bool ) ),
167 mIndexScope, SLOT( setEnabled( bool ) ) );
168 connect ( mIndexButton, SIGNAL( toggled( bool ) ),
169 scopeLabel, SLOT( setEnabled( bool ) ) );
171 connect( this, SIGNAL( okClicked () ), this, SLOT( slotDone() ) );
174 int DImapTroubleShootDialog::run()
176 DImapTroubleShootDialog d;
177 d.exec();
178 return d.rc;
181 void DImapTroubleShootDialog::slotDone()
183 rc = None;
184 if ( mIndexButton->isChecked() ) {
185 rc = mIndexScope->currentIndex();
186 } else if ( mCacheButton->isChecked() ) {
187 rc = RefreshCache;
189 done( Ok );
192 KMFolderCachedImap::KMFolderCachedImap( KMFolder *folder, const char *aName )
193 : KMFolderMaildir( folder, aName ),
194 mSyncState( SYNC_STATE_INITIAL ), mContentState( imapNoInformation ),
195 mSubfolderState( imapNoInformation ),
196 mIncidencesFor( IncForAdmins ),
197 mIsSelected( false ),
198 mCheckFlags( true ), mReadOnly( false ), mAccount( 0 ), uidMapDirty( true ),
199 uidWriteTimer( -1 ), mLastUid( 0 ), mTentativeHighestUid( 0 ),
200 mUserRights( 0 ), mOldUserRights( 0 ), mSilentUpload( false ),
201 /*mHoldSyncs( false ),*/
202 mFolderRemoved( false ),
203 mRecurse( true ),
204 mStatusChangedLocally( false ), mAnnotationFolderTypeChanged( false ),
205 mIncidencesForChanged( false ), mPersonalNamespacesCheckDone( true ),
206 mQuotaInfo(), mAlarmsBlocked( false ),
207 mRescueCommandCount( 0 )
209 setUidValidity( "" );
210 readUidCache();
212 mProgress = 0;
215 KMFolderCachedImap::~KMFolderCachedImap()
217 if ( !mFolderRemoved ) {
218 writeConfig();
219 writeUidCache();
222 if ( kmkernel->undoStack() ) {
223 kmkernel->undoStack()->folderDestroyed( folder() );
227 void KMFolderCachedImap::initializeFrom( KMFolderCachedImap *parent )
229 setAccount( parent->account() );
232 * Now that we have an account, tell it that this folder was created:
233 * if this folder was just removed, then we don't really want to remove
234 * it from the server.
236 mAccount->removeDeletedFolder( imapPath() );
237 setUserRights( parent->userRights() );
240 void KMFolderCachedImap::readConfig()
242 KConfig *config = KMKernel::config();
243 KConfigGroup group( config, "Folder-" + folder()->idString() );
244 if ( mImapPath.isEmpty() ) {
245 mImapPath = group.readEntry( "ImapPath" );
247 if ( QString( objectName() ).toUpper() == "INBOX" && mImapPath == "/INBOX/" ) {
248 folder()->setLabel( i18n( "inbox" ) );
249 // for the icon
250 folder()->setSystemFolder( true );
252 mNoContent = group.readEntry( "NoContent", false );
253 mReadOnly = group.readEntry( "ReadOnly", false );
255 if ( mAnnotationFolderType != "FROMSERVER" ) {
256 mAnnotationFolderType = group.readEntry( "Annotation-FolderType" );
257 // if there is an annotation, it has to be XML
258 if ( !mAnnotationFolderType.isEmpty() &&
259 !mAnnotationFolderType.startsWith( "mail" ) ) {
260 kmkernel->iCalIface().setStorageFormat( folder(), StorageXML );
263 mIncidencesFor = incidencesForFromString( group.readEntry( "IncidencesFor" ) );
264 mAlarmsBlocked = group.readEntry( "AlarmsBlocked", false );
265 // kDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath )
266 // << " readConfig: mIncidencesFor=" << mIncidencesFor << endl;
268 mUserRights = group.readEntry( "UserRights", 0 ); // default is we don't know
269 mOldUserRights = mUserRights;
271 int storageQuotaUsage = group.readEntry( "StorageQuotaUsage", -1 );
272 int storageQuotaLimit = group.readEntry( "StorageQuotaLimit", -1 );
273 QString storageQuotaRoot = group.readEntry( "StorageQuotaRoot", QString() );
274 if ( !storageQuotaRoot.isNull() ) { // isEmpty() means we know there is no quota set
275 mQuotaInfo.setName( "STORAGE" );
276 mQuotaInfo.setRoot( storageQuotaRoot );
278 if ( storageQuotaUsage > -1 ) {
279 mQuotaInfo.setCurrent( storageQuotaUsage );
281 if ( storageQuotaLimit > -1 ) {
282 mQuotaInfo.setMax( storageQuotaLimit );
286 KMFolderMaildir::readConfig();
288 mStatusChangedLocally = group.readEntry( "StatusChangedLocally", false );
289 mAnnotationFolderTypeChanged = group.readEntry( "AnnotationFolderTypeChanged", false );
290 mIncidencesForChanged = group.readEntry( "IncidencesForChanged", false );
291 if ( mImapPath.isEmpty() ) {
292 mImapPathCreation = group.readEntry("ImapPathCreation");
296 void KMFolderCachedImap::writeConfig()
298 KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() );
299 configGroup.writeEntry( "ImapPath", mImapPath );
300 configGroup.writeEntry( "NoContent", mNoContent );
301 configGroup.writeEntry( "ReadOnly", mReadOnly );
302 configGroup.writeEntry( "StatusChangedLocally", mStatusChangedLocally );
303 if ( !mImapPathCreation.isEmpty() ) {
304 if ( mImapPath.isEmpty() ) {
305 configGroup.writeEntry( "ImapPathCreation", mImapPathCreation );
306 } else {
307 configGroup.deleteEntry( "ImapPathCreation" );
310 writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
311 KMFolderMaildir::writeConfig();
314 void KMFolderCachedImap::writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig()
316 KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() );
317 if ( !folder()->noContent() ) {
318 configGroup.writeEntry( "AnnotationFolderTypeChanged", mAnnotationFolderTypeChanged );
319 configGroup.writeEntry( "Annotation-FolderType", mAnnotationFolderType );
320 configGroup.writeEntry( "IncidencesForChanged", mIncidencesForChanged );
321 configGroup.writeEntry( "IncidencesFor", incidencesForToString( mIncidencesFor ) );
322 configGroup.writeEntry( "AlarmsBlocked", mAlarmsBlocked );
323 configGroup.writeEntry( "UserRights", mUserRights );
325 if ( mQuotaInfo.isValid() ) {
326 if ( mQuotaInfo.current().isValid() ) {
327 configGroup.writeEntry( "StorageQuotaUsage", mQuotaInfo.current().toInt() );
329 if ( mQuotaInfo.max().isValid() ) {
330 configGroup.writeEntry( "StorageQuotaLimit", mQuotaInfo.max().toInt() );
332 configGroup.writeEntry( "StorageQuotaRoot", mQuotaInfo.root() );
333 } else {
334 configGroup.deleteEntry( "StorageQuotaUsage");
335 configGroup.deleteEntry( "StorageQuotaRoot");
336 configGroup.deleteEntry( "StorageQuotaLimit");
341 int KMFolderCachedImap::create()
343 int rc = KMFolderMaildir::create();
344 // FIXME why the below? - till
345 readConfig();
346 mUnreadMsgs = -1;
347 return rc;
350 void KMFolderCachedImap::remove()
352 mFolderRemoved = true;
354 QString part1 = folder()->path() + "/." + dotEscape( objectName() );
355 QString uidCacheFile = part1 + ".uidcache";
356 // This is the account folder of an account that was just removed
357 // When this happens, be sure to delete all traces of the cache
358 if ( QFile::exists( uidCacheFile ) ) {
359 unlink( QFile::encodeName( uidCacheFile ) );
362 FolderStorage::remove();
365 QString KMFolderCachedImap::uidCacheLocation() const
367 QString sLocation( folder()->path() );
368 if ( !sLocation.isEmpty() ) {
369 sLocation += '/';
371 return sLocation + '.' + dotEscape(fileName()) + ".uidcache";
374 int KMFolderCachedImap::readUidCache()
376 QFile uidcache( uidCacheLocation() );
377 if ( uidcache.open( QIODevice::ReadOnly ) ) {
378 char buf[1024];
379 int len = uidcache.readLine( buf, sizeof( buf ) );
380 if ( len > 0 ) {
381 int cacheVersion;
382 sscanf( buf, "# KMail-UidCache V%d\n", &cacheVersion );
383 if ( cacheVersion == UIDCACHE_VERSION ) {
384 len = uidcache.readLine( buf, sizeof( buf ) );
385 if ( len > 0 ) {
386 setUidValidity( QString::fromLocal8Bit( buf ).trimmed() );
387 len = uidcache.readLine( buf, sizeof( buf ) );
388 if ( len > 0 ) {
389 // load the last known highest uid from the on disk cache
390 setLastUid( QString::fromLocal8Bit( buf ).trimmed().toULong() );
391 return 0;
397 return -1;
400 int KMFolderCachedImap::writeUidCache()
402 if ( uidValidity().isEmpty() || uidValidity() == "INVALID" ) {
403 // No info from the server yet, remove the file.
404 if ( QFile::exists( uidCacheLocation() ) ) {
405 unlink( QFile::encodeName( uidCacheLocation() ) );
407 return 0;
410 QFile uidcache( uidCacheLocation() );
411 if ( uidcache.open( QIODevice::WriteOnly ) ) {
412 QTextStream str( &uidcache );
413 str << "# KMail-UidCache V" << UIDCACHE_VERSION << endl;
414 str << uidValidity() << endl;
415 str << lastUid() << endl;
416 uidcache.flush();
417 fsync( uidcache.handle() ); /* this is probably overkill */
418 uidcache.close();
419 return 0;
420 } else {
421 return errno; /* does QFile set errno? */
425 void KMFolderCachedImap::reloadUidMap()
427 kDebug(5006) << "Reloading Uid Map " << endl;
428 uidMap.clear();
429 open( "reloadUid" );
430 for ( int i = 0; i < count(); ++i ) {
431 KMMsgBase *msg = getMsgBase( i );
432 if ( !msg ) {
433 continue;
435 ulong uid = msg->UID();
436 uidMap.insert( uid, i );
438 close( "reloadUid" );
439 uidMapDirty = false;
442 KMMessage *KMFolderCachedImap::take( int idx )
444 uidMapDirty = true;
445 return KMFolderMaildir::take( idx );
448 int KMFolderCachedImap::addMsgInternal( KMMessage *msg, bool newMail, int *index_return )
450 // Possible optimization: Only dirty if not filtered below
451 ulong uid = msg->UID();
452 if ( uid != 0 ) {
453 uidMapDirty = true;
456 // Add the message
457 int rc = KMFolderMaildir::addMsg( msg, index_return );
459 if ( newMail && imapPath() == "/INBOX/" ) {
460 // This is a new message. Filter it
461 mAccount->processNewMsg( msg );
464 return rc;
467 int KMFolderCachedImap::addMsg( KMMessage *msg, int *index_return )
469 if ( !canAddMsgNow( msg, index_return ) ) {
470 return 0;
473 // Add it to storage
474 int rc = KMFolderMaildir::addMsgInternal( msg, index_return, true /*stripUID*/);
475 return rc;
478 void KMFolderCachedImap::removeMsg( int idx, bool imapQuiet )
480 uidMapDirty = true;
481 // Remove it from disk
482 KMFolderMaildir::removeMsg( idx, imapQuiet );
485 bool KMFolderCachedImap::canRemoveFolder() const
487 // If this has subfolders it can't be removed
488 if ( folder() && folder()->child() && folder()->child()->count() > 0 ) {
489 return false;
491 return true;
494 int KMFolderCachedImap::rename( const QString &aName, KMFolderDir *aParent )
496 Q_UNUSED( aParent );
498 QString oldName = mAccount->renamedFolder( imapPath() );
499 if ( oldName.isEmpty() ) {
500 oldName = objectName();
503 if ( aName == oldName ) {
504 // Stupid user trying to rename it to it's old name :)
505 return 0;
508 if ( account() == 0 || imapPath().isEmpty() ) {
509 // We don't think any of this can happen anymore
510 QString err = i18n("You must synchronize with the server before renaming IMAP folders.");
511 KMessageBox::error( 0, err );
512 return -1;
516 * Make the change appear to the user with setLabel, but we'll do the change
517 * on the server during the next sync. The name() is the name at the time of
518 * the last sync. Only rename if the new one is different. If it's the same,
519 * don't rename, but also make sure the rename is reset, in the case of
520 * A -> B -> A renames.
522 if ( objectName() != aName ) {
523 mAccount->addRenamedFolder( imapPath(), folder()->label(), aName );
524 } else {
525 mAccount->removeRenamedFolder( imapPath() );
528 folder()->setLabel( aName );
529 emit nameChanged(); // for kmailicalifaceimpl
531 return 0;
534 KMFolder *KMFolderCachedImap::trashFolder() const
536 QString trashStr = account()->trash();
537 return kmkernel->dimapFolderMgr()->findIdString( trashStr );
540 void KMFolderCachedImap::setLastUid( ulong uid )
542 mLastUid = uid;
543 if ( uidWriteTimer == -1 ) {
544 // Write in one minute
545 uidWriteTimer = startTimer( 60000 );
549 void KMFolderCachedImap::timerEvent( QTimerEvent *e )
551 Q_UNUSED( e );
553 killTimer( uidWriteTimer );
554 uidWriteTimer = -1;
555 writeUidCache();
558 ulong KMFolderCachedImap::lastUid()
560 return mLastUid;
563 KMMsgBase *KMFolderCachedImap::findByUID( ulong uid )
565 bool mapReloaded = false;
566 if ( uidMapDirty ) {
567 reloadUidMap();
568 mapReloaded = true;
571 QMap<ulong,int>::Iterator it = uidMap.find( uid );
572 if ( it != uidMap.end() ) {
573 KMMsgBase *msg = getMsgBase( *it );
574 if ( msg && msg->UID() == uid ) {
575 return msg;
577 } else {
578 kDebug(5006) << "Didn't find uid: " << uid << "in cache!" << endl;
580 // Not found by now
581 // if ( mapReloaded )
582 // Not here then
583 return 0;
585 // There could be a problem in the maps. Rebuild them and try again
586 reloadUidMap();
587 it = uidMap.find( uid );
588 if ( it != uidMap.end() ) {
589 // Since the uid map is just rebuilt, no need for the sanity check
590 return getMsgBase( *it );
591 } else {
592 kDebug(5006) << "Reloaded, but stil didn't find uid: " << uid << endl;
595 // Then it's not here
596 return 0;
599 KMAcctCachedImap *KMFolderCachedImap::account() const
601 if ( (KMAcctCachedImap *)mAccount == 0 ) {
602 // Find the account
603 mAccount =
604 static_cast<KMAcctCachedImap *>( kmkernel->acctMgr()->findByName( objectName() ) );
607 return mAccount;
610 void KMFolderCachedImap::slotTroubleshoot()
612 const int rc = DImapTroubleShootDialog::run();
614 if ( rc == DImapTroubleShootDialog::RefreshCache ) {
615 // Refresh cache
616 if ( !account() ) {
617 KMessageBox::sorry( 0, i18n("No account setup for this folder.\n"
618 "Please try running a sync before this.") );
619 return;
621 QString str = i18n("Are you sure you want to refresh the IMAP cache of "
622 "the folder %1 and all its subfolders?\nThis will "
623 "remove all changes you have done locally to your "
624 "folders.", label() );
625 QString s1 = i18n("Refresh IMAP Cache");
626 QString s2 = i18n("&Refresh");
627 if ( KMessageBox::warningContinueCancel( 0, str, s1, KGuiItem( s2 ) ) ==
628 KMessageBox::Continue ) {
629 account()->invalidateIMAPFolders( this );
631 } else {
632 // Rebuild index file
633 switch ( rc ) {
634 case DImapTroubleShootDialog::ReindexAll:
636 KMFolderCachedImap *rootStorage =
637 dynamic_cast<KMFolderCachedImap*>( account()->rootFolder() );
638 if ( rootStorage ) {
639 rootStorage->createIndexFromContentsRecursive();
641 break;
643 case DImapTroubleShootDialog::ReindexCurrent:
644 createIndexFromContents();
645 break;
646 case DImapTroubleShootDialog::ReindexRecursive:
647 createIndexFromContentsRecursive();
648 break;
649 default:
650 return;
652 KMessageBox::information( 0, i18n( "The index of this folder has been "
653 "recreated." ) );
657 void KMFolderCachedImap::serverSync( bool recurse )
659 if ( mSyncState != SYNC_STATE_INITIAL ) {
660 if ( KMessageBox::warningYesNo(
662 i18n("Folder %1 is not in initial sync state (state was %2). "
663 "Do you want to reset it to initial sync state and sync anyway?",
664 imapPath(), int( mSyncState ) ),
665 QString(), KGuiItem( i18n("Reset && Sync") ),
666 KStandardGuiItem::cancel() ) == KMessageBox::Yes ) {
667 mSyncState = SYNC_STATE_INITIAL;
668 } else {
669 return;
673 mRecurse = recurse;
674 assert( account() );
676 ProgressItem *progressItem = mAccount->mailCheckProgressItem();
677 if ( progressItem ) {
678 progressItem->reset();
679 progressItem->setTotalItems( 100 );
682 mProgress = 0;
683 mTentativeHighestUid = 0; // reset, last sync could have been canceled
684 serverSyncInternal();
687 QString KMFolderCachedImap::state2String( int state ) const
689 switch( state ) {
690 case SYNC_STATE_INITIAL: return "SYNC_STATE_INITIAL";
691 case SYNC_STATE_GET_USERRIGHTS: return "SYNC_STATE_GET_USERRIGHTS";
692 case SYNC_STATE_PUT_MESSAGES: return "SYNC_STATE_PUT_MESSAGES";
693 case SYNC_STATE_UPLOAD_FLAGS: return "SYNC_STATE_UPLOAD_FLAGS";
694 case SYNC_STATE_CREATE_SUBFOLDERS: return "SYNC_STATE_CREATE_SUBFOLDERS";
695 case SYNC_STATE_LIST_SUBFOLDERS: return "SYNC_STATE_LIST_SUBFOLDERS";
696 case SYNC_STATE_LIST_NAMESPACES: return "SYNC_STATE_LIST_NAMESPACES";
697 case SYNC_STATE_LIST_SUBFOLDERS2: return "SYNC_STATE_LIST_SUBFOLDERS2";
698 case SYNC_STATE_DELETE_SUBFOLDERS: return "SYNC_STATE_DELETE_SUBFOLDERS";
699 case SYNC_STATE_LIST_MESSAGES: return "SYNC_STATE_LIST_MESSAGES";
700 case SYNC_STATE_DELETE_MESSAGES: return "SYNC_STATE_DELETE_MESSAGES";
701 case SYNC_STATE_GET_MESSAGES: return "SYNC_STATE_GET_MESSAGES";
702 case SYNC_STATE_EXPUNGE_MESSAGES: return "SYNC_STATE_EXPUNGE_MESSAGES";
703 case SYNC_STATE_HANDLE_INBOX: return "SYNC_STATE_HANDLE_INBOX";
704 case SYNC_STATE_TEST_ANNOTATIONS: return "SYNC_STATE_TEST_ANNOTATIONS";
705 case SYNC_STATE_GET_ANNOTATIONS: return "SYNC_STATE_GET_ANNOTATIONS";
706 case SYNC_STATE_SET_ANNOTATIONS: return "SYNC_STATE_SET_ANNOTATIONS";
707 case SYNC_STATE_GET_ACLS: return "SYNC_STATE_GET_ACLS";
708 case SYNC_STATE_SET_ACLS: return "SYNC_STATE_SET_ACLS";
709 case SYNC_STATE_GET_QUOTA: return "SYNC_STATE_GET_QUOTA";
710 case SYNC_STATE_FIND_SUBFOLDERS: return "SYNC_STATE_FIND_SUBFOLDERS";
711 case SYNC_STATE_SYNC_SUBFOLDERS: return "SYNC_STATE_SYNC_SUBFOLDERS";
712 case SYNC_STATE_RENAME_FOLDER: return "SYNC_STATE_RENAME_FOLDER";
713 case SYNC_STATE_CHECK_UIDVALIDITY: return "SYNC_STATE_CHECK_UIDVALIDITY";
714 default: return "Unknown state";
719 Progress calculation: each step is assigned a span. Initially the total is 100.
720 But if we skip a step, don't increase the progress.
721 This leaves more room for the step a with variable size (get_messages)
722 connecting 5
723 getuserrights 5
724 rename 5
725 check_uidvalidity 5
726 create_subfolders 5
727 put_messages 10 (but it can take a very long time, with many messages....)
728 upload_flags 5
729 list_subfolders 5
730 list_subfolders2 0 (all local)
731 delete_subfolders 5
732 list_messages 10
733 delete_messages 10
734 expunge_messages 5
735 get_messages variable (remaining-5) i.e. minimum 15.
736 check_annotations 0 (rare)
737 set_annotations 0 (rare)
738 get_annotations 2
739 set_acls 0 (rare)
740 get_acls 3
742 noContent folders have only a few of the above steps
743 (permissions, and all subfolder stuff), so its steps should be given more span
747 // While the server synchronization is running, mSyncState will hold
748 // the state that should be executed next
749 void KMFolderCachedImap::serverSyncInternal()
751 // This is used to stop processing when we're about to exit
752 // and the current job wasn't cancellable.
753 // For user-requested abort, we'll use signalAbortRequested instead.
754 if ( kmkernel->mailCheckAborted() ) {
755 resetSyncState();
756 emit folderComplete( this, false );
757 return;
760 switch( mSyncState ) {
761 case SYNC_STATE_INITIAL:
763 mProgress = 0;
764 foldersForDeletionOnServer.clear();
765 newState( mProgress, i18n("Synchronizing"));
767 open( "cachedimap" );
768 if ( !noContent() ) {
769 mAccount->addLastUnreadMsgCount( this, countUnread() );
772 // Connect to the server (i.e. prepare the slave)
773 ImapAccountBase::ConnectionState cs = mAccount->makeConnection();
774 if ( cs == ImapAccountBase::Error ) {
775 // Cancelled by user, or slave can't start
776 // We stop here. We're already in SYNC_STATE_INITIAL for the next time.
777 newState( mProgress, i18n( "Error connecting to server %1", mAccount->host() ) );
778 close( "cachedimap" );
779 emit folderComplete( this, false );
780 break;
781 } else if ( cs == ImapAccountBase::Connecting ) {
782 mAccount->setAnnotationCheckPassed( false );
783 newState( mProgress, i18n("Connecting to %1", mAccount->host() ) );
785 // We'll wait for the connectionResult signal from the account.
786 connect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
787 this, SLOT( slotConnectionResult(int, const QString&) ) );
788 break;
789 } else {
790 // Connected
791 mSyncState = SYNC_STATE_GET_USERRIGHTS;
792 // Fall through to next state
796 case SYNC_STATE_GET_USERRIGHTS:
797 mSyncState = SYNC_STATE_RENAME_FOLDER;
799 if ( !noContent() && mAccount->hasACLSupport() ) {
800 // Check the user's rights. We do this every time in case they changed.
801 mOldUserRights = mUserRights;
802 newState( mProgress, i18n("Checking permissions"));
803 connect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
804 this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
805 mAccount->getUserRights( folder(), imapPath() ); // after connecting, due to the INBOX case
806 break;
809 case SYNC_STATE_RENAME_FOLDER:
811 mSyncState = SYNC_STATE_CHECK_UIDVALIDITY;
812 // Returns the new name if the folder was renamed, empty otherwise.
813 bool isResourceFolder = kmkernel->iCalIface().isStandardResourceFolder( folder() );
814 QString newName = mAccount->renamedFolder( imapPath() );
815 if ( !newName.isEmpty() && !folder()->isSystemFolder() && !isResourceFolder ) {
816 newState( mProgress, i18n("Renaming folder") );
817 CachedImapJob *job = new CachedImapJob( newName, CachedImapJob::tRenameFolder, this );
818 connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
819 connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
820 job->start();
821 break;
825 case SYNC_STATE_CHECK_UIDVALIDITY:
826 mSyncState = SYNC_STATE_CREATE_SUBFOLDERS;
827 if ( !noContent() ) {
828 checkUidValidity();
829 break;
831 // Else carry on
833 case SYNC_STATE_CREATE_SUBFOLDERS:
834 mSyncState = SYNC_STATE_PUT_MESSAGES;
835 createNewFolders();
836 break;
838 case SYNC_STATE_PUT_MESSAGES:
839 mSyncState = SYNC_STATE_UPLOAD_FLAGS;
840 if ( !noContent() ) {
841 uploadNewMessages();
842 break;
844 // Else carry on
845 case SYNC_STATE_UPLOAD_FLAGS:
846 mSyncState = SYNC_STATE_LIST_NAMESPACES;
847 if ( !noContent() ) {
848 // We haven't downloaded messages yet, so we need to build the map.
849 if ( uidMapDirty ) {
850 reloadUidMap();
853 // Upload flags, unless we know from the ACL that we're not allowed
854 // to do that or they did not change locally
855 if ( mUserRights <= 0 || ( mUserRights & KMail::ACLJobs::WriteFlags ) ) {
856 if ( mStatusChangedLocally ) {
857 uploadFlags();
858 break;
862 // Else carry on
864 case SYNC_STATE_LIST_NAMESPACES:
865 if ( this == mAccount->rootFolder() ) {
866 listNamespaces();
867 break;
869 mSyncState = SYNC_STATE_LIST_SUBFOLDERS;
870 // Else carry on
872 case SYNC_STATE_LIST_SUBFOLDERS:
873 newState( mProgress, i18n("Retrieving folderlist"));
874 mSyncState = SYNC_STATE_LIST_SUBFOLDERS2;
875 if ( !listDirectory() ) {
876 mSyncState = SYNC_STATE_INITIAL;
877 KMessageBox::error(0, i18n("Error while retrieving the folderlist"));
879 break;
881 case SYNC_STATE_LIST_SUBFOLDERS2:
882 mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
883 mProgress += 10;
884 newState( mProgress, i18n("Retrieving subfolders"));
885 listDirectory2();
886 break;
888 case SYNC_STATE_DELETE_SUBFOLDERS:
889 mSyncState = SYNC_STATE_LIST_MESSAGES;
890 if ( !foldersForDeletionOnServer.isEmpty() ) {
891 newState( mProgress, i18n("Deleting folders from server"));
892 CachedImapJob *job = new CachedImapJob( foldersForDeletionOnServer,
893 CachedImapJob::tDeleteFolders, this );
894 connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
895 connect( job, SIGNAL( finished() ), this, SLOT( slotFolderDeletionOnServerFinished() ) );
896 job->start();
897 break;
899 // Not needed, the next step emits newState very quick
900 //newState( mProgress, i18n("No folders to delete from server"));
901 // Carry on
903 case SYNC_STATE_LIST_MESSAGES:
904 mSyncState = SYNC_STATE_DELETE_MESSAGES;
905 if ( !noContent() ) {
906 newState( mProgress, i18n("Retrieving message list"));
907 listMessages();
908 break;
910 // Else carry on
912 case SYNC_STATE_DELETE_MESSAGES:
913 mSyncState = SYNC_STATE_EXPUNGE_MESSAGES;
914 if ( !noContent() ) {
915 if ( deleteMessages() ) {
916 // Fine, we will continue with the next state
917 } else {
918 // No messages to delete, skip to GET_MESSAGES
919 newState( mProgress, i18n("No messages to delete..."));
920 mSyncState = SYNC_STATE_GET_MESSAGES;
921 serverSyncInternal();
923 break;
925 // Else carry on
927 case SYNC_STATE_EXPUNGE_MESSAGES:
928 mSyncState = SYNC_STATE_GET_MESSAGES;
929 if ( !noContent() ) {
930 newState( mProgress, i18n("Expunging deleted messages"));
931 CachedImapJob *job = new CachedImapJob( QString(),
932 CachedImapJob::tExpungeFolder, this );
933 connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
934 connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
935 job->start();
936 break;
938 // Else carry on
940 case SYNC_STATE_GET_MESSAGES:
941 mSyncState = SYNC_STATE_HANDLE_INBOX;
942 if ( !noContent() ) {
943 if ( !mMsgsForDownload.isEmpty() ) {
944 newState( mProgress, i18n("Retrieving new messages"));
945 CachedImapJob *job = new CachedImapJob( mMsgsForDownload,
946 CachedImapJob::tGetMessage,
947 this );
948 connect( job, SIGNAL( progress( unsigned long, unsigned long ) ),
949 this, SLOT( slotProgress( unsigned long, unsigned long ) ) );
950 connect( job, SIGNAL( finished() ), this, SLOT( slotUpdateLastUid() ) );
951 connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
952 job->start();
953 mMsgsForDownload.clear();
954 break;
955 } else {
956 newState( mProgress, i18n("No new messages from server"));
958 * There were no messages to download, but it could be that we
959 * uploaded some which we didn't need to download again because we
960 * already knew the uid. Now that we are sure there is nothing to
961 * download, and everything that had to be deleted on the server
962 * has been deleted, adjust our local notion of the highest uid
963 * seen thus far.
965 slotUpdateLastUid();
967 if ( mLastUid == 0 && uidWriteTimer == -1 ) {
968 // This is probably a new and empty folder. Write the UID cache
969 writeUidCache();
974 // Else carry on
976 case SYNC_STATE_HANDLE_INBOX:
977 // Wrap up the 'download emails' stage. We always end up at 95 here.
978 mProgress = 95;
979 mSyncState = SYNC_STATE_TEST_ANNOTATIONS;
981 #define KOLAB_FOLDERTEST "/vendor/kolab/folder-test"
982 case SYNC_STATE_TEST_ANNOTATIONS:
983 mSyncState = SYNC_STATE_GET_ANNOTATIONS;
984 // The first folder with user rights to write annotations
985 if ( !mAccount->annotationCheckPassed() &&
986 ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) &&
987 !imapPath().isEmpty() && imapPath() != "/" ) {
988 kDebug(5006) << "Setting test attribute on folder: "
989 << folder()->prettyUrl() << endl;
990 newState( mProgress, i18n("Checking annotation support"));
992 KUrl url = mAccount->getUrl();
993 url.setPath( imapPath() );
994 KMail::AnnotationList annotations; // to be set
996 KMail::AnnotationAttribute attr( KOLAB_FOLDERTEST, "value.shared", "true" );
997 annotations.append( attr );
999 kDebug(5006) << "Setting test attribute to "<< url << endl;
1000 KIO::Job *job = AnnotationJobs::multiSetAnnotation( mAccount->slave(),
1001 url, annotations );
1002 ImapAccountBase::jobData jd( url.url(), folder() );
1003 jd.cancellable = true; // we can always do so later
1004 mAccount->insertJob( job, jd );
1005 connect( job, SIGNAL( result( KJob * ) ),
1006 SLOT( slotTestAnnotationResult( KJob * ) ) );
1007 break;
1010 case SYNC_STATE_GET_ANNOTATIONS:
1012 #define KOLAB_FOLDERTYPE "/vendor/kolab/folder-type"
1013 #define KOLAB_INCIDENCESFOR "/vendor/kolab/incidences-for"
1014 //#define KOLAB_FOLDERTYPE "/comment" //for testing, while cyrus-imap doesn't support /vendor/*
1015 mSyncState = SYNC_STATE_SET_ANNOTATIONS;
1017 bool needToGetInitialAnnotations = false;
1018 if ( !noContent() ) {
1019 // for a folder we didn't create ourselves: get annotation from server
1020 if ( mAnnotationFolderType == "FROMSERVER" ) {
1021 needToGetInitialAnnotations = true;
1022 mAnnotationFolderType.clear();
1023 } else {
1024 updateAnnotationFolderType();
1029 * First retrieve the annotation, so that we know we have to set it
1030 * if it's not set. On the other hand, if the user changed the
1031 * contentstype, there's no need to get first.
1033 if ( !noContent() && mAccount->hasAnnotationSupport() &&
1034 ( kmkernel->iCalIface().isEnabled() || needToGetInitialAnnotations ) ) {
1035 QStringList annotations; // list of annotations to be fetched
1036 if ( !mAnnotationFolderTypeChanged || mAnnotationFolderType.isEmpty() )
1037 annotations << KOLAB_FOLDERTYPE;
1038 if ( !mIncidencesForChanged )
1039 annotations << KOLAB_INCIDENCESFOR;
1040 if ( !annotations.isEmpty() ) {
1041 newState( mProgress, i18n("Retrieving annotations"));
1042 KUrl url = mAccount->getUrl();
1043 url.setPath( imapPath() );
1044 AnnotationJobs::MultiGetAnnotationJob *job =
1045 AnnotationJobs::multiGetAnnotation( mAccount->slave(), url, annotations );
1046 ImapAccountBase::jobData jd( url.url(), folder() );
1047 jd.cancellable = true;
1048 mAccount->insertJob(job, jd);
1050 connect( job, SIGNAL(annotationResult(const QString&, const QString&, bool)),
1051 SLOT(slotAnnotationResult(const QString&, const QString&, bool)) );
1052 connect( job, SIGNAL(result(KJob *)),
1053 SLOT(slotGetAnnotationResult(KJob *)) );
1054 break;
1057 } // case
1058 case SYNC_STATE_SET_ANNOTATIONS:
1060 mSyncState = SYNC_STATE_SET_ACLS;
1061 if ( !noContent() && mAccount->hasAnnotationSupport() &&
1062 ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) {
1063 newState( mProgress, i18n("Setting annotations"));
1064 KUrl url = mAccount->getUrl();
1065 url.setPath( imapPath() );
1066 KMail::AnnotationList annotations; // to be set
1067 if ( mAnnotationFolderTypeChanged && !mAnnotationFolderType.isEmpty() ) {
1068 KMail::AnnotationAttribute attr( KOLAB_FOLDERTYPE, "value.shared",
1069 mAnnotationFolderType );
1070 annotations.append( attr );
1071 kDebug(5006) << "Setting folder-type annotation for " << label()
1072 << " to " << mAnnotationFolderType << endl;
1074 if ( mIncidencesForChanged ) {
1075 const QString val = incidencesForToString( mIncidencesFor );
1076 KMail::AnnotationAttribute attr( KOLAB_INCIDENCESFOR, "value.shared",
1077 val );
1078 annotations.append( attr );
1079 kDebug(5006) << "Setting incidences-for annotation for " << label()
1080 << " to " << val << endl;
1082 if ( !annotations.isEmpty() ) {
1083 KIO::Job *job =
1084 AnnotationJobs::multiSetAnnotation( mAccount->slave(), url,
1085 annotations );
1086 ImapAccountBase::jobData jd( url.url(), folder() );
1087 jd.cancellable = true; // we can always do so later
1088 mAccount->insertJob(job, jd);
1090 connect(job, SIGNAL(annotationChanged( const QString&, const QString&, const QString& ) ),
1091 SLOT( slotAnnotationChanged( const QString&, const QString&, const QString& ) ));
1092 connect(job, SIGNAL(result(KJob *)),
1093 SLOT(slotSetAnnotationResult(KJob *)));
1094 break;
1098 case SYNC_STATE_SET_ACLS:
1099 mSyncState = SYNC_STATE_GET_ACLS;
1101 if ( !noContent() && mAccount->hasACLSupport() &&
1102 ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) {
1103 bool hasChangedACLs = false;
1104 ACLList::ConstIterator it = mACLList.begin();
1105 for ( ; it != mACLList.end() && !hasChangedACLs; ++it ) {
1106 hasChangedACLs = (*it).changed;
1108 if ( hasChangedACLs ) {
1109 newState( mProgress, i18n("Setting permissions"));
1110 KUrl url = mAccount->getUrl();
1111 url.setPath( imapPath() );
1112 KIO::Job *job = KMail::ACLJobs::multiSetACL( mAccount->slave(), url,
1113 mACLList );
1114 ImapAccountBase::jobData jd( url.url(), folder() );
1115 mAccount->insertJob(job, jd);
1117 connect(job, SIGNAL(result(KJob *)),
1118 SLOT(slotMultiSetACLResult(KJob *)));
1119 connect(job, SIGNAL(aclChanged( const QString&, int )),
1120 SLOT(slotACLChanged( const QString&, int )) );
1121 break;
1125 case SYNC_STATE_GET_ACLS:
1126 mSyncState = SYNC_STATE_GET_QUOTA;
1128 if ( !noContent() && mAccount->hasACLSupport() ) {
1129 newState( mProgress, i18n( "Retrieving permissions" ) );
1130 mAccount->getACL( folder(), mImapPath );
1131 connect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
1132 this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
1133 break;
1135 case SYNC_STATE_GET_QUOTA:
1136 // Continue with the subfolders
1137 mSyncState = SYNC_STATE_FIND_SUBFOLDERS;
1138 if ( !noContent() && mAccount->hasQuotaSupport() ) {
1139 newState( mProgress, i18n("Getting quota information"));
1140 KUrl url = mAccount->getUrl();
1141 url.setPath( imapPath() );
1142 KIO::Job *job = KMail::QuotaJobs::getStorageQuota( mAccount->slave(), url );
1143 ImapAccountBase::jobData jd( url.url(), folder() );
1144 mAccount->insertJob(job, jd);
1145 connect( job, SIGNAL( storageQuotaResult( const QuotaInfo& ) ),
1146 SLOT( slotStorageQuotaResult( const QuotaInfo& ) ) );
1147 connect( job, SIGNAL(result(KIO::Job *)),
1148 SLOT(slotQuotaResult(KIO::Job *)) );
1149 break;
1151 case SYNC_STATE_FIND_SUBFOLDERS:
1153 mProgress = 98;
1154 newState( mProgress, i18n("Updating cache file"));
1156 mSyncState = SYNC_STATE_SYNC_SUBFOLDERS;
1157 mSubfoldersForSync.clear();
1158 mCurrentSubfolder = 0;
1159 if ( folder() && folder()->child() ) {
1160 QList<KMFolderNode*>::const_iterator it;
1161 for ( it = folder()->child()->begin();
1162 it != folder()->child()->end(); ++it )
1164 KMFolderNode *node = *it;
1165 if ( !node->isDir() ) {
1166 KMFolderCachedImap *storage =
1167 static_cast<KMFolderCachedImap*>( static_cast<KMFolder*>( node )->storage() );
1168 // Only sync folders that have been accepted by the server
1169 if ( !storage->imapPath().isEmpty() &&
1170 // and that were not just deleted from it
1171 !foldersForDeletionOnServer.contains( storage->imapPath() ) ) {
1172 mSubfoldersForSync << storage;
1173 } else {
1174 kDebug(5006) << "Do not add " << storage->label()
1175 << " to synclist" << endl;
1181 // All done for this folder.
1182 mProgress = 100; // all done
1183 newState( mProgress, i18n("Synchronization done"));
1184 KUrl url = mAccount->getUrl();
1185 url.setPath( imapPath() );
1186 kmkernel->iCalIface().folderSynced( folder(), url );
1189 if ( !mRecurse ) // "check mail for this folder" only
1190 mSubfoldersForSync.clear();
1192 // Carry on
1193 case SYNC_STATE_SYNC_SUBFOLDERS:
1195 if ( mCurrentSubfolder ) {
1196 disconnect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
1197 this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
1198 mCurrentSubfolder = 0;
1201 if ( mSubfoldersForSync.isEmpty() ) {
1202 mSyncState = SYNC_STATE_INITIAL;
1203 mAccount->addUnreadMsgCount( this, countUnread() ); // before closing
1204 close( "cachedimap" );
1205 emit folderComplete( this, true );
1206 } else {
1207 mCurrentSubfolder = mSubfoldersForSync.front();
1208 mSubfoldersForSync.pop_front();
1209 connect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
1210 this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
1212 assert( !mCurrentSubfolder->imapPath().isEmpty() );
1213 mCurrentSubfolder->setAccount( account() );
1214 bool recurse = mCurrentSubfolder->noChildren() ? false : true;
1215 mCurrentSubfolder->serverSync( recurse );
1218 break;
1220 default:
1221 kDebug(5006) << "KMFolderCachedImap::serverSyncInternal() WARNING: no such state "
1222 << int(mSyncState) << endl;
1226 void KMFolderCachedImap::slotConnectionResult( int errorCode, const QString &errorMsg )
1228 disconnect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
1229 this, SLOT( slotConnectionResult(int, const QString&) ) );
1230 if ( !errorCode ) {
1231 // Success
1232 mSyncState = SYNC_STATE_GET_USERRIGHTS;
1233 mProgress += 5;
1234 serverSyncInternal();
1235 } else {
1236 // Error (error message already shown by the account)
1237 newState( mProgress, KIO::buildErrorString( errorCode, errorMsg ) );
1238 emit folderComplete( this, false );
1242 /* find new messages (messages without a UID) */
1243 QList<unsigned long> KMFolderCachedImap::findNewMessages()
1245 QList<unsigned long> result;
1246 for ( int i = 0; i < count(); ++i ) {
1247 KMMsgBase *msg = getMsgBase( i );
1248 if ( !msg ) { // what goes on if getMsg() returns 0?
1249 continue;
1251 if ( msg->UID() == 0 ) {
1252 result.append( msg->getMsgSerNum() );
1255 return result;
1258 /* Upload new messages to server */
1259 void KMFolderCachedImap::uploadNewMessages()
1261 QList<unsigned long> newMsgs = findNewMessages();
1262 if ( !newMsgs.isEmpty() ) {
1263 if ( mUserRights <= 0 || ( mUserRights & ( KMail::ACLJobs::Insert ) ) ) {
1264 newState( mProgress, i18n("Uploading messages to server"));
1265 CachedImapJob *job = new CachedImapJob( newMsgs, CachedImapJob::tPutMessage, this );
1266 connect( job, SIGNAL( progress( unsigned long, unsigned long ) ),
1267 this, SLOT( slotPutProgress( unsigned long, unsigned long ) ) );
1268 connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
1269 job->start();
1270 return;
1271 } else {
1272 KMCommand *command = rescueUnsyncedMessages();
1273 connect( command, SIGNAL( completed( KMCommand * ) ),
1274 this, SLOT( serverSyncInternal() ) );
1276 } else { // nothing to upload
1277 if ( mUserRights != mOldUserRights &&
1278 ( mOldUserRights & KMail::ACLJobs::Insert ) &&
1279 !( mUserRights & KMail::ACLJobs::Insert ) ) {
1280 // write access revoked
1281 KMessageBox::information(
1283 i18n("<p>Your access rights to folder <b>%1</b> have been restricted, "
1284 "it will no longer be possible to add messages to this folder.</p>",
1285 folder()->prettyUrl() ),
1286 i18n("Acces rights revoked"), "KMailACLRevocationNotification" );
1289 newState( mProgress, i18n("No messages to upload to server"));
1290 serverSyncInternal();
1293 /* Progress info during uploadNewMessages */
1294 void KMFolderCachedImap::slotPutProgress( unsigned long done, unsigned long total )
1296 // (going from mProgress to mProgress+10)
1297 int progressSpan = 10;
1298 newState( mProgress + ( progressSpan * done ) / total, QString() );
1299 if ( done == total ) { // we're done
1300 mProgress += progressSpan;
1304 /* Upload message flags to server */
1305 void KMFolderCachedImap::uploadFlags()
1307 if ( !uidMap.isEmpty() ) {
1308 mStatusFlagsJobs = 0;
1309 newState( mProgress, i18n("Uploading status of messages to server"));
1311 // FIXME DUPLICATED FROM KMFOLDERIMAP
1312 QMap< QString, QStringList > groups;
1313 //open(); //already done
1314 for ( int i = 0; i < count(); ++i ) {
1315 KMMsgBase *msg = getMsgBase( i );
1316 if ( !msg || msg->UID() == 0 ) {
1317 // Either not a valid message or not one that is on the server yet
1318 continue;
1321 QString flags = KMFolderImap::statusToFlags( msg->status() );
1322 // Collect uids for each typem of flags.
1323 QString uid;
1324 uid.setNum( msg->UID() );
1325 groups[flags].append( uid );
1327 QMap< QString, QStringList >::Iterator dit;
1328 for ( dit = groups.begin(); dit != groups.end(); ++dit ) {
1329 QByteArray flags = dit.key().toLatin1();
1330 QStringList sets = KMFolderImap::makeSets( (*dit), true );
1331 mStatusFlagsJobs += sets.count(); // ### that's not in kmfolderimap....
1332 // Send off a status setting job for each set.
1333 for ( QStringList::Iterator slit = sets.begin(); slit != sets.end(); ++slit ) {
1334 QString imappath = imapPath() + ";UID=" + ( *slit );
1335 mAccount->setImapStatus( folder(), imappath, flags );
1338 // FIXME END DUPLICATED FROM KMFOLDERIMAP
1340 if ( mStatusFlagsJobs ) {
1341 connect( mAccount, SIGNAL( imapStatusChanged( KMFolder*, const QString&, bool ) ),
1342 this, SLOT( slotImapStatusChanged( KMFolder*, const QString&, bool ) ) );
1343 return;
1346 newState( mProgress, i18n("No messages to upload to server") );
1347 serverSyncInternal();
1350 void KMFolderCachedImap::slotImapStatusChanged( KMFolder *folder, const QString&, bool cont )
1352 if ( mSyncState == SYNC_STATE_INITIAL ) {
1353 kDebug(5006) << "IMAP status changed but reset " << endl;
1354 return; // we were reset
1356 if ( folder->storage() == this ) {
1357 --mStatusFlagsJobs;
1358 if ( mStatusFlagsJobs == 0 || !cont ) { // done or aborting
1359 disconnect( mAccount, SIGNAL( imapStatusChanged( KMFolder*, const QString&, bool ) ),
1360 this, SLOT( slotImapStatusChanged( KMFolder*, const QString&, bool ) ) );
1362 if ( mStatusFlagsJobs == 0 && cont ) {
1363 mProgress += 5;
1364 serverSyncInternal();
1369 // This is not perfect, what if the status didn't really change? Oh well ...
1370 void KMFolderCachedImap::setStatus( int idx, const MessageStatus &status, bool toggle )
1372 KMFolderMaildir::setStatus( idx, status, toggle );
1373 mStatusChangedLocally = true;
1376 void KMFolderCachedImap::setStatus( QList<int> &ids, const MessageStatus &status, bool toggle )
1378 KMFolderMaildir::setStatus( ids, status, toggle );
1379 mStatusChangedLocally = true;
1382 /* Upload new folders to server */
1383 void KMFolderCachedImap::createNewFolders()
1385 QList<KMFolderCachedImap*> newFolders = findNewFolders();
1386 if ( !newFolders.isEmpty() ) {
1387 newState( mProgress, i18n("Creating subfolders on server"));
1388 CachedImapJob *job = new CachedImapJob( newFolders, CachedImapJob::tAddSubfolders, this );
1389 connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
1390 connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
1391 job->start();
1392 } else {
1393 serverSyncInternal();
1397 QList<KMFolderCachedImap*> KMFolderCachedImap::findNewFolders()
1399 QList<KMFolderCachedImap*> newFolders;
1400 if ( folder() && folder()->child() ) {
1401 QList<KMFolderNode*>::const_iterator it;
1402 for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) {
1403 KMFolderNode *node = *it;
1404 if ( !node->isDir() ) {
1405 if ( static_cast<KMFolder*>(node)->folderType() != KMFolderTypeCachedImap ) {
1406 kError(5006) << "KMFolderCachedImap::findNewFolders(): ARGH!!! "
1407 << node->name() << " is not an IMAP folder\n";
1408 assert( 0 );
1410 KMFolderCachedImap *folder =
1411 static_cast<KMFolderCachedImap*>( static_cast<KMFolder*>( node )->storage() );
1412 if ( folder->imapPath().isEmpty() ) {
1413 newFolders << folder;
1418 return newFolders;
1421 bool KMFolderCachedImap::deleteMessages()
1423 if ( mUserRights > 0 && !( mUserRights & KMail::ACLJobs::Delete ) ) {
1424 return false;
1427 /* Delete messages from cache that are gone from the server */
1428 QList<KMMessage*> msgsForDeletion;
1431 * It is not possible to just go over all indices and remove
1432 * them one by one because the index list can get resized under
1433 * us. So use msg pointers instead.
1435 QMap<ulong,int>::const_iterator it = uidMap.constBegin();
1436 for ( ; it != uidMap.constEnd(); it++ ) {
1437 ulong uid ( it.key() );
1438 if ( uid != 0 && !uidsOnServer.find( uid ) ) {
1439 msgsForDeletion.append( getMsg( *it ) );
1443 if ( !msgsForDeletion.isEmpty() ) {
1444 removeMsg( msgsForDeletion );
1447 /* Delete messages from the server that we don't have anymore */
1448 if ( !uidsForDeletionOnServer.isEmpty() ) {
1449 newState( mProgress, i18n("Deleting removed messages from server"));
1450 QStringList sets = KMFolderImap::makeSets( uidsForDeletionOnServer, true );
1451 uidsForDeletionOnServer.clear();
1452 kDebug(5006) << "Deleting " << sets.count()
1453 << " sets of messages from server folder " << imapPath() << endl;
1454 CachedImapJob *job =
1455 new CachedImapJob( sets, CachedImapJob::tDeleteMessage, this );
1456 connect( job, SIGNAL( result( KMail::FolderJob * ) ),
1457 this, SLOT( slotDeleteMessagesResult( KMail::FolderJob * ) ) );
1458 job->start();
1459 return true;
1460 } else {
1461 return false;
1465 void KMFolderCachedImap::slotDeleteMessagesResult( KMail::FolderJob *job )
1467 if ( job->error() ) {
1468 // Skip the EXPUNGE state if deleting didn't work, no need to show two error messages
1469 mSyncState = SYNC_STATE_GET_MESSAGES;
1471 mProgress += 10;
1472 serverSyncInternal();
1475 void KMFolderCachedImap::checkUidValidity()
1478 * IMAP root folders don't seem to have a UID validity setting.
1479 * Also, don't try the uid validity on new folders.
1481 if ( imapPath().isEmpty() || imapPath() == "/" ) {
1482 // Just proceed
1483 serverSyncInternal();
1484 } else {
1485 newState( mProgress, i18n("Checking folder validity"));
1486 CachedImapJob *job = new CachedImapJob( FolderJob::tCheckUidValidity, this );
1487 connect( job, SIGNAL( result( KMail::FolderJob* ) ),
1488 this, SLOT( slotCheckUidValidityResult( KMail::FolderJob* ) ) );
1489 job->start();
1493 void KMFolderCachedImap::slotCheckUidValidityResult( KMail::FolderJob *job )
1495 if ( job->error() ) { // there was an error and the user chose "continue"
1497 * We can't continue doing anything in the same folder though,
1498 * it would delete all mails. But we can continue to subfolders
1499 * if any. Well we can also try annotation/acl stuff...
1501 mSyncState = SYNC_STATE_HANDLE_INBOX;
1503 mProgress += 5;
1504 serverSyncInternal();
1507 void KMFolderCachedImap::listMessages()
1509 bool groupwareOnly =
1510 GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount() &&
1511 GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id() &&
1512 folder()->isSystemFolder() && mImapPath == "/INBOX/";
1515 * Don't list messages on the root folder, and skip the inbox, if this is
1516 * the inbox of a groupware-only dimap account.
1518 if ( imapPath() == "/" || groupwareOnly ) {
1519 serverSyncInternal();
1520 return;
1523 if ( !mAccount->slave() ) { // sync aborted
1524 resetSyncState();
1525 emit folderComplete( this, false );
1526 return;
1528 uidsOnServer.clear();
1529 uidsOnServer.resize( count() * 2 );
1530 uidsForDeletionOnServer.clear();
1531 mMsgsForDownload.clear();
1532 mUidsForDownload.clear();
1534 CachedImapJob *job = new CachedImapJob( FolderJob::tListMessages, this );
1535 connect( job, SIGNAL( result( KMail::FolderJob * ) ),
1536 this, SLOT( slotGetLastMessagesResult( KMail::FolderJob * ) ) );
1537 job->start();
1540 void KMFolderCachedImap::slotGetLastMessagesResult( KMail::FolderJob *job )
1542 getMessagesResult( job, true );
1545 // Connected to the listMessages job in CachedImapJob
1546 void KMFolderCachedImap::slotGetMessagesData( KIO::Job *job, const QByteArray &data )
1548 KMAcctCachedImap::JobIterator it = mAccount->findJob( job );
1549 if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
1550 kDebug(5006) << "could not find job!?!?!" << endl;
1552 * Be sure to reset the sync state, if the listing was partial we
1553 * would otherwise delete not-listed mail locally, and on the next
1554 * sync on the server as well.
1556 mSyncState = SYNC_STATE_HANDLE_INBOX;
1557 serverSyncInternal(); /* HACK^W Fix: we should at least try to keep going */
1558 return;
1560 (*it).cdata += data;
1561 int pos = (*it).cdata.indexOf("\r\n--IMAPDIGEST");
1562 if ( pos > 0 ) {
1563 int a = (*it).cdata.indexOf("\r\nX-uidValidity:");
1564 if (a != -1) {
1565 int b = (*it).cdata.indexOf("\r\n", a + 17);
1566 setUidValidity((*it).cdata.mid(a + 17, b - a - 17));
1568 a = (*it).cdata.indexOf("\r\nX-Access:");
1571 * Only trust X-Access (i.e. the imap select info) if we don't know
1572 * mUserRights. The latter is more accurate (checked on every sync)
1573 * whereas X-Access is only updated when selecting the folder again,
1574 * which might not happen if using RMB / Check Mail in this folder.
1575 * We don't need two (potentially conflicting) sources for the
1576 * readonly setting, in any case.
1578 if ( a != -1 && mUserRights == -1 ) {
1579 int b = (*it).cdata.indexOf( "\r\n", a + 12 );
1580 const QString access = (*it).cdata.mid( a + 12, b - a - 12 );
1581 setReadOnly( access == "Read only" );
1583 (*it).cdata.remove( 0, pos );
1585 pos = (*it).cdata.indexOf( "\r\n--IMAPDIGEST", 1 );
1587 // Start with something largish when rebuilding the cache
1588 if ( uidsOnServer.size() == 0 ) {
1589 uidsOnServer.resize( KMail::nextPrime( 2000 ) );
1592 int flags;
1593 const int v = 42;
1594 while ( pos >= 0 ) {
1595 KMMessage msg;
1596 msg.fromString( (*it).cdata.mid( 16, pos - 16 ) );
1597 flags = msg.headerField( "X-Flags" ).toInt();
1598 bool deleted = ( flags & 8 );
1599 ulong uid = msg.UID();
1600 if ( !deleted ) {
1601 if ( uid != 0 ) {
1602 if ( uidsOnServer.count() == uidsOnServer.size() ) {
1603 uidsOnServer.resize( KMail::nextPrime( uidsOnServer.size() * 2 ) );
1604 kDebug( 5006 ) << "Resizing to: " << uidsOnServer.size() << endl;
1606 uidsOnServer.insert( uid, &v );
1608 bool redownload = false;
1609 if ( uid <= lastUid() ) {
1611 * If this message UID is not present locally, then it must
1612 * have been deleted by the user, so we delete it on the
1613 * server also. If we don't have delete permissions on the server,
1614 * re-download the message, it must have vanished by some error, or
1615 * while we still thought we were allowed to delete (ACL change).
1617 * This relies heavily on lastUid() being correct at all times.
1619 KMMsgBase *existingMessage = findByUID( uid );
1620 if ( !existingMessage ) {
1621 if ( mUserRights <= 0 || ( mUserRights & KMail::ACLJobs::Delete ) ) {
1622 uidsForDeletionOnServer << uid;
1623 } else {
1624 redownload = true;
1626 } else {
1628 * If this is a read only folder, ignore status updates from the
1629 * server since we can't write our status back our local version
1630 * is what has to be considered correct.
1632 if ( !mReadOnly ) {
1633 /* The message is OK, update flags */
1634 KMFolderImap::flagsToStatus( existingMessage, flags );
1638 if ( uid > lastUid() || redownload ) {
1640 * The message is new since the last sync, but we might have
1641 * just uploaded it, in which case the uid map already contains it.
1643 if ( !uidMap.contains( uid ) ) {
1644 ulong size = msg.headerField("X-Length").toULong();
1645 mMsgsForDownload << KMail::CachedImapJob::MsgForDownload( uid, flags, size );
1646 if ( imapPath() == "/INBOX/" ) {
1647 mUidsForDownload << uid;
1650 // Remember the highest uid and once the download is completed, update mLastUid
1651 if ( uid > mTentativeHighestUid ) {
1652 mTentativeHighestUid = uid;
1656 (*it).cdata.remove( 0, pos );
1657 (*it).done++;
1658 pos = (*it).cdata.indexOf( "\r\n--IMAPDIGEST", 1 );
1662 void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet )
1664 mProgress += 10;
1665 if ( job->error() ) { // error listing messages but the user chose to continue
1666 mContentState = imapNoInformation;
1667 mSyncState = SYNC_STATE_HANDLE_INBOX; // be sure not to continue in this folder
1668 } else {
1669 if ( lastSet ) { // always true here (this comes from online-imap...)
1670 mContentState = imapFinished;
1671 mStatusChangedLocally = false; // we are up to date again
1674 serverSyncInternal();
1677 void KMFolderCachedImap::slotProgress( unsigned long done, unsigned long total )
1679 int progressSpan = 100 - 5 - mProgress;
1681 // Progress info while retrieving new emails
1682 // (going from mProgress to mProgress+progressSpan)
1683 newState( mProgress + ( progressSpan * done ) / total, QString() );
1686 void KMFolderCachedImap::setAccount( KMAcctCachedImap *aAccount )
1688 assert( ::qobject_cast<KMAcctCachedImap *>( aAccount ) );
1689 mAccount = aAccount;
1690 if ( imapPath() == "/" ) {
1691 aAccount->setFolder( folder() );
1694 // Folder was renamed in a previous session, and the user didn't sync yet
1695 QString newName = mAccount->renamedFolder( imapPath() );
1696 if ( !newName.isEmpty() ) {
1697 folder()->setLabel( newName );
1700 if ( !folder() || !folder()->child() || !folder()->child()->count() ) {
1701 return;
1704 QList<KMFolderNode*>::const_iterator it;
1705 for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) {
1706 KMFolderNode *node = *it;
1707 if ( !node->isDir() ) {
1708 static_cast<KMFolderCachedImap*>(
1709 static_cast<KMFolder*>( node )->storage() )->setAccount( aAccount );
1714 void KMFolderCachedImap::listNamespaces()
1716 ImapAccountBase::ListType type = ImapAccountBase::List;
1717 if ( mAccount->onlySubscribedFolders() ) {
1718 type = ImapAccountBase::ListSubscribed;
1721 kDebug(5006) << "listNamespaces " << mNamespacesToList << endl;
1722 if ( mNamespacesToList.isEmpty() ) {
1723 mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
1724 mPersonalNamespacesCheckDone = true;
1726 QStringList ns = mAccount->namespaces()[ImapAccountBase::OtherUsersNS];
1727 ns += mAccount->namespaces()[ImapAccountBase::SharedNS];
1728 mNamespacesToCheck = ns.count();
1729 for ( QStringList::Iterator it = ns.begin(); it != ns.end(); ++it ) {
1730 if ( (*it).isEmpty() ) {
1731 // ignore empty listings as they have been listed before
1732 --mNamespacesToCheck;
1733 continue;
1735 KMail::ListJob *job =
1736 new KMail::ListJob( mAccount, type, this, mAccount->addPathToNamespace( *it ) );
1737 connect( job, SIGNAL( receivedFolders( const QStringList&, const QStringList&,
1738 const QStringList&, const QStringList&,
1739 const ImapAccountBase::jobData& ) ),
1740 this, SLOT( slotCheckNamespace( const QStringList&, const QStringList&,
1741 const QStringList&, const QStringList&,
1742 const ImapAccountBase::jobData& ) ) );
1743 job->start();
1745 if ( mNamespacesToCheck == 0 ) {
1746 serverSyncInternal();
1748 return;
1750 mPersonalNamespacesCheckDone = false;
1752 QString ns = mNamespacesToList.front();
1753 mNamespacesToList.pop_front();
1755 mSyncState = SYNC_STATE_LIST_SUBFOLDERS2;
1756 newState( mProgress, i18n("Retrieving folders for namespace %1", ns) );
1757 KMail::ListJob *job = new KMail::ListJob( mAccount, type, this,
1758 mAccount->addPathToNamespace( ns ) );
1759 job->setNamespace( ns );
1760 connect( job, SIGNAL( receivedFolders( const QStringList&, const QStringList&,
1761 const QStringList&, const QStringList&,
1762 const ImapAccountBase::jobData& ) ),
1763 this, SLOT( slotListResult( const QStringList&, const QStringList&,
1764 const QStringList&, const QStringList&,
1765 const ImapAccountBase::jobData& ) ) );
1766 job->start();
1769 void KMFolderCachedImap::slotCheckNamespace( const QStringList &subfolderNames,
1770 const QStringList &subfolderPaths,
1771 const QStringList &subfolderMimeTypes,
1772 const QStringList &subfolderAttributes,
1773 const ImapAccountBase::jobData &jobData )
1775 Q_UNUSED( subfolderPaths );
1776 Q_UNUSED( subfolderMimeTypes );
1777 Q_UNUSED( subfolderAttributes );
1778 --mNamespacesToCheck;
1779 kDebug(5006) << "slotCheckNamespace " << subfolderNames << ",remain=" <<
1780 mNamespacesToCheck << endl;
1782 // get a correct foldername:
1783 // strip / and make sure it does not contain the delimiter
1784 QString name = jobData.path.mid( 1, jobData.path.length() - 2 );
1785 name.remove( mAccount->delimiterForNamespace( name ) );
1786 if ( name.isEmpty() ) {
1787 // should not happen
1788 kWarning(5006) << "slotCheckNamespace: ignoring empty folder!" << endl;
1789 return;
1792 folder()->createChildFolder();
1793 QList<KMFolderNode*>::const_iterator it;
1794 KMFolderNode *node = 0;
1795 for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) {
1796 if ( !(*it)->isDir() && (*it)->name() == name ) {
1797 node = *it;
1798 break;
1801 if ( !subfolderNames.isEmpty() ) {
1802 if ( node ) {
1803 // folder exists so we have nothing to do - it will be listed later
1804 kDebug(5006) << "found namespace folder " << name << endl;
1805 } else {
1806 // create folder
1807 kDebug(5006) << "create namespace folder " << name << endl;
1808 KMFolder *newFolder = folder()->child()->createFolder( name, false,
1809 KMFolderTypeCachedImap );
1810 if ( newFolder ) {
1811 KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>( newFolder->storage() );
1812 f->setImapPath( mAccount->addPathToNamespace( name ) );
1813 f->setNoContent( true );
1814 f->setAccount( mAccount );
1815 f->close( "cachedimap" );
1816 kmkernel->dimapFolderMgr()->contentsChanged();
1819 } else {
1820 if ( node ) {
1821 kDebug(5006) << "delete namespace folder " << name << endl;
1822 KMFolder *fld = static_cast<KMFolder*>( node );
1823 kmkernel->dimapFolderMgr()->remove( fld );
1827 if ( mNamespacesToCheck == 0 ) {
1828 // all namespaces are done so continue with the next step
1829 serverSyncInternal();
1833 bool KMFolderCachedImap::listDirectory()
1835 if ( !mAccount->slave() ) { // sync aborted
1836 resetSyncState();
1837 emit folderComplete( this, false );
1838 return false;
1840 mSubfolderState = imapInProgress;
1842 // get the folders
1843 ImapAccountBase::ListType type = ImapAccountBase::List;
1844 if ( mAccount->onlySubscribedFolders() ) {
1845 type = ImapAccountBase::ListSubscribed;
1847 KMail::ListJob *job = new KMail::ListJob( mAccount, type, this );
1848 job->setHonorLocalSubscription( true );
1849 connect( job, SIGNAL( receivedFolders( const QStringList&, const QStringList&,
1850 const QStringList&, const QStringList&,
1851 const ImapAccountBase::jobData& ) ),
1852 this, SLOT( slotListResult( const QStringList&, const QStringList&,
1853 const QStringList&, const QStringList&,
1854 const ImapAccountBase::jobData& ) ) );
1855 job->start();
1857 return true;
1860 void KMFolderCachedImap::slotListResult( const QStringList &folderNames,
1861 const QStringList &folderPaths,
1862 const QStringList &folderMimeTypes,
1863 const QStringList &folderAttributes,
1864 const ImapAccountBase::jobData &jobData )
1866 Q_UNUSED( jobData );
1868 mSubfolderNames = folderNames;
1869 mSubfolderPaths = folderPaths;
1870 mSubfolderMimeTypes = folderMimeTypes;
1871 mSubfolderAttributes = folderAttributes;
1873 mSubfolderState = imapFinished;
1875 folder()->createChildFolder();
1876 bool root = ( this == mAccount->rootFolder() );
1878 QList<KMFolder*> toRemove;
1879 bool emptyList = ( root && mSubfolderNames.empty() );
1880 if ( !emptyList ) {
1881 QList<KMFolderNode*>::const_iterator it;
1882 for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) {
1883 KMFolderNode *node = *it;
1884 if (!node->isDir() ) {
1885 KMFolderCachedImap *f =
1886 static_cast<KMFolderCachedImap*>( static_cast<KMFolder*>( node )->storage() );
1888 if ( !mSubfolderNames.contains(node->name()) ) {
1889 QString name = node->name();
1890 // as more than one namespace can be listed in the root folder we need to make sure
1891 // that the folder is within the current namespace
1892 bool isInNamespace = ( jobData.curNamespace.isEmpty() ||
1893 jobData.curNamespace == mAccount->namespaceForFolder( f ) );
1894 // ignore some cases
1895 bool ignore = root && ( f->imapPath() == "/INBOX/" ||
1896 mAccount->isNamespaceFolder( name ) || !isInNamespace );
1898 // This subfolder isn't present on the server
1899 if ( !f->imapPath().isEmpty() && !ignore ) {
1900 // The folder has an imap path set, so it has been
1901 // on the server before. Delete it locally.
1902 toRemove.append( f->folder() );
1903 kDebug(5006) << node->name()
1904 << " isn't on the server. It has an imapPath -> delete it locally" << endl;
1906 } else { // folder both local and on server
1907 //kDebug(5006) << node->name() << " is on the server." << endl;
1909 } else {
1910 //kDebug(5006) << "skipping dir node:" << node->name() << endl;
1915 QList<KMFolder*>::const_iterator jt;
1916 for ( jt = toRemove.constBegin(); jt != toRemove.constEnd(); ++jt ) {
1917 if ( *jt ) {
1918 rescueUnsyncedMessagesAndDeleteFolder( *jt );
1922 mProgress += 5;
1923 if ( mToBeDeletedAfterRescue.isEmpty() )
1924 serverSyncInternal();
1927 void KMFolderCachedImap::listDirectory2()
1929 QString path = folder()->path();
1930 kmkernel->dimapFolderMgr()->quiet( true );
1932 bool root = ( this == mAccount->rootFolder() );
1933 if ( root && !mAccount->hasInbox() ) {
1934 KMFolderCachedImap *f = 0;
1935 KMFolderNode *node = 0;
1936 // create the INBOX
1937 QList<KMFolderNode*>::const_iterator it = folder()->child()->begin();
1938 for ( ; it != folder()->child()->end(); ++it ) {
1939 if ( !(*it)->isDir() && (*it)->name() == "INBOX" ) {
1940 node = *it;
1941 break;
1944 if ( node ) {
1945 f = static_cast<KMFolderCachedImap*>( static_cast<KMFolder*>( node )->storage() );
1946 } else {
1947 KMFolder *newFolder =
1948 folder()->child()->createFolder( "INBOX", true, KMFolderTypeCachedImap );
1949 if ( newFolder ) {
1950 f = static_cast<KMFolderCachedImap*>( newFolder->storage() );
1953 if ( f ) {
1954 f->setAccount( mAccount );
1955 f->setImapPath( "/INBOX/" );
1956 f->folder()->setLabel( i18n("inbox") );
1958 if ( !node ) {
1959 if ( f ) {
1960 f->close( "cachedimap" );
1962 kmkernel->dimapFolderMgr()->contentsChanged();
1964 // so we have an INBOX
1965 mAccount->setHasInbox( true );
1968 if ( root && !mSubfolderNames.isEmpty() ) {
1969 KMFolderCachedImap *parent =
1970 findParent( mSubfolderPaths.first(), mSubfolderNames.first() );
1971 if ( parent ) {
1972 kDebug(5006) << "KMFolderCachedImap::listDirectory2 - pass listing to "
1973 << parent->label() << endl;
1974 mSubfolderNames.clear();
1978 // Find all subfolders present on server but not on disk
1979 QVector<int> foldersNewOnServer;
1980 for (int i = 0; i < mSubfolderNames.count(); i++) {
1982 // Find the subdir, if already present
1983 KMFolderCachedImap *f = 0;
1984 KMFolderNode *node = 0;
1985 for ( QList<KMFolderNode*>::ConstIterator it = folder()->child()->begin();
1986 it != folder()->child()->end(); ++it )
1987 if ( !(*it)->isDir() && (*it)->name() == mSubfolderNames[i] ) {
1988 node = *it;
1989 break;
1992 if ( !node ) {
1993 // This folder is not present here
1994 // Either it's new on the server, or we just deleted it.
1995 QString subfolderPath = mSubfolderPaths[i];
1996 // The code used to look at the uidcache to know if it was "just deleted".
1997 // But this breaks with noContent folders and with shared folders.
1998 // So instead we keep a list in the account.
1999 bool locallyDeleted = mAccount->isDeletedFolder( subfolderPath );
2000 // That list is saved/restored across sessions, but to avoid any mistake,
2001 // ask for confirmation if the folder was deleted in a previous session
2002 // (could be that the folder was deleted & recreated meanwhile from another client...)
2003 if ( !locallyDeleted && mAccount->isPreviouslyDeletedFolder( subfolderPath ) ) {
2004 locallyDeleted = KMessageBox::warningYesNo(
2006 i18n("<qt><p>It seems that the folder <b>%1</b> was deleted. "
2007 "Do you want to delete it from the server?</p></qt>",
2008 mSubfolderNames[i] ), QString(),
2009 KStandardGuiItem::del(), KStandardGuiItem::cancel() ) == KMessageBox::Yes;
2012 if ( locallyDeleted ) {
2013 kDebug(5006) << subfolderPath
2014 << " was deleted locally => delete on server." << endl;
2015 // grab all subsubfolders too
2016 foldersForDeletionOnServer += mAccount->deletedFolderPaths( subfolderPath );
2017 } else {
2018 kDebug(5006) << subfolderPath
2019 << " is a new folder on the server => create local cache" << endl;
2020 foldersNewOnServer.append( i );
2022 } else { // Folder found locally
2023 if ( static_cast<KMFolder*>(node)->folderType() == KMFolderTypeCachedImap ) {
2024 f = dynamic_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
2026 if ( f ) {
2027 // kDebug(5006) << "folder("<<f->name()<<")->imapPath()=" << f->imapPath()
2028 // << "\nSetting imapPath " << mSubfolderPaths[i] << endl;
2029 // Write folder settings
2030 f->setAccount( mAccount );
2031 f->setNoContent( mSubfolderMimeTypes[i] == "inode/directory" );
2032 f->setNoChildren( mSubfolderMimeTypes[i] == "message/digest" );
2033 f->setImapPath( mSubfolderPaths[i] );
2038 /* In case we are ignoring non-groupware folders, and this is the groupware
2039 * main account, find out the contents types of folders that have newly
2040 * appeared on the server. Otherwise just create them and finish listing.
2041 * If a folder is already known to be locally unsubscribed, it won't be
2042 * listed at all, on this level, so these are only folders that we are
2043 * seeing for the first time. */
2045 /* Note: We ask the globalsettings, and not the current state of the
2046 * kmkernel->iCalIface().isEnabled(), since that is false during the
2047 * very first sync, where we already want to filter. */
2048 if ( GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount() &&
2049 GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id() &&
2050 mAccount->hasAnnotationSupport() &&
2051 GlobalSettings::self()->theIMAPResourceEnabled() &&
2052 !foldersNewOnServer.isEmpty() ) {
2054 QStringList paths;
2055 for ( int i = 0; i < foldersNewOnServer.count(); ++i ) {
2056 paths << mSubfolderPaths[ foldersNewOnServer[i] ];
2059 AnnotationJobs::MultiUrlGetAnnotationJob *job =
2060 AnnotationJobs::multiUrlGetAnnotation(
2061 mAccount->slave(), mAccount->getUrl(), paths, KOLAB_FOLDERTYPE );
2062 ImapAccountBase::jobData jd( QString(), folder() );
2063 jd.cancellable = true;
2064 mAccount->insertJob( job, jd );
2065 connect( job, SIGNAL( result( KJob * ) ),
2066 SLOT( slotMultiUrlGetAnnotationResult( KJob * ) ) );
2068 } else {
2069 createFoldersNewOnServerAndFinishListing( foldersNewOnServer );
2073 void KMFolderCachedImap::createFoldersNewOnServerAndFinishListing(
2074 const QVector<int> foldersNewOnServer )
2076 for ( int i = 0; i < foldersNewOnServer.count(); ++i ) {
2077 int idx = foldersNewOnServer[i];
2078 KMFolder *newFolder =
2079 folder()->child()->createFolder( mSubfolderNames[idx], false, KMFolderTypeCachedImap );
2080 if ( newFolder ) {
2081 KMFolderCachedImap *f = dynamic_cast<KMFolderCachedImap*>(newFolder->storage());
2082 kDebug(5006) << " ####### Locally creating folder " << mSubfolderNames[idx] <<endl;
2083 f->close( "cachedimap" );
2084 f->setAccount( mAccount );
2085 f->mAnnotationFolderType = "FROMSERVER";
2086 f->setNoContent( mSubfolderMimeTypes[idx] == "inode/directory" );
2087 f->setNoChildren( mSubfolderMimeTypes[idx] == "message/digest" );
2088 f->setImapPath( mSubfolderPaths[idx] );
2089 kmkernel->dimapFolderMgr()->contentsChanged();
2090 } else {
2091 kDebug(5006) << "can't create folder " << mSubfolderNames[idx] <<endl;
2095 kmkernel->dimapFolderMgr()->quiet( false );
2096 emit listComplete( this );
2098 if ( !mPersonalNamespacesCheckDone ) {
2099 // we're not done with the namespaces
2100 mSyncState = SYNC_STATE_LIST_NAMESPACES;
2102 serverSyncInternal();
2105 //-----------------------------------------------------------------------------
2106 KMFolderCachedImap *KMFolderCachedImap::findParent( const QString &path,
2107 const QString &name )
2109 QString parent = path.left( path.length() - name.length() - 2 );
2111 if ( parent.length() > 1 ) {
2112 // extract name of the parent
2113 parent = parent.right( parent.length() - 1 );
2114 if ( parent != label() ) {
2115 // look for a better parent
2116 QList<KMFolderNode*>::const_iterator it;
2117 for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) {
2118 KMFolderNode *node = *it;
2119 if ( node->name() == parent ) {
2120 KMFolder *fld = static_cast<KMFolder*>( node );
2121 KMFolderCachedImap *imapFld =
2122 static_cast<KMFolderCachedImap*>( fld->storage() );
2123 return imapFld;
2128 return 0;
2131 void KMFolderCachedImap::slotSubFolderComplete( KMFolderCachedImap *sub, bool success )
2133 Q_UNUSED( sub );
2135 if ( success ) {
2136 serverSyncInternal();
2137 } else {
2138 // success == false means the sync was aborted.
2139 if ( mCurrentSubfolder ) {
2140 Q_ASSERT( sub == mCurrentSubfolder );
2141 disconnect( mCurrentSubfolder, SIGNAL( folderComplete( KMFolderCachedImap*, bool ) ),
2142 this, SLOT( slotSubFolderComplete( KMFolderCachedImap*, bool ) ) );
2143 mCurrentSubfolder = 0;
2146 mSubfoldersForSync.clear();
2147 mSyncState = SYNC_STATE_INITIAL;
2148 close( "cachedimap" );
2149 emit folderComplete( this, false );
2153 void KMFolderCachedImap::slotSimpleData( KIO::Job *job, const QByteArray &data )
2155 KMAcctCachedImap::JobIterator it = mAccount->findJob( job );
2156 if ( it == mAccount->jobsEnd() ) {
2157 return;
2160 QBuffer buff( &(*it).data );
2161 buff.open( QIODevice::WriteOnly | QIODevice::Append );
2162 buff.write( data.data(), data.size() );
2163 buff.close();
2166 FolderJob *KMFolderCachedImap::doCreateJob( KMMessage *msg,
2167 FolderJob::JobType jt,
2168 KMFolder *folder,
2169 const QString &partSpecifier,
2170 const AttachmentStrategy* ) const
2172 Q_UNUSED( partSpecifier );
2174 QList<KMMessage*> msgList;
2175 msgList.append( msg );
2177 CachedImapJob *job =
2178 new CachedImapJob(
2179 msgList, jt, folder ? static_cast<KMFolderCachedImap*>( folder->storage() ) : 0 );
2180 job->setParentFolder( this );
2181 return job;
2184 FolderJob *KMFolderCachedImap::doCreateJob( QList<KMMessage*> &msgList,
2185 const QString &sets,
2186 FolderJob::JobType jt,
2187 KMFolder *folder ) const
2189 //FIXME: how to handle sets here?
2190 Q_UNUSED( sets );
2191 CachedImapJob *job =
2192 new CachedImapJob(
2193 msgList, jt, folder ? static_cast<KMFolderCachedImap*>( folder->storage() ) : 0 );
2194 job->setParentFolder( this );
2195 return job;
2198 void KMFolderCachedImap::setUserRights( unsigned int userRights )
2200 mUserRights = userRights;
2201 writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
2204 void KMFolderCachedImap::slotReceivedUserRights( KMFolder *folder )
2206 if ( folder->storage() == this ) {
2207 disconnect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
2208 this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
2209 if ( mUserRights == 0 ) { // didn't work
2210 mUserRights = -1; // error code (used in folderdia)
2211 } else {
2212 setReadOnly( ( mUserRights & KMail::ACLJobs::Insert ) == 0 );
2214 mProgress += 5;
2215 serverSyncInternal();
2219 void KMFolderCachedImap::setReadOnly( bool readOnly )
2221 if ( readOnly != mReadOnly ) {
2222 mReadOnly = readOnly;
2223 emit readOnlyChanged( folder() );
2227 void KMFolderCachedImap::slotReceivedACL( KMFolder *folder,
2228 KIO::Job*, const KMail::ACLList &aclList )
2230 if ( folder->storage() == this ) {
2231 disconnect( mAccount, SIGNAL( receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& ) ),
2232 this, SLOT( slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& ) ) );
2233 mACLList = aclList;
2234 serverSyncInternal();
2238 void KMFolderCachedImap::slotStorageQuotaResult( const QuotaInfo &info )
2240 mQuotaInfo = info;
2241 writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
2244 void KMFolderCachedImap::setACLList( const ACLList &arr )
2246 mACLList = arr;
2249 void KMFolderCachedImap::slotMultiSetACLResult( KJob *job )
2251 KMAcctCachedImap::JobIterator it =
2252 mAccount->findJob( static_cast<KIO::Job*>( job ) );
2254 if ( it == mAccount->jobsEnd() ) {
2255 return; // Shouldn't happen
2257 if ( (*it).parent != folder() ) {
2258 return; // Shouldn't happen
2261 if ( job->error() ) {
2262 // Display error but don't abort the sync just for this
2263 // PENDING(dfaure) reconsider using handleJobError now that it offers continue/cancel
2264 static_cast<KIO::Job*>(job)->ui()->setWindow( 0 );
2265 static_cast<KIO::Job*>(job)->ui()->showErrorMessage();
2266 } else {
2267 kmkernel->iCalIface().addFolderChange( folder(), ACL );
2270 if ( mAccount->slave() ) {
2271 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2273 serverSyncInternal();
2276 void
2277 KMFolderCachedImap::slotACLChanged( const QString &userId, int permissions )
2279 // The job indicates success in changing the permissions for this user
2280 // -> we note that it's been done.
2281 for ( ACLList::Iterator it = mACLList.begin(); it != mACLList.end(); ++it ) {
2282 if ( (*it).userId == userId && (*it).permissions == permissions ) {
2283 if ( permissions == -1 ) { // deleted
2284 mACLList.erase( it );
2285 } else { // added/modified
2286 (*it).changed = false;
2288 return;
2293 // called by KMAcctCachedImap::killAllJobs
2294 void KMFolderCachedImap::resetSyncState()
2296 if ( mSyncState == SYNC_STATE_INITIAL ) {
2297 return;
2300 mSubfoldersForSync.clear();
2301 mSyncState = SYNC_STATE_INITIAL;
2302 close( "cachedimap" );
2304 // Don't use newState here, it would revert to mProgress
2305 // (which is less than the current value when listing messages)
2306 KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem();
2307 QString str = i18n("Aborted");
2308 if ( progressItem ) {
2309 progressItem->setStatus( str );
2311 emit statusMsg( str );
2314 void KMFolderCachedImap::slotIncreaseProgress()
2316 mProgress += 5;
2319 void KMFolderCachedImap::newState( int progress, const QString &syncStatus )
2321 KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem();
2322 if ( progressItem ) {
2323 progressItem->setCompletedItems( progress );
2326 if ( !syncStatus.isEmpty() ) {
2327 QString str;
2328 // For a subfolder, show the label. But for the main folder, it's already shown.
2329 if ( mAccount->imapFolder() == this ) {
2330 str = syncStatus;
2331 } else {
2332 str = QString( "%1: %2" ).arg( label() ).arg( syncStatus );
2334 if ( progressItem ) {
2335 progressItem->setStatus( str );
2337 emit statusMsg( str );
2339 if ( progressItem ) {
2340 progressItem->updateProgress();
2344 void KMFolderCachedImap::setSubfolderState( imapState state )
2346 mSubfolderState = state;
2347 if ( state == imapNoInformation && folder()->child() ) {
2348 // pass through to children
2349 QList<KMFolderNode*>::const_iterator it;
2350 for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) {
2351 KMFolderNode *node = *it;
2352 if ( node->isDir() ) {
2353 continue;
2355 KMFolder *folder = static_cast<KMFolder*>( node );
2356 static_cast<KMFolderCachedImap*>( folder->storage())->setSubfolderState( state );
2361 void KMFolderCachedImap::setImapPath( const QString &path )
2363 mImapPath = path;
2366 // mAnnotationFolderType is the annotation as known to the server (and stored in kmailrc)
2367 // It is updated from the folder contents type and whether it's a standard resource folder.
2368 // This happens during the syncing phase and during initFolder for a new folder.
2369 // Don't do it earlier, e.g. from setContentsType:
2370 // on startup, it's too early there to know if this is a standard resource folder.
2371 void KMFolderCachedImap::updateAnnotationFolderType()
2373 QString oldType = mAnnotationFolderType;
2374 QString oldSubType;
2375 int dot = oldType.indexOf( '.' );
2376 if ( dot != -1 ) {
2377 oldType.truncate( dot );
2378 oldSubType = mAnnotationFolderType.mid( dot + 1 );
2381 QString newType, newSubType;
2382 // We want to store an annotation on the folder only if using the kolab storage.
2383 if ( kmkernel->iCalIface().storageFormat( folder() ) == StorageXML ) {
2384 newType = KMailICalIfaceImpl::annotationForContentsType( mContentsType );
2385 if ( kmkernel->iCalIface().isStandardResourceFolder( folder() ) ) {
2386 newSubType = "default";
2387 } else {
2388 // preserve unknown subtypes, like drafts etc. And preserve ".default" too.
2389 newSubType = oldSubType;
2393 if ( newType != oldType || newSubType != oldSubType ) {
2394 mAnnotationFolderType = newType + ( newSubType.isEmpty() ? QString() : '.'+newSubType );
2395 mAnnotationFolderTypeChanged = true; // force a "set annotation" on next sync
2396 kDebug(5006) << mImapPath << ": updateAnnotationFolderType: '"
2397 << mAnnotationFolderType << "', was (" << oldType
2398 << " " << oldSubType << ") => mAnnotationFolderTypeChanged set to TRUE" << endl;
2400 // Ensure that further readConfig()s don't lose mAnnotationFolderType
2401 writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
2404 void KMFolderCachedImap::setIncidencesFor( IncidencesFor incfor )
2406 if ( mIncidencesFor != incfor ) {
2407 mIncidencesFor = incfor;
2408 mIncidencesForChanged = true;
2412 void KMFolderCachedImap::slotAnnotationResult( const QString &entry,
2413 const QString &value,
2414 bool found )
2416 if ( entry == KOLAB_FOLDERTYPE ) {
2418 * There are four cases.
2419 * 1) no content-type on server -> set it
2420 * 2) different content-type on server, locally changed -> set it
2421 * (we don't even come here)
2422 * 3) different (known) content-type on server, no local change -> get it
2423 * 4) different unknown content-type on server, probably some older
2424 * version -> set it
2426 if ( found ) {
2427 QString type = value;
2428 QString subtype;
2429 int dot = value.indexOf( '.' );
2430 if ( dot != -1 ) {
2431 type.truncate( dot );
2432 subtype = value.mid( dot + 1 );
2434 bool foundKnownType = false;
2435 for ( uint i = 0 ; i <= ContentsTypeLast; ++i ) {
2436 FolderContentsType contentsType = static_cast<KMail::FolderContentsType>( i );
2437 if ( type == KMailICalIfaceImpl::annotationForContentsType( contentsType ) ) {
2438 // Case 3: known content-type on server, get it
2439 if ( contentsType != ContentsTypeMail ) {
2440 kmkernel->iCalIface().setStorageFormat( folder(), StorageXML );
2442 mAnnotationFolderType = value;
2443 if ( folder()->parent()->owner()->idString() != GlobalSettings::self()->theIMAPResourceFolderParent() &&
2444 GlobalSettings::self()->theIMAPResourceEnabled() &&
2445 subtype == "default" ) {
2446 // Truncate subtype if this folder can't be a default resource
2447 // folder for us, although it apparently is for someone else.
2448 mAnnotationFolderType = type;
2449 kDebug(5006) << mImapPath
2450 << ": slotGetAnnotationResult: parent folder is "
2451 << folder()->parent()->owner()->idString()
2452 << " => truncating annotation to " << value << endl;
2454 setContentsType( contentsType );
2455 mAnnotationFolderTypeChanged = false; // we changed it, not the user
2456 foundKnownType = true;
2459 * Users don't read events/contacts/etc. in kmail, so mark them all
2460 * as read. This is done in cachedimapjob when getting new messages,
2461 * but do it here too, for the initial set of messages when we
2462 * didn't know this was a resource folder yet, for old folders, etc.
2464 if ( contentsType != ContentsTypeMail ) {
2465 markUnreadAsRead();
2468 // Ensure that further readConfig()s don't lose mAnnotationFolderType
2469 writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
2470 break;
2473 if ( !foundKnownType && !mReadOnly ) {
2474 // Case 4: server has strange content-type, set it to what we need
2475 mAnnotationFolderTypeChanged = true;
2477 // TODO handle subtype (inbox, drafts, sentitems, junkemail)
2478 } else if ( !mReadOnly ) {
2479 // Case 1: server doesn't have content-type, set it
2480 mAnnotationFolderTypeChanged = true;
2482 } else if ( entry == KOLAB_INCIDENCESFOR ) {
2483 if ( found ) {
2484 mIncidencesFor = incidencesForFromString( value );
2485 Q_ASSERT( mIncidencesForChanged == false );
2490 void KMFolderCachedImap::slotGetAnnotationResult( KJob *job )
2492 KMAcctCachedImap::JobIterator it =
2493 mAccount->findJob( static_cast<KIO::Job*>( job ) );
2495 Q_ASSERT( it != mAccount->jobsEnd() );
2496 if ( it == mAccount->jobsEnd() ) {
2497 return; // Shouldn't happen
2499 Q_ASSERT( (*it).parent == folder() );
2500 if ( (*it).parent != folder() ) {
2501 return; // Shouldn't happen
2504 AnnotationJobs::GetAnnotationJob *annjob =
2505 static_cast<AnnotationJobs::GetAnnotationJob *>( job );
2506 if ( annjob->error() ) {
2507 if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
2508 // that's when the imap server doesn't support annotations
2509 if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML &&
2510 (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() ) {
2511 KMessageBox::error(
2513 i18n( "The IMAP server %1 does not have support for IMAP annotations. "
2514 "The XML storage cannot be used on this server; "
2515 "please re-configure KMail differently.",
2516 mAccount->host() ) );
2518 mAccount->setHasNoAnnotationSupport();
2519 } else {
2520 kWarning(5006) << "slotGetAnnotationResult: " << job->errorString() << endl;
2524 if ( mAccount->slave() ) {
2525 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2527 mProgress += 2;
2528 serverSyncInternal();
2531 void KMFolderCachedImap::slotMultiUrlGetAnnotationResult( KJob *job )
2533 KMAcctCachedImap::JobIterator it = mAccount->findJob( job );
2534 Q_ASSERT( it != mAccount->jobsEnd() );
2535 if ( it == mAccount->jobsEnd() ) {
2536 return; // Shouldn't happen
2538 Q_ASSERT( (*it).parent == folder() );
2539 if ( (*it).parent != folder() ) {
2540 return; // Shouldn't happen
2543 QVector<int> folders;
2544 AnnotationJobs::MultiUrlGetAnnotationJob *annjob =
2545 static_cast<AnnotationJobs::MultiUrlGetAnnotationJob *>( job );
2546 if ( annjob->error() ) {
2547 if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
2548 // that's when the imap server doesn't support annotations
2549 if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML &&
2550 (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() ) {
2551 KMessageBox::error(
2553 i18n( "The IMAP server %1 does not support annotations. "
2554 "The XML storage cannot be used on this server, "
2555 "please re-configure KMail differently",
2556 mAccount->host() ) );
2558 mAccount->setHasNoAnnotationSupport();
2559 } else {
2560 kWarning(5006) << "slotGetMultiUrlAnnotationResult: " << job->errorString() << endl;
2562 } else {
2563 // we got the annotation allright, let's filter out the ones with the wrong type
2564 QMap<QString, QString> annotations = annjob->annotations();
2565 QMap<QString, QString>::Iterator it = annotations.begin();
2566 for ( ; it != annotations.end(); ++it ) {
2567 const QString folderPath = it.key();
2568 const QString annotation = it.value();
2569 kDebug(5006) << k_funcinfo << "Folder: " << folderPath << " has type: " << annotation << endl;
2570 // we're only interested in the main type
2571 QString type( annotation );
2572 int dot = annotation.indexOf( '.' );
2573 if ( dot != -1 ) {
2574 type.truncate( dot );
2576 type = type.simplified();
2578 const int idx = mSubfolderPaths.indexOf( folderPath );
2579 const bool isNoContent = mSubfolderMimeTypes[idx] == "inode/directory";
2580 if ( ( isNoContent && type.isEmpty() ) ||
2581 ( !type.isEmpty() &&
2582 type != KMailICalIfaceImpl::annotationForContentsType( ContentsTypeMail ) ) ) {
2583 folders.append( idx );
2584 kDebug(5006) << k_funcinfo << " subscribing to: " << folderPath << endl;
2585 } else {
2586 kDebug(5006) << k_funcinfo << " automatically unsubscribing from: " << folderPath << endl;
2587 mAccount->changeLocalSubscription( folderPath, false );
2592 if ( mAccount->slave() ) {
2593 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2595 createFoldersNewOnServerAndFinishListing( folders );
2598 void KMFolderCachedImap::slotQuotaResult( KIO::Job *job )
2600 KMAcctCachedImap::JobIterator it = mAccount->findJob( job );
2601 Q_ASSERT( it != mAccount->jobsEnd() );
2602 if ( it == mAccount->jobsEnd() ) {
2603 return; // Shouldn't happen
2605 Q_ASSERT( (*it).parent == folder() );
2606 if ( (*it).parent != folder() ) {
2607 return; // Shouldn't happen
2610 QuotaJobs::GetStorageQuotaJob *quotajob = static_cast<QuotaJobs::GetStorageQuotaJob *>( job );
2611 QuotaInfo empty;
2612 if ( quotajob->error() ) {
2613 if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
2614 // that's when the imap server doesn't support quota
2615 mAccount->setHasNoQuotaSupport();
2616 mQuotaInfo = empty;
2617 } else {
2618 kWarning(5006) << "slotGetQuotaResult: " << job->errorString() << endl;
2622 if ( mAccount->slave() ) {
2623 mAccount->removeJob( job );
2625 mProgress += 2;
2626 serverSyncInternal();
2629 void KMFolderCachedImap::slotAnnotationChanged( const QString &entry,
2630 const QString &attribute,
2631 const QString &value )
2633 kDebug(5006) << k_funcinfo << entry << " " << attribute << " " << value << endl;
2634 if ( entry == KOLAB_FOLDERTYPE ) {
2635 mAnnotationFolderTypeChanged = false;
2636 } else if ( entry == KOLAB_INCIDENCESFOR ) {
2637 mIncidencesForChanged = false;
2639 * The incidences-for changed, we must trigger the freebusy creation.
2640 * HACK: in theory we would need a new enum value for this.
2642 kmkernel->iCalIface().addFolderChange( folder(), ACL );
2646 void KMFolderCachedImap::slotTestAnnotationResult( KJob *job )
2648 KMAcctCachedImap::JobIterator it =
2649 mAccount->findJob( static_cast<KIO::Job*>( job ) );
2651 Q_ASSERT( it != mAccount->jobsEnd() );
2652 if ( it == mAccount->jobsEnd() ) {
2653 return; // Shouldn't happen
2655 Q_ASSERT( (*it).parent == folder() );
2656 if ( (*it).parent != folder() ) {
2657 return; // Shouldn't happen
2660 mAccount->setAnnotationCheckPassed( true );
2661 if ( job->error() ) {
2662 kDebug(5006) << "Test Annotation was not passed, disabling annotation support" << endl;
2663 mAccount->setHasNoAnnotationSupport( );
2664 } else {
2665 kDebug(5006) << "Test Annotation was passed OK" << endl;
2667 if ( mAccount->slave() ) {
2668 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2670 serverSyncInternal();
2673 void KMFolderCachedImap::slotSetAnnotationResult( KJob *job )
2675 KMAcctCachedImap::JobIterator it =
2676 mAccount->findJob( static_cast<KIO::Job*>( job ) );
2678 if ( it == mAccount->jobsEnd() ) {
2679 return; // Shouldn't happen
2681 if ( (*it).parent != folder() ) {
2682 return; // Shouldn't happen
2685 bool cont = true;
2686 if ( job->error() ) {
2688 * Don't show error if the server doesn't support ANNOTATEMORE
2689 * and this folder only contains mail.
2691 if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION &&
2692 contentsType() == ContentsTypeMail ) {
2693 if ( mAccount->slave() ) {
2694 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2695 } else {
2696 cont =
2697 mAccount->handleJobError( static_cast<KIO::Job*>( job ),
2698 i18n( "Error while setting annotation: " ) + '\n' );
2701 } else {
2702 if ( mAccount->slave() ) {
2703 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2706 if ( cont ) {
2707 serverSyncInternal();
2711 void KMFolderCachedImap::slotUpdateLastUid()
2713 if ( mTentativeHighestUid != 0 ) {
2714 setLastUid( mTentativeHighestUid );
2716 mTentativeHighestUid = 0;
2719 bool KMFolderCachedImap::isMoveable() const
2721 return ( hasChildren() == HasNoChildren &&
2722 !folder()->isSystemFolder() ) ? true : false;
2725 void KMFolderCachedImap::slotFolderDeletionOnServerFinished()
2727 for ( QStringList::const_iterator it = foldersForDeletionOnServer.constBegin();
2728 it != foldersForDeletionOnServer.constEnd(); ++it ) {
2729 KUrl url( mAccount->getUrl() );
2730 url.setPath( *it );
2731 kmkernel->iCalIface().folderDeletedOnServer( url );
2733 serverSyncInternal();
2736 int KMFolderCachedImap::createIndexFromContentsRecursive()
2738 if ( !folder() || !folder()->child() ) {
2739 return 0;
2742 for ( QList<KMFolderNode*>::Iterator it = folder()->child()->begin();
2743 it != folder()->child()->end(); ++it ) {
2744 if ( !(*it)->isDir() ) {
2745 KMFolderCachedImap *storage =
2746 static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(*it)->storage());
2747 kDebug(5006) << k_funcinfo << "Re-indexing: " << storage->folder()->label() << endl;
2748 int rv = storage->createIndexFromContentsRecursive();
2749 if ( rv > 0 ) {
2750 return rv;
2755 return createIndexFromContents();
2758 void KMFolderCachedImap::setAlarmsBlocked( bool blocked )
2760 mAlarmsBlocked = blocked;
2763 bool KMFolderCachedImap::alarmsBlocked() const
2765 return mAlarmsBlocked;
2768 KMCommand* KMFolderCachedImap::rescueUnsyncedMessages()
2770 QList<unsigned long> newMsgs = findNewMessages();
2771 kDebug(5006) << k_funcinfo << newMsgs << " of " << count() << endl;
2772 if ( newMsgs.isEmpty() )
2773 return 0;
2774 KMFolder *dest = 0;
2775 bool manualMove = true;
2776 while ( GlobalSettings::autoLostFoundMove() ) {
2777 // find the inbox of this account
2778 KMFolder *inboxFolder = kmkernel->findFolderById( QString(".%1.directory/INBOX").arg( account()->id() ) );
2779 if ( !inboxFolder ) {
2780 kWarning(5006) << k_funcinfo << "inbox not found!" << endl;
2781 break;
2783 KMFolderDir *inboxDir = inboxFolder->child();
2784 if ( !inboxDir && !inboxFolder->storage() )
2785 break;
2786 assert( inboxFolder->storage()->folderType() == KMFolderTypeCachedImap );
2788 // create lost+found folder if needed
2789 KMFolderNode *node;
2790 KMFolder *lfFolder = 0;
2791 if ( !(node = inboxDir->hasNamedFolder( i18n("lost+found") )) ) {
2792 kDebug(5006) << k_funcinfo << "creating lost+found folder" << endl;
2793 KMFolder* folder = kmkernel->dimapFolderMgr()->createFolder(
2794 i18n("lost+found"), false, KMFolderTypeCachedImap, inboxDir );
2795 if ( !folder || !folder->storage() )
2796 break;
2797 static_cast<KMFolderCachedImap*>( folder->storage() )->initializeFrom(
2798 static_cast<KMFolderCachedImap*>( inboxFolder->storage() ) );
2799 folder->storage()->setContentsType( KMail::ContentsTypeMail );
2800 folder->storage()->writeConfig();
2801 lfFolder = folder;
2802 } else {
2803 kDebug(5006) << k_funcinfo << "found lost+found folder" << endl;
2804 lfFolder = dynamic_cast<KMFolder*>( node );
2806 if ( !lfFolder || !lfFolder->createChildFolder() || !lfFolder->storage() )
2807 break;
2809 // create subfolder for this incident
2810 QDate today = QDate::currentDate();
2811 QString baseName = folder()->label() + "-" + QString::number( today.year() )
2812 + (today.month() < 10 ? "0" : "" ) + QString::number( today.month() )
2813 + (today.day() < 10 ? "0" : "" ) + QString::number( today.day() );
2814 QString name = baseName;
2815 int suffix = 0;
2816 while ( (node = lfFolder->child()->hasNamedFolder( name )) ) {
2817 ++suffix;
2818 name = baseName + '-' + QString::number( suffix );
2820 kDebug(5006) << k_funcinfo << "creating lost+found folder " << name << endl;
2821 dest = kmkernel->dimapFolderMgr()->createFolder( name, false, KMFolderTypeCachedImap, lfFolder->child() );
2822 if ( !dest || !dest->storage() )
2823 break;
2824 static_cast<KMFolderCachedImap*>( dest->storage() )->initializeFrom(
2825 static_cast<KMFolderCachedImap*>( lfFolder->storage() ) );
2826 dest->storage()->setContentsType( contentsType() );
2827 dest->storage()->writeConfig();
2829 KMessageBox::sorry( 0, i18n("<p>There are new messages in folder <b>%1</b>, which "
2830 "have not been uploaded to the server yet, but the folder has been deleted "
2831 "on the server or you do not "
2832 "have sufficient access rights on the folder to upload them.</p>"
2833 "<p>All affected messages will therefore be moved to <b>%2</b> "
2834 "to avoid data loss.</p>").arg( folder()->prettyUrl() ).arg( dest->prettyUrl() ),
2835 i18n("Insufficient access rights") );
2836 manualMove = false;
2837 break;
2840 if ( manualMove ) {
2841 const QString msg ( i18n( "<p>There are new messages in this folder (%1), which "
2842 "have not been uploaded to the server yet, but the folder has been deleted "
2843 "on the server or you do not "
2844 "have sufficient access rights on the folder now to upload them. "
2845 "Please contact your administrator to allow upload of new messages "
2846 "to you, or move them out of this folder.</p> "
2847 "<p>Do you want to move these messages to another folder now?</p>").arg( folder()->prettyUrl() ) );
2848 if ( KMessageBox::warningYesNo( 0, msg, QString::null, KGuiItem( i18n("Move") ), KGuiItem( i18n("Do Not Move") ) )
2849 == KMessageBox::Yes ) {
2850 KMail::FolderSelectionDialog dlg( kmkernel->getKMMainWidget(),
2851 i18n("Move Messages to Folder"), true );
2852 if ( dlg.exec() ) {
2853 dest = dlg.folder();
2857 if ( dest ) {
2858 QList<KMMsgBase*> msgs;
2859 for( int i = 0; i < count(); ++i ) {
2860 KMMsgBase *msg = getMsgBase( i );
2861 if( !msg ) continue; /* what goes on if getMsg() returns 0? */
2862 if ( msg->UID() == 0 )
2863 msgs.append( msg );
2865 KMCommand *command = new KMMoveCommand( dest, msgs );
2866 command->start();
2867 return command;
2869 return 0;
2872 void KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder( KMFolder *folder, bool root )
2874 kDebug(5006) << k_funcinfo << folder << " " << root << endl;
2875 if ( root )
2876 mToBeDeletedAfterRescue.append( folder );
2877 folder->open( "KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder" );
2878 KMFolderCachedImap* storage = dynamic_cast<KMFolderCachedImap*>( folder->storage() );
2879 if ( storage ) {
2880 KMCommand *command = storage->rescueUnsyncedMessages();
2881 if ( command ) {
2882 connect( command, SIGNAL(completed(KMCommand*)),
2883 SLOT(slotRescueDone(KMCommand*)) );
2884 ++mRescueCommandCount;
2887 if ( folder->child() ) {
2888 for ( KMFolderNodeList::ConstIterator it = folder->child()->constBegin(); it != folder->child()->constEnd(); ++it ) {
2889 KMFolderNode *node = *it;
2890 if ( node && !node->isDir() ) {
2891 KMFolder *subFolder = static_cast<KMFolder*>( node );
2892 rescueUnsyncedMessagesAndDeleteFolder( subFolder, false );
2896 folder->close( "KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder" );
2897 if ( root )
2898 slotRescueDone( 0 ); // just in case there is nothing to rescue
2901 void KMFolderCachedImap::slotRescueDone(KMCommand * command)
2903 // FIXME: check command result
2904 --mRescueCommandCount;
2905 if ( mRescueCommandCount > 0 )
2906 return;
2907 for ( QList<KMFolder*>::ConstIterator it = mToBeDeletedAfterRescue.constBegin();
2908 it != mToBeDeletedAfterRescue.constEnd(); ++it ) {
2909 kmkernel->dimapFolderMgr()->remove( *it );
2911 mToBeDeletedAfterRescue.clear();
2912 serverSyncInternal();
2915 #include "kmfoldercachedimap.moc"