krop's commit fixes my problem in a better way, reverting
[kdepim.git] / kmail / kmfoldercachedimap.cpp
blob56c14ee2c827fb451fd1101d366f4b792144194f
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 #include "globalsettings.h"
35 #include "kmkernel.h"
36 #include "undostack.h"
37 #include "kmfoldermgr.h"
38 #include "kmacctcachedimap.h"
39 #include "accountmanager.h"
40 using KMail::AccountManager;
41 #include "kmailicalifaceimpl.h"
42 #include "kmfolder.h"
43 #include "kmglobal.h"
44 #include "acljobs.h"
45 #include "broadcaststatus.h"
46 using KPIM::BroadcastStatus;
47 #include "progressmanager.h"
48 using KMail::CachedImapJob;
49 #include "imapaccountbase.h"
50 using KMail::ImapAccountBase;
51 #include "listjob.h"
52 using KMail::ListJob;
53 #include "folderselectiondialog.h"
54 #include "kmcommands.h"
55 #include "kmmainwidget.h"
56 #include "annotationjobs.h"
57 #include "quotajobs.h"
58 #include "groupwareadaptor.h"
59 #include "autoqpointer.h"
60 using namespace KMail;
62 #include <kio/jobuidelegate.h>
63 #include <kio/global.h>
64 #include <kio/scheduler.h>
66 #include <kcombobox.h>
67 #include <kmessagebox.h>
68 #include <klocale.h>
69 #include <kdebug.h>
70 #include <kconfig.h>
71 #include <kconfiggroup.h>
72 #include <khbox.h>
74 #include <QByteArray>
75 #include <QTextStream>
76 #include <QTimerEvent>
77 #include <QVBoxLayout>
78 #include <QVector>
79 #include <QBuffer>
80 #include <QButtonGroup>
81 #include <QFile>
82 #include <QLabel>
83 #include <QLayout>
84 #include <QRadioButton>
86 #include <unistd.h>
87 #include <errno.h>
89 #define UIDCACHE_VERSION 1
90 #define MAIL_LOSS_DEBUGGING 0
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 lose 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 mButtonGroup = new QButtonGroup( 0 );
146 mIndexButton = new QRadioButton( page );
147 mIndexButton->setText( i18n( "Rebuild &index" ) );
148 mButtonGroup->addButton( mIndexButton, 1 );
149 topLayout->addWidget( mIndexButton );
151 KHBox *hbox = new KHBox( page );
152 QLabel *scopeLabel = new QLabel(
153 i18nc( "@label:listbox Scope used when rebuilding index.","Scope:" ), hbox );
154 scopeLabel->setEnabled( false );
155 mIndexScope = new KComboBox( hbox );
156 mIndexScope->addItem( i18n( "Only Current Folder" ) );
157 mIndexScope->addItem( i18n( "Current Folder & All Subfolders" ) );
158 mIndexScope->addItem( i18n( "All Folders of This Account" ) );
159 mIndexScope->setEnabled( false );
160 topLayout->addWidget( hbox );
162 mCacheButton = new QRadioButton( page );
163 mCacheButton->setText( i18n( "Refresh &Cache" ) );
164 mButtonGroup->addButton( mCacheButton, 2 );
165 topLayout->addWidget( mCacheButton );
167 connect ( mIndexButton, SIGNAL( toggled( bool ) ),
168 mIndexScope, SLOT( setEnabled( bool ) ) );
169 connect ( mIndexButton, SIGNAL( toggled( bool ) ),
170 scopeLabel, SLOT( setEnabled( bool ) ) );
171 connect( mButtonGroup, SIGNAL( buttonClicked( int ) ), SLOT( slotChanged( int ) ) );
172 connect( this, SIGNAL( okClicked () ), this, SLOT( slotDone() ) );
173 enableButtonOk( false );
176 int DImapTroubleShootDialog::run()
178 DImapTroubleShootDialog d;
179 d.exec();
180 return d.rc;
183 void DImapTroubleShootDialog::slotChanged( int id )
185 enableButtonOk( mButtonGroup->button( id )->isChecked() );
188 void DImapTroubleShootDialog::slotDone()
190 rc = None;
191 if ( mIndexButton->isChecked() ) {
192 rc = mIndexScope->currentIndex();
193 } else if ( mCacheButton->isChecked() ) {
194 rc = RefreshCache;
196 done( Ok );
199 KMFolderCachedImap::KMFolderCachedImap( KMFolder *folder, const char *aName )
200 : KMFolderMaildir( folder, aName ),
201 mSyncState( SYNC_STATE_INITIAL ), mContentState( imapNoInformation ),
202 mSubfolderState( imapNoInformation ),
203 mIncidencesFor( IncForAdmins ),
204 mSharedSeenFlags( false ),
205 mIsSelected( false ),
206 mCheckFlags( true ), mReadOnly( false ), mAccount( 0 ), uidMapDirty( true ),
207 uidWriteTimer( -1 ), mLastUid( 0 ), mTentativeHighestUid( 0 ),
208 mFoundAnIMAPDigest( false ),
209 mUserRights( 0 ), mOldUserRights( 0 ), mSilentUpload( false ),
210 /*mHoldSyncs( false ),*/
211 mFolderRemoved( false ),
212 mRecurse( true ),
213 mStatusChangedLocally( false ),
214 mAnnotationFolderTypeChanged( false ),
215 mIncidencesForChanged( false ),
216 mSharedSeenFlagsChanged( false ),
217 mPersonalNamespacesCheckDone( true ),
218 mQuotaInfo(), mAlarmsBlocked( false ),
219 mRescueCommandCount( 0 ),
220 mPermanentFlags( 31 ) // assume standard flags by default (see imap4/imapinfo.h for bit fields values)
222 setUidValidity( "" );
223 // if we fail to read a uid file but there is one, nuke it
224 if ( readUidCache() == -1 ) {
225 if ( QFile::exists( uidCacheLocation() ) ) {
226 KMessageBox::error( 0,
227 i18n( "The UID cache file for folder %1 could not be read. There "
228 "could be a problem with file system permission, or it is corrupted.",
229 folder->prettyUrl() ) );
230 // try to unlink it, in case it was corruped. If it couldn't be read
231 // because of permissions, this will fail, which is fine
232 unlink( QFile::encodeName( uidCacheLocation() ) );
236 mProgress = 0;
239 KMFolderCachedImap::~KMFolderCachedImap()
241 if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() );
244 void KMFolderCachedImap::reallyDoClose()
246 if( !mFolderRemoved ) {
247 writeUidCache();
249 KMFolderMaildir::reallyDoClose();
252 void KMFolderCachedImap::initializeFrom( KMFolderCachedImap *parent )
254 setAccount( parent->account() );
257 * Now that we have an account, tell it that this folder was created:
258 * if this folder was just removed, then we don't really want to remove
259 * it from the server.
261 mAccount->removeDeletedFolder( imapPath() );
262 setUserRights( parent->userRights() );
265 void KMFolderCachedImap::readConfig()
267 KSharedConfig::Ptr config = KMKernel::config();
268 KConfigGroup group( config, folder()->configGroupName() );
269 if ( mImapPath.isEmpty() ) {
270 mImapPath = group.readEntry( "ImapPath" );
272 if ( QString( objectName() ).toUpper() == "INBOX" && mImapPath == "/INBOX/" ) {
273 folder()->setLabel( i18n( "inbox" ) );
274 // for the icon
275 folder()->setSystemFolder( true );
277 mNoContent = group.readEntry( "NoContent", false );
278 mReadOnly = group.readEntry( "ReadOnly", false );
280 if ( !group.readEntry( "FolderAttributes" ).isEmpty() )
281 mFolderAttributes = group.readEntry( "FolderAttributes" );
283 if ( mAnnotationFolderType != "FROMSERVER" ) {
284 mAnnotationFolderType = group.readEntry( "Annotation-FolderType" );
285 // if there is an annotation, it has to be XML
286 if ( !mAnnotationFolderType.isEmpty() &&
287 !mAnnotationFolderType.startsWith( QLatin1String("mail") ) ) {
288 kmkernel->iCalIface().setStorageFormat( folder(), StorageXML );
291 mIncidencesFor = incidencesForFromString( group.readEntry( "IncidencesFor" ) );
292 mAlarmsBlocked = group.readEntry( "AlarmsBlocked", false );
293 // kDebug() << ( mImapPath.isEmpty() ? label() : mImapPath )
294 // << " readConfig: mIncidencesFor=" << mIncidencesFor;
295 mSharedSeenFlags = group.readEntry( "SharedSeenFlags", false );
297 mUserRights = group.readEntry( "UserRights", 0 ); // default is we don't know
298 mOldUserRights = mUserRights;
300 int storageQuotaUsage = group.readEntry( "StorageQuotaUsage", -1 );
301 int storageQuotaLimit = group.readEntry( "StorageQuotaLimit", -1 );
302 QString storageQuotaRoot = group.readEntry( "StorageQuotaRoot", QString() );
303 if ( !storageQuotaRoot.isNull() ) { // isEmpty() means we know there is no quota set
304 mQuotaInfo.setName( "STORAGE" );
305 mQuotaInfo.setRoot( storageQuotaRoot );
307 if ( storageQuotaUsage > -1 ) {
308 mQuotaInfo.setCurrent( storageQuotaUsage );
310 if ( storageQuotaLimit > -1 ) {
311 mQuotaInfo.setMax( storageQuotaLimit );
315 KMFolderMaildir::readConfig();
317 mStatusChangedLocally = group.readEntry( "StatusChangedLocally", false );
318 QStringList uidsChanged = group.readEntry( "UIDStatusChangedLocally", QStringList() );
319 foreach( const QString &uid, uidsChanged ) {
320 mUIDsOfLocallyChangedStatuses.insert( uid.toUInt() );
322 mAnnotationFolderTypeChanged = group.readEntry( "AnnotationFolderTypeChanged", false );
323 mIncidencesForChanged = group.readEntry( "IncidencesForChanged", false );
324 mSharedSeenFlagsChanged = group.readEntry( "SharedSeenFlagsChanged", false );
325 if ( mImapPath.isEmpty() ) {
326 mImapPathCreation = group.readEntry("ImapPathCreation");
329 QStringList delUids = group.readEntry( "UIDSDeletedSinceLastSync" , QStringList() );
330 #if MAIL_LOSS_DEBUGGING
331 kDebug() << "READING IN UIDSDeletedSinceLastSync: " << folder()->prettyUrl() << endl << uids;
332 #endif
333 for ( QStringList::iterator it = delUids.begin(); it != delUids.end(); ++it ) {
334 mDeletedUIDsSinceLastSync.insert( (*it).toULong(), 0);
338 void KMFolderCachedImap::writeConfig()
340 // don't re-write the config of a removed folder, this has just been deleted in
341 // the folder manager
342 if ( mFolderRemoved )
343 return;
345 KConfigGroup configGroup( KMKernel::config(), folder()->configGroupName() );
347 configGroup.writeEntry( "ImapPath", mImapPath );
348 configGroup.writeEntry( "NoContent", mNoContent );
349 configGroup.writeEntry( "ReadOnly", mReadOnly );
350 configGroup.writeEntry( "FolderAttributes", mFolderAttributes );
352 // StatusChangedLocally is always false, as we use UIDStatusChangedLocally now
353 configGroup.writeEntry( "StatusChangedLocally", false );
354 QStringList uidsToWrite;
355 foreach( const ulong uid, mUIDsOfLocallyChangedStatuses ) {
356 uidsToWrite.append( QString::number( uid ) );
358 configGroup.writeEntry( "UIDStatusChangedLocally", uidsToWrite );
359 if ( !mImapPathCreation.isEmpty() ) {
360 if ( mImapPath.isEmpty() ) {
361 configGroup.writeEntry( "ImapPathCreation", mImapPathCreation );
362 } else {
363 configGroup.deleteEntry( "ImapPathCreation" );
366 if ( !mDeletedUIDsSinceLastSync.isEmpty() ) {
367 QList<ulong> uids = mDeletedUIDsSinceLastSync.keys();
368 QStringList uidstrings;
369 for( QList<ulong>::iterator it = uids.begin(); it != uids.end(); ++it ) {
370 uidstrings.append( QString::number( (*it) ) );
372 configGroup.writeEntry( "UIDSDeletedSinceLastSync", uidstrings );
373 #if MAIL_LOSS_DEBUGGING
374 kDebug() << "WRITING OUT UIDSDeletedSinceLastSync in: " << folder( )->prettyUrl( ) << endl << uidstrings;
375 #endif
376 } else {
377 configGroup.deleteEntry( "UIDSDeletedSinceLastSync" );
379 writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
380 KMFolderMaildir::writeConfig();
383 void KMFolderCachedImap::writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig()
385 KConfigGroup configGroup( KMKernel::config(), folder()->configGroupName() );
386 if ( !folder()->noContent() ) {
387 configGroup.writeEntry( "AnnotationFolderTypeChanged", mAnnotationFolderTypeChanged );
388 configGroup.writeEntry( "Annotation-FolderType", mAnnotationFolderType );
389 configGroup.writeEntry( "IncidencesForChanged", mIncidencesForChanged );
390 configGroup.writeEntry( "IncidencesFor", incidencesForToString( mIncidencesFor ) );
391 configGroup.writeEntry( "AlarmsBlocked", mAlarmsBlocked );
392 configGroup.writeEntry( "SharedSeenFlags", mSharedSeenFlags );
393 configGroup.writeEntry( "SharedSeenFlagsChanged", mSharedSeenFlagsChanged );
394 configGroup.writeEntry( "UserRights", mUserRights );
396 configGroup.deleteEntry( "StorageQuotaUsage");
397 configGroup.deleteEntry( "StorageQuotaRoot");
398 configGroup.deleteEntry( "StorageQuotaLimit");
400 if ( mQuotaInfo.isValid() ) {
401 if ( mQuotaInfo.current().isValid() ) {
402 configGroup.writeEntry( "StorageQuotaUsage", mQuotaInfo.current().toInt() );
404 if ( mQuotaInfo.max().isValid() ) {
405 configGroup.writeEntry( "StorageQuotaLimit", mQuotaInfo.max().toInt() );
407 configGroup.writeEntry( "StorageQuotaRoot", mQuotaInfo.root() );
412 int KMFolderCachedImap::create()
414 int rc = KMFolderMaildir::create();
415 // FIXME why the below? - till
416 readConfig();
417 mUnreadMsgs = -1;
418 return rc;
421 void KMFolderCachedImap::remove()
423 mFolderRemoved = true;
425 QString part1 = folder()->path() + "/." + dotEscape( objectName() );
426 QString uidCacheFile = part1 + ".uidcache";
427 // This is the account folder of an account that was just removed
428 // When this happens, be sure to delete all traces of the cache
429 if ( QFile::exists( uidCacheFile ) ) {
430 unlink( QFile::encodeName( uidCacheFile ) );
433 FolderStorage::remove();
436 QString KMFolderCachedImap::uidCacheLocation() const
438 QString sLocation( folder()->path() );
439 if ( !sLocation.isEmpty() ) {
440 sLocation += '/';
442 return sLocation + '.' + dotEscape(fileName()) + ".uidcache";
445 int KMFolderCachedImap::readUidCache()
447 QFile uidcache( uidCacheLocation() );
448 if ( uidcache.open( QIODevice::ReadOnly ) ) {
449 char buf[1024];
450 int len = uidcache.readLine( buf, sizeof( buf ) );
451 if ( len > 0 ) {
452 int cacheVersion;
453 sscanf( buf, "# KMail-UidCache V%d\n", &cacheVersion );
454 if ( cacheVersion == UIDCACHE_VERSION ) {
455 len = uidcache.readLine( buf, sizeof( buf ) );
456 if ( len > 0 ) {
457 setUidValidity( QString::fromLocal8Bit( buf ).trimmed() );
458 len = uidcache.readLine( buf, sizeof( buf ) );
459 if ( len > 0 ) {
460 #if MAIL_LOSS_DEBUGGING
461 kDebug() << "Reading in last uid from cache: " << QString::fromLocal8Bit(buf).trimmed() << " in " << folder()->prettyUrl();
462 #endif
463 // load the last known highest uid from the on disk cache
464 setLastUid( QString::fromLocal8Bit( buf ).trimmed().toULong() );
465 return 0;
471 return -1;
474 int KMFolderCachedImap::writeUidCache()
476 if ( uidValidity().isEmpty() || uidValidity() == "INVALID" ) {
477 // No info from the server yet, remove the file.
478 if ( QFile::exists( uidCacheLocation() ) ) {
479 return unlink( QFile::encodeName( uidCacheLocation() ) );
481 return 0;
483 #if MAIL_LOSS_DEBUGGING
484 kDebug() << "Writing out UID cache lastuid: " << lastUid() << " in: " << folder()->prettyUrl();
485 #endif
486 QFile uidcache( uidCacheLocation() );
487 if ( uidcache.open( QIODevice::WriteOnly ) ) {
488 QTextStream str( &uidcache );
489 str << "# KMail-UidCache V" << UIDCACHE_VERSION << endl;
490 str << uidValidity() << endl;
491 str << lastUid() << endl;
492 uidcache.flush();
493 if ( uidcache.error() == QFile::NoError ) {
494 fsync( uidcache.handle() ); /* this is probably overkill */
495 uidcache.close();
496 if ( uidcache.error() == QFile::NoError )
497 return 0;
500 KMessageBox::error( 0,
501 i18n( "The UID cache file for folder %1 could not be written. There "
502 "could be a problem with file system permission.", folder()->prettyUrl() ) );
504 return -1;
507 void KMFolderCachedImap::reloadUidMap()
509 //kDebug() << "Reloading Uid Map";
510 uidMap.clear();
511 open( "reloadUid" );
512 for ( int i = 0; i < count(); ++i ) {
513 KMMsgBase *msg = getMsgBase( i );
514 if ( !msg ) {
515 continue;
517 ulong uid = msg->UID();
518 uidMap.insert( uid, i );
520 close( "reloadUid" );
521 uidMapDirty = false;
524 KMMessage *KMFolderCachedImap::take( int idx )
526 uidMapDirty = true;
527 rememberDeletion( idx );
528 return KMFolderMaildir::take( idx );
531 void KMFolderCachedImap::takeTemporarily( int idx )
533 KMFolderMaildir::take( idx );
536 int KMFolderCachedImap::addMsgInternal( KMMessage *msg, bool newMail, int *index_return )
538 // Possible optimization: Only dirty if not filtered below
539 ulong uid = msg->UID();
540 if ( uid != 0 ) {
541 uidMapDirty = true;
544 KMFolderOpener openThis( folder(), "KMFolderCachedImap::addMsgInternal" );
545 int rc = openThis.openResult();
546 if ( rc ) {
547 kDebug() << "open:" << rc << "of folder:" << label();
548 return rc;
551 // Add the message
552 rc = KMFolderMaildir::addMsg( msg, index_return );
554 if ( newMail && ( imapPath() == "/INBOX/" || ( !GlobalSettings::self()->filterOnlyDIMAPInbox()
555 && (userRights() <= 0 || userRights() & ACLJobs::Administer )
556 && (contentsType() == ContentsTypeMail || GlobalSettings::self()->filterGroupwareFolders()) ) ) ) {
557 // This is a new message. Filter it
558 mAccount->processNewMsg( msg );
561 return rc;
564 int KMFolderCachedImap::addMsg( KMMessage *msg, int *index_return )
566 if ( !canAddMsgNow( msg, index_return ) ) {
567 return 0;
570 // Add it to storage
571 int rc = KMFolderMaildir::addMsgInternal( msg, index_return, true /*stripUID*/);
572 return rc;
575 void KMFolderCachedImap::rememberDeletion( int idx )
577 KMMsgBase *msg = getMsgBase( idx );
578 assert(msg);
579 long uid = msg->UID();
580 assert(uid>=0);
581 mDeletedUIDsSinceLastSync.insert(uid, 0);
582 kDebug() << "Explicit delete of UID " << uid << " at index: " << idx << " in " << folder()->prettyUrl();
585 /* Reimplemented from KMFolderMaildir */
586 void KMFolderCachedImap::removeMsg( int idx, bool imapQuiet )
588 uidMapDirty = true;
589 rememberDeletion( idx );
590 // Remove it from disk
591 KMFolderMaildir::removeMsg( idx, imapQuiet );
594 bool KMFolderCachedImap::canRemoveFolder() const
596 // If this has subfolders it can't be removed
597 if ( folder() && folder()->child() && folder()->child()->count() > 0 ) {
598 return false;
600 return true;
603 int KMFolderCachedImap::rename( const QString &aName, KMFolderDir *aParent )
605 Q_UNUSED( aParent );
607 QString oldName = mAccount->renamedFolder( imapPath() );
608 if ( oldName.isEmpty() ) {
609 oldName = objectName();
612 if ( aName == oldName ) {
613 // Stupid user trying to rename it to it's old name :)
614 return 0;
617 if ( account() == 0 || imapPath().isEmpty() ) {
618 // We don't think any of this can happen anymore
619 QString err = i18n("You must synchronize with the server before renaming IMAP folders.");
620 KMessageBox::error( 0, err );
621 return -1;
625 * Make the change appear to the user with setLabel, but we'll do the change
626 * on the server during the next sync. The name() is the name at the time of
627 * the last sync. Only rename if the new one is different. If it's the same,
628 * don't rename, but also make sure the rename is reset, in the case of
629 * A -> B -> A renames.
631 if ( objectName() != aName ) {
632 mAccount->addRenamedFolder( imapPath(), folder()->label(), aName );
633 } else {
634 mAccount->removeRenamedFolder( imapPath() );
637 folder()->setLabel( aName );
638 emit nameChanged(); // for kmailicalifaceimpl
640 return 0;
643 KMFolder *KMFolderCachedImap::trashFolder() const
645 QString trashStr = account()->trash();
646 return kmkernel->dimapFolderMgr()->findIdString( trashStr );
649 void KMFolderCachedImap::setLastUid( ulong uid )
651 #if MAIL_LOSS_DEBUGGING
652 kDebug() << "Setting mLastUid to: " << uid << " in " << folder()->prettyUrl();
653 #endif
654 mLastUid = uid;
655 if ( uidWriteTimer == -1 ) {
656 // Write in one minute
657 uidWriteTimer = startTimer( 60000 );
661 void KMFolderCachedImap::timerEvent( QTimerEvent *e )
663 Q_UNUSED( e );
665 killTimer( uidWriteTimer );
666 uidWriteTimer = -1;
667 if ( writeUidCache() == -1 )
668 unlink( QFile::encodeName( uidCacheLocation() ) );
671 ulong KMFolderCachedImap::lastUid()
673 return mLastUid;
676 KMMsgBase *KMFolderCachedImap::findByUID( ulong uid )
678 bool mapReloaded = false;
679 if ( uidMapDirty ) {
680 reloadUidMap();
681 mapReloaded = true;
684 QMap<ulong,int>::Iterator it = uidMap.find( uid );
685 if ( it != uidMap.end() ) {
686 KMMsgBase *msg = getMsgBase( *it );
687 #if MAIL_LOSS_DEBUGGING
688 kDebug() << "Folder: " << folder()->prettyUrl();
689 kDebug() << "UID " << uid << " is supposed to be in the map";
690 kDebug() << "UID's index is to be " << *it;
691 kDebug() << "There is a message there? " << (msg != 0);
692 if ( msg ) {
693 kDebug() << "Its UID is: " << msg->UID();
695 #endif
697 if ( msg && msg->UID() == uid ) {
698 return msg;
700 kDebug() << "########## Didn't find uid: " << uid << "in cache athough it's supposed to be there!";
701 } else {
702 #if MAIL_LOSS_DEBUGGING
703 kDebug() << "Didn't find uid:" << uid <<"in cache!";
704 #endif
706 // Not found by now
707 // if ( mapReloaded )
708 // Not here then
709 return 0;
711 // There could be a problem in the maps. Rebuild them and try again
712 reloadUidMap();
713 it = uidMap.find( uid );
714 if ( it != uidMap.end() ) {
715 // Since the uid map is just rebuilt, no need for the sanity check
716 return getMsgBase( *it );
717 } else {
718 #if MAIL_LOSS_DEBUGGING
719 kDebug() << "Reloaded, but stil didn't find uid:" << uid;
720 #endif
723 // Then it's not here
724 return 0;
727 KMAcctCachedImap *KMFolderCachedImap::account() const
729 if( (KMAcctCachedImap *)mAccount == 0 && kmkernel && kmkernel->acctMgr() ) {
730 // Find the account
731 mAccount =
732 static_cast<KMAcctCachedImap *>( kmkernel->acctMgr()->findByName( objectName() ) );
735 return mAccount;
738 void KMFolderCachedImap::slotTroubleshoot()
740 const int rc = DImapTroubleShootDialog::run();
742 if ( rc == DImapTroubleShootDialog::RefreshCache ) {
743 // Refresh cache
744 if ( !account() ) {
745 KMessageBox::sorry( 0, i18n("No account setup for this folder.\n"
746 "Please try running a sync before this.") );
747 return;
749 QString str = i18n("Are you sure you want to refresh the IMAP cache of "
750 "the folder %1 and all its subfolders?\nThis will "
751 "remove all changes you have done locally to your "
752 "folders.", label() );
753 QString s1 = i18n("Refresh IMAP Cache");
754 QString s2 = i18n("&Refresh");
755 if ( KMessageBox::warningContinueCancel( 0, str, s1, KGuiItem( s2 ) ) ==
756 KMessageBox::Continue ) {
757 account()->invalidateIMAPFolders( this );
759 } else {
760 // Rebuild index file
761 switch ( rc ) {
762 case DImapTroubleShootDialog::ReindexAll:
764 KMFolderCachedImap *rootStorage =
765 dynamic_cast<KMFolderCachedImap*>( account()->rootFolder() );
766 if ( rootStorage ) {
767 rootStorage->createIndexFromContentsRecursive();
769 break;
771 case DImapTroubleShootDialog::ReindexCurrent:
772 createIndexFromContents();
773 break;
774 case DImapTroubleShootDialog::ReindexRecursive:
775 createIndexFromContentsRecursive();
776 break;
777 default:
778 return;
780 KMessageBox::information( 0, i18n( "The index of this folder has been "
781 "recreated." ) );
783 writeIndex();
784 kmkernel->getKMMainWidget()->folderSelected();
788 void KMFolderCachedImap::serverSync( bool recurse )
790 if ( mSyncState != SYNC_STATE_INITIAL ) {
791 if ( KMessageBox::warningYesNo(
793 i18n("Folder %1 is not in initial sync state (state was %2). "
794 "Do you want to reset it to initial sync state and sync anyway?",
795 imapPath(), int( mSyncState ) ),
796 QString(), KGuiItem( i18n("Reset && Sync") ),
797 KStandardGuiItem::cancel() ) == KMessageBox::Yes ) {
798 mSyncState = SYNC_STATE_INITIAL;
799 } else {
800 return;
804 mRecurse = recurse;
805 assert( account() );
807 ProgressItem *progressItem = mAccount->mailCheckProgressItem();
808 if ( progressItem ) {
809 progressItem->reset();
810 progressItem->setTotalItems( 100 );
813 mProgress = 0;
814 mTentativeHighestUid = 0; // reset, last sync could have been canceled
815 serverSyncInternal();
818 QString KMFolderCachedImap::state2String( int state ) const
820 switch( state ) {
821 case SYNC_STATE_INITIAL: return "SYNC_STATE_INITIAL";
822 case SYNC_STATE_GET_USERRIGHTS: return "SYNC_STATE_GET_USERRIGHTS";
823 case SYNC_STATE_PUT_MESSAGES: return "SYNC_STATE_PUT_MESSAGES";
824 case SYNC_STATE_UPLOAD_FLAGS: return "SYNC_STATE_UPLOAD_FLAGS";
825 case SYNC_STATE_CREATE_SUBFOLDERS: return "SYNC_STATE_CREATE_SUBFOLDERS";
826 case SYNC_STATE_LIST_SUBFOLDERS: return "SYNC_STATE_LIST_SUBFOLDERS";
827 case SYNC_STATE_LIST_NAMESPACES: return "SYNC_STATE_LIST_NAMESPACES";
828 case SYNC_STATE_LIST_SUBFOLDERS2: return "SYNC_STATE_LIST_SUBFOLDERS2";
829 case SYNC_STATE_DELETE_SUBFOLDERS: return "SYNC_STATE_DELETE_SUBFOLDERS";
830 case SYNC_STATE_LIST_MESSAGES: return "SYNC_STATE_LIST_MESSAGES";
831 case SYNC_STATE_DELETE_MESSAGES: return "SYNC_STATE_DELETE_MESSAGES";
832 case SYNC_STATE_GET_MESSAGES: return "SYNC_STATE_GET_MESSAGES";
833 case SYNC_STATE_EXPUNGE_MESSAGES: return "SYNC_STATE_EXPUNGE_MESSAGES";
834 case SYNC_STATE_HANDLE_INBOX: return "SYNC_STATE_HANDLE_INBOX";
835 case SYNC_STATE_TEST_ANNOTATIONS: return "SYNC_STATE_TEST_ANNOTATIONS";
836 case SYNC_STATE_GET_ANNOTATIONS: return "SYNC_STATE_GET_ANNOTATIONS";
837 case SYNC_STATE_SET_ANNOTATIONS: return "SYNC_STATE_SET_ANNOTATIONS";
838 case SYNC_STATE_GET_ACLS: return "SYNC_STATE_GET_ACLS";
839 case SYNC_STATE_SET_ACLS: return "SYNC_STATE_SET_ACLS";
840 case SYNC_STATE_GET_QUOTA: return "SYNC_STATE_GET_QUOTA";
841 case SYNC_STATE_FIND_SUBFOLDERS: return "SYNC_STATE_FIND_SUBFOLDERS";
842 case SYNC_STATE_SYNC_SUBFOLDERS: return "SYNC_STATE_SYNC_SUBFOLDERS";
843 case SYNC_STATE_RENAME_FOLDER: return "SYNC_STATE_RENAME_FOLDER";
844 case SYNC_STATE_CHECK_UIDVALIDITY: return "SYNC_STATE_CHECK_UIDVALIDITY";
845 default: return "Unknown state";
850 Progress calculation: each step is assigned a span. Initially the total is 100.
851 But if we skip a step, don't increase the progress.
852 This leaves more room for the step a with variable size (get_messages)
853 connecting 5
854 getuserrights 5
855 rename 5
856 check_uidvalidity 5
857 create_subfolders 5
858 put_messages 10 (but it can take a very long time, with many messages....)
859 upload_flags 5
860 list_subfolders 5
861 list_subfolders2 0 (all local)
862 delete_subfolders 5
863 list_messages 10
864 delete_messages 10
865 expunge_messages 5
866 get_messages variable (remaining-5) i.e. minimum 15.
867 check_annotations 0 (rare)
868 set_annotations 0 (rare)
869 get_annotations 2
870 set_acls 0 (rare)
871 get_acls 3
873 noContent folders have only a few of the above steps
874 (permissions, and all subfolder stuff), so its steps should be given more span
878 // While the server synchronization is running, mSyncState will hold
879 // the state that should be executed next
880 void KMFolderCachedImap::serverSyncInternal()
882 // This is used to stop processing when we're about to exit
883 // and the current job wasn't cancellable.
884 // For user-requested abort, we'll use signalAbortRequested instead.
885 if ( kmkernel->mailCheckAborted() ) {
886 resetSyncState();
887 emit folderComplete( this, false );
888 return;
891 switch( mSyncState ) {
892 case SYNC_STATE_INITIAL:
894 mProgress = 0;
895 foldersForDeletionOnServer.clear();
896 newState( mProgress, i18n("Synchronizing"));
898 open( "cachedimap" );
899 if ( !noContent() ) {
900 mAccount->addLastUnreadMsgCount( this, countUnread() );
903 // Connect to the server (i.e. prepare the slave)
904 ImapAccountBase::ConnectionState cs = mAccount->makeConnection();
905 if ( cs == ImapAccountBase::Error ) {
906 // Cancelled by user, or slave can't start
907 // We stop here. We're already in SYNC_STATE_INITIAL for the next time.
908 newState( mProgress, i18n( "Error connecting to server %1", mAccount->host() ) );
909 close( "cachedimap" );
910 emit folderComplete( this, false );
911 break;
912 } else if ( cs == ImapAccountBase::Connecting ) {
913 mAccount->setAnnotationCheckPassed( false );
914 newState( mProgress, i18n("Connecting to %1", mAccount->host() ) );
916 // We'll wait for the connectionResult signal from the account.
917 connect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
918 this, SLOT( slotConnectionResult(int, const QString&) ) );
919 break;
920 } else {
921 // Connected
922 mSyncState = SYNC_STATE_GET_USERRIGHTS;
923 // Fall through to next state
927 case SYNC_STATE_GET_USERRIGHTS:
928 mSyncState = SYNC_STATE_RENAME_FOLDER;
930 if ( !noContent() && mAccount->hasACLSupport() ) {
931 // Check the user's rights. We do this every time in case they changed.
932 mOldUserRights = mUserRights;
933 newState( mProgress, i18n("Checking permissions"));
934 connect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
935 this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
936 mAccount->getUserRights( folder(), imapPath() ); // after connecting, due to the INBOX case
937 break;
940 case SYNC_STATE_RENAME_FOLDER:
942 mSyncState = SYNC_STATE_CHECK_UIDVALIDITY;
943 // Returns the new name if the folder was renamed, empty otherwise.
944 bool isResourceFolder = kmkernel->iCalIface().isStandardResourceFolder( folder() );
945 QString newName = mAccount->renamedFolder( imapPath() );
946 if ( !newName.isEmpty() && !folder()->isSystemFolder() && !isResourceFolder ) {
947 newState( mProgress, i18n("Renaming folder") );
948 CachedImapJob *job = new CachedImapJob( newName, CachedImapJob::tRenameFolder, this );
949 connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
950 connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
951 job->start();
952 break;
956 case SYNC_STATE_CHECK_UIDVALIDITY:
957 mSyncState = SYNC_STATE_CREATE_SUBFOLDERS;
958 if ( !noContent() ) {
959 checkUidValidity();
960 break;
962 // Else carry on
964 case SYNC_STATE_CREATE_SUBFOLDERS:
965 mSyncState = SYNC_STATE_PUT_MESSAGES;
966 createNewFolders();
967 break;
969 case SYNC_STATE_PUT_MESSAGES:
970 mSyncState = SYNC_STATE_UPLOAD_FLAGS;
971 if ( !noContent() ) {
972 uploadNewMessages();
973 break;
975 // Else carry on
976 case SYNC_STATE_UPLOAD_FLAGS:
977 mSyncState = SYNC_STATE_LIST_NAMESPACES;
978 if ( !noContent() ) {
979 // We haven't downloaded messages yet, so we need to build the map.
980 if ( uidMapDirty ) {
981 reloadUidMap();
984 // Upload flags, unless we know from the ACL that we're not allowed
985 // to do that or they did not change locally
986 if ( mUserRights <= 0 || ( mUserRights & KMail::ACLJobs::WriteFlags ) ) {
987 if ( !mUIDsOfLocallyChangedStatuses.isEmpty() || mStatusChangedLocally ) {
988 uploadFlags();
989 break;
992 else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) {
993 if ( !mUIDsOfLocallyChangedStatuses.isEmpty() || mStatusChangedLocally ) {
994 uploadSeenFlags();
995 break;
999 // Else carry on
1001 case SYNC_STATE_LIST_NAMESPACES:
1002 if ( this == mAccount->rootFolder() ) {
1003 listNamespaces();
1004 break;
1006 mSyncState = SYNC_STATE_LIST_SUBFOLDERS;
1007 // Else carry on
1009 case SYNC_STATE_LIST_SUBFOLDERS:
1010 newState( mProgress, i18n("Retrieving folderlist"));
1011 mSyncState = SYNC_STATE_LIST_SUBFOLDERS2;
1012 if ( !listDirectory() ) {
1013 mSyncState = SYNC_STATE_INITIAL;
1014 KMessageBox::error(0, i18n("Error while retrieving the folderlist"));
1016 break;
1018 case SYNC_STATE_LIST_SUBFOLDERS2:
1019 mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
1020 mProgress += 10;
1021 newState( mProgress, i18n("Retrieving subfolders"));
1022 listDirectory2();
1023 break;
1025 case SYNC_STATE_DELETE_SUBFOLDERS:
1026 mSyncState = SYNC_STATE_LIST_MESSAGES;
1027 if ( !foldersForDeletionOnServer.isEmpty() ) {
1028 newState( mProgress, i18n("Deleting folders from server"));
1029 CachedImapJob *job = new CachedImapJob( foldersForDeletionOnServer,
1030 CachedImapJob::tDeleteFolders, this );
1031 connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
1032 connect( job, SIGNAL( finished() ), this, SLOT( slotFolderDeletionOnServerFinished() ) );
1033 job->start();
1034 break;
1036 // Not needed, the next step emits newState very quick
1037 //newState( mProgress, i18n("No folders to delete from server"));
1038 // Carry on
1040 case SYNC_STATE_LIST_MESSAGES:
1041 mSyncState = SYNC_STATE_DELETE_MESSAGES;
1042 if ( !noContent() ) {
1043 newState( mProgress, i18n("Retrieving message list"));
1044 listMessages();
1045 break;
1047 // Else carry on
1049 case SYNC_STATE_DELETE_MESSAGES:
1050 mSyncState = SYNC_STATE_EXPUNGE_MESSAGES;
1051 if ( !noContent() ) {
1052 if ( deleteMessages() ) {
1053 // Fine, we will continue with the next state
1054 } else {
1055 // No messages to delete, skip to GET_MESSAGES
1056 newState( mProgress, i18n("No messages to delete..."));
1057 mSyncState = SYNC_STATE_GET_MESSAGES;
1058 serverSyncInternal();
1060 break;
1062 // Else carry on
1064 case SYNC_STATE_EXPUNGE_MESSAGES:
1065 mSyncState = SYNC_STATE_GET_MESSAGES;
1066 if ( !noContent() ) {
1067 newState( mProgress, i18n("Expunging deleted messages"));
1068 CachedImapJob *job = new CachedImapJob( QString(),
1069 CachedImapJob::tExpungeFolder, this );
1070 connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
1071 connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
1072 job->start();
1073 break;
1075 // Else carry on
1077 case SYNC_STATE_GET_MESSAGES:
1078 mSyncState = SYNC_STATE_HANDLE_INBOX;
1079 if ( !noContent() ) {
1080 if ( !mMsgsForDownload.isEmpty() ) {
1081 newState( mProgress, i18n("Retrieving new messages"));
1082 CachedImapJob *job = new CachedImapJob( mMsgsForDownload,
1083 CachedImapJob::tGetMessage,
1084 this );
1085 connect( job, SIGNAL( progress( unsigned long, unsigned long ) ),
1086 this, SLOT( slotProgress( unsigned long, unsigned long ) ) );
1087 connect( job, SIGNAL( finished() ), this, SLOT( slotUpdateLastUid() ) );
1088 connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
1089 job->start();
1090 mMsgsForDownload.clear();
1091 break;
1092 } else {
1093 newState( mProgress, i18n("No new messages from server"));
1095 * There were no messages to download, but it could be that we
1096 * uploaded some which we didn't need to download again because we
1097 * already knew the uid. Now that we are sure there is nothing to
1098 * download, and everything that had to be deleted on the server
1099 * has been deleted, adjust our local notion of the highest uid
1100 * seen thus far.
1102 slotUpdateLastUid();
1104 if ( mLastUid == 0 && uidWriteTimer == -1 ) {
1105 // This is probably a new and empty folder. Write the UID cache
1106 if ( writeUidCache() == -1 ) {
1107 resetSyncState();
1108 emit folderComplete( this, false );
1109 return;
1115 // Else carry on
1117 case SYNC_STATE_HANDLE_INBOX:
1118 // Wrap up the 'download emails' stage. We always end up at 95 here.
1119 mProgress = 95;
1120 mSyncState = SYNC_STATE_TEST_ANNOTATIONS;
1122 #define KOLAB_FOLDERTEST "/vendor/kolab/folder-test"
1123 case SYNC_STATE_TEST_ANNOTATIONS:
1124 mSyncState = SYNC_STATE_GET_ANNOTATIONS;
1125 // The first folder with user rights to write annotations
1126 if ( !mAccount->annotationCheckPassed() &&
1127 ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) &&
1128 !imapPath().isEmpty() && imapPath() != "/" ) {
1129 kDebug() << "Setting test attribute on folder:"
1130 << folder()->prettyUrl();
1131 newState( mProgress, i18n("Checking annotation support"));
1133 KUrl url = mAccount->getUrl();
1134 url.setPath( imapPath() );
1135 KMail::AnnotationList annotations; // to be set
1137 KMail::AnnotationAttribute attr( KOLAB_FOLDERTEST, "value.shared", "true" );
1138 annotations.append( attr );
1140 kDebug() << "Setting test attribute to"<< url;
1141 KIO::Job *job = AnnotationJobs::multiSetAnnotation( mAccount->slave(),
1142 url, annotations );
1143 ImapAccountBase::jobData jd( url.url(), folder() );
1144 jd.cancellable = true; // we can always do so later
1145 mAccount->insertJob( job, jd );
1146 connect( job, SIGNAL( result( KJob * ) ),
1147 SLOT( slotTestAnnotationResult( KJob * ) ) );
1148 break;
1151 case SYNC_STATE_GET_ANNOTATIONS:
1153 #define KOLAB_FOLDERTYPE "/vendor/kolab/folder-type"
1154 #define KOLAB_INCIDENCESFOR "/vendor/kolab/incidences-for"
1155 #define KOLAB_SHAREDSEEN "/vendor/cmu/cyrus-imapd/sharedseen"
1156 //#define KOLAB_FOLDERTYPE "/comment" //for testing, while cyrus-imap doesn't support /vendor/*
1157 mSyncState = SYNC_STATE_SET_ANNOTATIONS;
1159 bool needToGetInitialAnnotations = false;
1160 if ( !noContent() ) {
1161 // for a folder we didn't create ourselves: get annotation from server
1162 if ( mAnnotationFolderType == "FROMSERVER" ) {
1163 needToGetInitialAnnotations = true;
1164 mAnnotationFolderType.clear();
1165 } else {
1166 updateAnnotationFolderType();
1171 * First retrieve the annotation, so that we know we have to set it
1172 * if it's not set. On the other hand, if the user changed the
1173 * contentstype, there's no need to get first.
1175 if ( !noContent() && mAccount->hasAnnotationSupport() &&
1176 ( kmkernel->iCalIface().isEnabled() || needToGetInitialAnnotations ) ) {
1177 QStringList annotations; // list of annotations to be fetched
1178 if ( !mAnnotationFolderTypeChanged || mAnnotationFolderType.isEmpty() )
1179 annotations << KOLAB_FOLDERTYPE;
1180 if ( !mIncidencesForChanged )
1181 annotations << KOLAB_INCIDENCESFOR;
1182 if ( !mSharedSeenFlagsChanged )
1183 annotations << KOLAB_SHAREDSEEN;
1184 if ( !annotations.isEmpty() ) {
1185 newState( mProgress, i18n("Retrieving annotations"));
1186 KUrl url = mAccount->getUrl();
1187 url.setPath( imapPath() );
1188 AnnotationJobs::MultiGetAnnotationJob *job =
1189 AnnotationJobs::multiGetAnnotation( mAccount->slave(), url, annotations );
1190 ImapAccountBase::jobData jd( url.url(), folder() );
1191 jd.cancellable = true;
1192 mAccount->insertJob(job, jd);
1194 connect( job, SIGNAL(annotationResult(const QString&, const QString&, bool)),
1195 SLOT(slotAnnotationResult(const QString&, const QString&, bool)) );
1196 connect( job, SIGNAL(result(KJob *)),
1197 SLOT(slotGetAnnotationResult(KJob *)) );
1198 break;
1201 } // case
1202 case SYNC_STATE_SET_ANNOTATIONS:
1204 mSyncState = SYNC_STATE_SET_ACLS;
1205 if ( !noContent() && mAccount->hasAnnotationSupport() &&
1206 ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) {
1207 newState( mProgress, i18n("Setting annotations"));
1208 KUrl url = mAccount->getUrl();
1209 url.setPath( imapPath() );
1210 KMail::AnnotationList annotations; // to be set
1211 if ( mAnnotationFolderTypeChanged && !mAnnotationFolderType.isEmpty() ) {
1212 KMail::AnnotationAttribute attr( KOLAB_FOLDERTYPE, "value.shared",
1213 mAnnotationFolderType );
1214 annotations.append( attr );
1215 kDebug() << "Setting folder-type annotation for" << label()
1216 << "to" << mAnnotationFolderType;
1218 if ( mIncidencesForChanged ) {
1219 const QString val = incidencesForToString( mIncidencesFor );
1220 KMail::AnnotationAttribute attr( KOLAB_INCIDENCESFOR, "value.shared",
1221 val );
1222 annotations.append( attr );
1223 kDebug() << "Setting incidences-for annotation for" << label()
1224 << "to" << val;
1226 if ( mSharedSeenFlagsChanged ) {
1227 const QString val = mSharedSeenFlags ? "true" : "false";
1228 KMail::AnnotationAttribute attr( KOLAB_SHAREDSEEN, "value.shared", val );
1229 annotations.append( attr );
1230 kDebug() << "Setting sharedseen annotation for " << label() << " to " << val;
1232 if ( !annotations.isEmpty() ) {
1233 KIO::Job *job =
1234 AnnotationJobs::multiSetAnnotation( mAccount->slave(), url,
1235 annotations );
1236 ImapAccountBase::jobData jd( url.url(), folder() );
1237 jd.cancellable = true; // we can always do so later
1238 mAccount->insertJob(job, jd);
1240 connect(job, SIGNAL(annotationChanged( const QString&, const QString&, const QString& ) ),
1241 SLOT( slotAnnotationChanged( const QString&, const QString&, const QString& ) ));
1242 connect(job, SIGNAL(result(KJob *)),
1243 SLOT(slotSetAnnotationResult(KJob *)));
1244 break;
1248 case SYNC_STATE_SET_ACLS:
1249 mSyncState = SYNC_STATE_GET_ACLS;
1251 if ( !noContent() && mAccount->hasACLSupport() &&
1252 ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) {
1253 bool hasChangedACLs = false;
1254 ACLList::ConstIterator it = mACLList.constBegin();
1255 for ( ; it != mACLList.constEnd() && !hasChangedACLs; ++it ) {
1256 hasChangedACLs = (*it).changed;
1258 if ( hasChangedACLs ) {
1259 newState( mProgress, i18n("Setting permissions"));
1260 KUrl url = mAccount->getUrl();
1261 url.setPath( imapPath() );
1262 KIO::Job *job = KMail::ACLJobs::multiSetACL( mAccount->slave(), url,
1263 mACLList );
1264 ImapAccountBase::jobData jd( url.url(), folder() );
1265 mAccount->insertJob(job, jd);
1267 connect(job, SIGNAL(result(KJob *)),
1268 SLOT(slotMultiSetACLResult(KJob *)));
1269 connect(job, SIGNAL(aclChanged( const QString&, int )),
1270 SLOT(slotACLChanged( const QString&, int )) );
1271 break;
1275 case SYNC_STATE_GET_ACLS:
1276 mSyncState = SYNC_STATE_GET_QUOTA;
1278 if ( !noContent() && mAccount->hasACLSupport() ) {
1279 newState( mProgress, i18n( "Retrieving permissions" ) );
1280 mAccount->getACL( folder(), mImapPath );
1281 connect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
1282 this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
1283 break;
1285 case SYNC_STATE_GET_QUOTA:
1286 // Continue with the subfolders
1287 mSyncState = SYNC_STATE_FIND_SUBFOLDERS;
1288 if ( !noContent() && mAccount->hasQuotaSupport() ) {
1289 newState( mProgress, i18n("Getting quota information"));
1290 KUrl url = mAccount->getUrl();
1291 url.setPath( imapPath() );
1292 KIO::Job *job = KMail::QuotaJobs::getStorageQuota( mAccount->slave(), url );
1293 ImapAccountBase::jobData jd( url.url(), folder() );
1294 mAccount->insertJob(job, jd);
1295 connect( job, SIGNAL( storageQuotaResult( const QuotaInfo& ) ),
1296 SLOT( slotStorageQuotaResult( const QuotaInfo& ) ) );
1297 connect( job, SIGNAL(result(KJob *)),
1298 SLOT(slotQuotaResult(KJob *)) );
1299 break;
1301 case SYNC_STATE_FIND_SUBFOLDERS:
1303 mProgress = 98;
1304 newState( mProgress, i18n("Updating cache file"));
1306 mSyncState = SYNC_STATE_SYNC_SUBFOLDERS;
1307 mSubfoldersForSync.clear();
1308 mCurrentSubfolder = 0;
1309 if ( folder() && folder()->child() ) {
1310 QList<KMFolderNode*>::const_iterator it;
1311 for ( it = folder()->child()->constBegin();
1312 it != folder()->child()->constEnd(); ++it )
1314 KMFolderNode *node = *it;
1315 if ( !node->isDir() ) {
1316 KMFolderCachedImap *storage =
1317 static_cast<KMFolderCachedImap*>( static_cast<KMFolder*>( node )->storage() );
1318 // Only sync folders that have been accepted by the server
1319 if ( !storage->imapPath().isEmpty() &&
1320 // and that were not just deleted from it
1321 !foldersForDeletionOnServer.contains( storage->imapPath() ) ) {
1322 mSubfoldersForSync << storage;
1323 } else {
1324 kDebug() << "Do not add" << storage->label()
1325 << "to synclist";
1331 // All done for this folder.
1332 mProgress = 100; // all done
1333 newState( mProgress, i18n("Synchronization done"));
1334 KUrl url = mAccount->getUrl();
1335 url.setPath( imapPath() );
1336 kmkernel->iCalIface().folderSynced( folder(), url );
1339 if ( !mRecurse ) // "check mail for this folder" only
1340 mSubfoldersForSync.clear();
1342 // Carry on
1343 case SYNC_STATE_SYNC_SUBFOLDERS:
1345 if ( mCurrentSubfolder ) {
1346 disconnect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
1347 this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
1348 mCurrentSubfolder = 0;
1351 if ( mSubfoldersForSync.isEmpty() ) {
1352 mSyncState = SYNC_STATE_INITIAL;
1353 mAccount->addUnreadMsgCount( this, countUnread() ); // before closing
1354 close( "cachedimap" );
1355 emit folderComplete( this, true );
1356 } else {
1357 mCurrentSubfolder = mSubfoldersForSync.front();
1358 mSubfoldersForSync.pop_front();
1359 connect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
1360 this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
1362 assert( !mCurrentSubfolder->imapPath().isEmpty() );
1363 mCurrentSubfolder->setAccount( account() );
1364 bool recurse = mCurrentSubfolder->noChildren() ? false : true;
1365 mCurrentSubfolder->serverSync( recurse );
1368 break;
1370 default:
1371 kDebug() << "WARNING: no such state" << int(mSyncState);
1375 void KMFolderCachedImap::slotConnectionResult( int errorCode, const QString &errorMsg )
1377 disconnect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
1378 this, SLOT( slotConnectionResult(int, const QString&) ) );
1379 if ( !errorCode ) {
1380 // Success
1381 mSyncState = SYNC_STATE_GET_USERRIGHTS;
1382 mProgress += 5;
1383 serverSyncInternal();
1384 } else {
1385 // Error (error message already shown by the account)
1386 newState( mProgress, KIO::buildErrorString( errorCode, errorMsg ) );
1387 emit folderComplete( this, false );
1391 /* find new messages (messages without a UID) */
1392 QList<unsigned long> KMFolderCachedImap::findNewMessages()
1394 QList<unsigned long> result;
1395 for ( int i = 0; i < count(); ++i ) {
1396 KMMsgBase *msg = getMsgBase( i );
1397 if ( !msg ) { // what goes on if getMsg() returns 0?
1398 continue;
1400 if ( msg->UID() == 0 ) {
1401 result.append( msg->getMsgSerNum() );
1404 return result;
1407 /* Upload new messages to server */
1408 void KMFolderCachedImap::uploadNewMessages()
1410 QList<unsigned long> newMsgs = findNewMessages();
1411 if ( !newMsgs.isEmpty() ) {
1412 if ( mUserRights <= 0 || ( mUserRights & ( KMail::ACLJobs::Insert ) ) ) {
1413 newState( mProgress, i18n("Uploading messages to server"));
1414 CachedImapJob *job = new CachedImapJob( newMsgs, CachedImapJob::tPutMessage, this );
1415 connect( job, SIGNAL( progress( unsigned long, unsigned long ) ),
1416 this, SLOT( slotPutProgress( unsigned long, unsigned long ) ) );
1417 connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
1418 job->start();
1419 return;
1420 } else {
1421 KMCommand *command = rescueUnsyncedMessages();
1422 connect( command, SIGNAL( completed( KMCommand * ) ),
1423 this, SLOT( serverSyncInternal() ) );
1425 } else { // nothing to upload
1426 if ( mUserRights != mOldUserRights &&
1427 ( mOldUserRights & KMail::ACLJobs::Insert ) &&
1428 !( mUserRights & KMail::ACLJobs::Insert ) ) {
1429 // write access revoked
1430 KMessageBox::information(
1432 i18n("<p>Your access rights to folder <b>%1</b> have been restricted, "
1433 "it will no longer be possible to add messages to this folder.</p>",
1434 folder()->prettyUrl() ),
1435 i18n("Access rights revoked"), "KMailACLRevocationNotification" );
1438 newState( mProgress, i18n("No messages to upload to server"));
1439 serverSyncInternal();
1442 /* Progress info during uploadNewMessages */
1443 void KMFolderCachedImap::slotPutProgress( unsigned long done, unsigned long total )
1445 // (going from mProgress to mProgress+10)
1446 int progressSpan = 10;
1447 newState( mProgress + ( progressSpan * done ) / total, QString() );
1448 if ( done == total ) { // we're done
1449 mProgress += progressSpan;
1453 /* Upload message flags to server */
1454 void KMFolderCachedImap::uploadFlags()
1456 if ( !uidMap.isEmpty() ) {
1457 mStatusFlagsJobs = 0;
1458 newState( mProgress, i18n("Uploading status of messages to server"));
1460 // FIXME DUPLICATED FROM KMFOLDERIMAP
1461 QMap< QString, QStringList > groups;
1462 //open(); //already done
1463 for ( int i = 0; i < count(); ++i ) {
1464 KMMsgBase *msg = getMsgBase( i );
1465 if ( !msg || msg->UID() == 0 ) {
1466 // Either not a valid message or not one that is on the server yet
1467 continue;
1469 if ( !mUIDsOfLocallyChangedStatuses.contains( msg->UID() ) && !mStatusChangedLocally ) {
1470 // This message has not had its status changed locally
1471 continue;
1474 QString flags = KMFolderImap::statusToFlags( msg->status(), mPermanentFlags );
1475 // Collect uids for each typem of flags.
1476 QString uid;
1477 uid.setNum( msg->UID() );
1478 groups[flags].append( uid );
1480 QMap< QString, QStringList >::Iterator dit;
1481 for ( dit = groups.begin(); dit != groups.end(); ++dit ) {
1482 QByteArray flags = dit.key().toLatin1();
1483 QStringList sets = KMFolderImap::makeSets( (*dit), true );
1484 mStatusFlagsJobs += sets.count(); // ### that's not in kmfolderimap....
1485 // Send off a status setting job for each set.
1486 for ( QStringList::Iterator slit = sets.begin(); slit != sets.end(); ++slit ) {
1487 QString imappath = imapPath() + ";UID=" + ( *slit );
1488 mAccount->setImapStatus( folder(), imappath, flags );
1491 // FIXME END DUPLICATED FROM KMFOLDERIMAP
1493 if ( mStatusFlagsJobs ) {
1494 connect( mAccount, SIGNAL( imapStatusChanged( KMFolder*, const QString&, bool ) ),
1495 this, SLOT( slotImapStatusChanged( KMFolder*, const QString&, bool ) ) );
1496 return;
1499 newState( mProgress, i18n("No messages to upload to server") );
1500 serverSyncInternal();
1503 void KMFolderCachedImap::uploadSeenFlags()
1505 if ( !uidMap.isEmpty() ) {
1506 mStatusFlagsJobs = 0;
1507 newState( mProgress, i18n("Uploading status of messages to server"));
1509 QList<ulong> seenUids, unseenUids;
1510 for( int i = 0; i < count(); ++i ) {
1511 KMMsgBase* msg = getMsgBase( i );
1512 if( !msg || msg->UID() == 0 )
1513 // Either not a valid message or not one that is on the server yet
1514 continue;
1516 if ( !mUIDsOfLocallyChangedStatuses.contains( msg->UID() ) && !mStatusChangedLocally ) {
1517 // This message has not had its status changed locally
1518 continue;
1521 if ( msg->status().isOld() || msg->status().isRead() )
1522 seenUids.append( msg->UID() );
1523 else
1524 unseenUids.append( msg->UID() );
1526 if ( !seenUids.isEmpty() ) {
1527 QStringList sets = KMFolderImap::makeSets( seenUids, true );
1528 mStatusFlagsJobs += sets.count();
1529 for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) {
1530 QString imappath = imapPath() + ";UID=" + ( *it );
1531 mAccount->setImapSeenStatus( folder(), imappath, true );
1534 if ( !unseenUids.isEmpty() ) {
1535 QStringList sets = KMFolderImap::makeSets( unseenUids, true );
1536 mStatusFlagsJobs += sets.count();
1537 for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) {
1538 QString imappath = imapPath() + ";UID=" + ( *it );
1539 mAccount->setImapSeenStatus( folder(), imappath, false );
1543 if ( mStatusFlagsJobs ) {
1544 connect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ),
1545 this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) );
1546 return;
1549 newState( mProgress, i18n("No messages to upload to server"));
1550 serverSyncInternal();
1553 void KMFolderCachedImap::slotImapStatusChanged( KMFolder *folder, const QString&, bool cont )
1555 if ( mSyncState == SYNC_STATE_INITIAL ) {
1556 //kDebug() << "IMAP status changed but reset";
1557 return; // we were reset
1559 //kDebug() << "IMAP status changed for folder: " << folder->prettyUrk();
1560 if ( folder->storage() == this ) {
1561 --mStatusFlagsJobs;
1562 if ( mStatusFlagsJobs == 0 || !cont ) { // done or aborting
1563 disconnect( mAccount, SIGNAL( imapStatusChanged( KMFolder*, const QString&, bool ) ),
1564 this, SLOT( slotImapStatusChanged( KMFolder*, const QString&, bool ) ) );
1566 if ( mStatusFlagsJobs == 0 && cont ) {
1567 mProgress += 5;
1568 serverSyncInternal();
1569 //kDebug() << "Proceeding with mailcheck.";
1574 // This is not perfect, what if the status didn't really change? Oh well ...
1575 void KMFolderCachedImap::setStatus( int idx, const MessageStatus &status, bool toggle )
1577 KMFolderMaildir::setStatus( idx, status, toggle );
1578 const KMMsgBase *msg = getMsgBase( idx );
1579 Q_ASSERT( msg );
1580 if ( msg )
1581 mUIDsOfLocallyChangedStatuses.insert( msg->UID() );
1584 void KMFolderCachedImap::setStatus( QList<int> &ids, const MessageStatus &status, bool toggle )
1586 KMFolderMaildir::setStatus( ids, status, toggle );
1587 foreach( const int id, ids ) {
1588 const KMMsgBase *msg = getMsgBase( id );
1589 Q_ASSERT( msg );
1590 if ( msg )
1591 mUIDsOfLocallyChangedStatuses.insert( msg->UID() );
1595 /* Upload new folders to server */
1596 void KMFolderCachedImap::createNewFolders()
1598 QList<KMFolderCachedImap*> newFolders = findNewFolders();
1599 if ( !newFolders.isEmpty() ) {
1600 newState( mProgress, i18n("Creating subfolders on server"));
1601 CachedImapJob *job = new CachedImapJob( newFolders, CachedImapJob::tAddSubfolders, this );
1602 connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
1603 connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
1604 job->start();
1605 } else {
1606 serverSyncInternal();
1610 QList<KMFolderCachedImap*> KMFolderCachedImap::findNewFolders()
1612 QList<KMFolderCachedImap*> newFolders;
1613 if ( folder() && folder()->child() ) {
1614 QList<KMFolderNode*>::const_iterator it;
1615 for ( it = folder()->child()->constBegin(); it != folder()->child()->constEnd(); ++it ) {
1616 KMFolderNode *node = *it;
1617 if ( !node->isDir() ) {
1618 if ( static_cast<KMFolder*>(node)->folderType() != KMFolderTypeCachedImap ) {
1619 kError() << "ARGH!!!"
1620 << node->name() << " is not an IMAP folder\n";
1621 assert( 0 );
1623 KMFolderCachedImap *folder =
1624 static_cast<KMFolderCachedImap*>( static_cast<KMFolder*>( node )->storage() );
1625 if ( folder->imapPath().isEmpty() ) {
1626 newFolders << folder;
1631 return newFolders;
1634 bool KMFolderCachedImap::deleteMessages()
1636 /* Delete messages from cache that are gone from the server */
1637 QList<KMMsgBase*> msgsForDeletion;
1640 * It is not possible to just go over all indices and remove
1641 * them one by one because the index list can get resized under
1642 * us. So use msg pointers instead.
1644 QStringList uids;
1645 QMap<ulong,int>::const_iterator it = uidMap.constBegin();
1646 for ( ; it != uidMap.constEnd(); ++it ) {
1647 ulong uid ( it.key() );
1648 if ( uid != 0 && !uidsOnServer.contains( uid ) ) {
1649 uids << QString::number( uid );
1650 msgsForDeletion.append( getMsgBase( *it ) );
1654 if ( !msgsForDeletion.isEmpty() ) {
1655 #if MAIL_LOSS_DEBUGGING
1656 if ( KMessageBox::warningYesNo(
1657 0, i18n( "<qt><p>Mails on the server in folder <b>%1</b> were deleted. "
1658 "Do you want to delete them locally?</p><p>UIDs: %2</p></qt>",
1659 folder()->prettyUrl(), uids.join(",") ) ) == KMessageBox::Yes )
1660 #endif
1661 removeMessages( msgsForDeletion );
1664 if ( mUserRights > 0 && !( mUserRights & KMail::ACLJobs::Delete ) ) {
1665 return false;
1668 /* Delete messages from the server that we don't have anymore */
1669 if ( !uidsForDeletionOnServer.isEmpty() ) {
1670 newState( mProgress, i18n("Deleting removed messages from server"));
1671 QStringList sets = KMFolderImap::makeSets( uidsForDeletionOnServer, true );
1672 uidsForDeletionOnServer.clear();
1673 kDebug() << "Deleting" << sets.count()
1674 << "sets of messages from server folder" << imapPath();
1675 CachedImapJob *job =
1676 new CachedImapJob( sets, CachedImapJob::tDeleteMessage, this );
1677 connect( job, SIGNAL( result( KMail::FolderJob * ) ),
1678 this, SLOT( slotDeleteMessagesResult( KMail::FolderJob * ) ) );
1679 job->start();
1680 return true;
1681 } else {
1683 // Nothing to delete on the server, make sure the map is clear again.
1684 // Normally this wouldn't be necessary, but there can be stale maps because of
1685 // https://issues.kolab.org/issue3833.
1686 mDeletedUIDsSinceLastSync.clear();
1687 return false;
1691 void KMFolderCachedImap::slotDeleteMessagesResult( KMail::FolderJob *job )
1693 if ( job->error() ) {
1694 // Skip the EXPUNGE state if deleting didn't work, no need to show two error messages
1695 mSyncState = SYNC_STATE_GET_MESSAGES;
1696 } else {
1697 // deleting on the server went fine, clear the pending deletions cache
1698 mDeletedUIDsSinceLastSync.clear();
1700 mProgress += 10;
1701 serverSyncInternal();
1704 void KMFolderCachedImap::checkUidValidity()
1707 * IMAP root folders don't seem to have a UID validity setting.
1708 * Also, don't try the uid validity on new folders.
1710 if ( imapPath().isEmpty() || imapPath() == "/" ) {
1711 // Just proceed
1712 serverSyncInternal();
1713 } else {
1714 newState( mProgress, i18n("Checking folder validity"));
1715 CachedImapJob *job = new CachedImapJob( FolderJob::tCheckUidValidity, this );
1716 connect( job, SIGNAL(permanentFlags(int)), SLOT(slotPermanentFlags(int)) );
1717 connect( job, SIGNAL( result( KMail::FolderJob* ) ),
1718 this, SLOT( slotCheckUidValidityResult( KMail::FolderJob* ) ) );
1719 job->start();
1723 void KMFolderCachedImap::slotCheckUidValidityResult( KMail::FolderJob *job )
1725 if ( job->error() ) { // there was an error and the user chose "continue"
1727 * We can't continue doing anything in the same folder though,
1728 * it would delete all mails. But we can continue to subfolders
1729 * if any. Well we can also try annotation/acl stuff...
1731 mSyncState = SYNC_STATE_HANDLE_INBOX;
1733 mProgress += 5;
1734 serverSyncInternal();
1737 void KMFolderCachedImap::slotPermanentFlags(int flags)
1739 mPermanentFlags = flags;
1742 void KMFolderCachedImap::listMessages()
1744 bool groupwareOnly =
1745 GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount() &&
1746 GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id() &&
1747 folder()->isSystemFolder() && mImapPath == "/INBOX/";
1750 * Don't list messages on the root folder, and skip the inbox, if this is
1751 * the inbox of a groupware-only dimap account.
1753 if ( imapPath() == "/" || groupwareOnly ) {
1754 serverSyncInternal();
1755 return;
1758 if ( !mAccount->slave() ) { // sync aborted
1759 resetSyncState();
1760 emit folderComplete( this, false );
1761 return;
1763 uidsOnServer.clear();
1764 uidsOnServer.reserve( count() * 2 );
1765 uidsForDeletionOnServer.clear();
1766 mMsgsForDownload.clear();
1767 mUidsForDownload.clear();
1768 // listing is only considered successful if saw a syntactically correct imapdigest
1769 mFoundAnIMAPDigest = false;
1771 CachedImapJob *job = new CachedImapJob( FolderJob::tListMessages, this );
1772 connect( job, SIGNAL( result( KMail::FolderJob * ) ),
1773 this, SLOT( slotGetLastMessagesResult( KMail::FolderJob * ) ) );
1774 job->start();
1777 void KMFolderCachedImap::slotGetLastMessagesResult( KMail::FolderJob *job )
1779 getMessagesResult( job, true );
1782 // Connected to the listMessages job in CachedImapJob
1783 void KMFolderCachedImap::slotGetMessagesData( KIO::Job *job, const QByteArray &data )
1785 KMAcctCachedImap::JobIterator it = mAccount->findJob( job );
1786 if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
1787 kDebug() << "could not find job!?!?!";
1789 * Be sure to reset the sync state, if the listing was partial we
1790 * would otherwise delete not-listed mail locally, and on the next
1791 * sync on the server as well.
1793 mSyncState = SYNC_STATE_HANDLE_INBOX;
1794 serverSyncInternal(); /* HACK^W Fix: we should at least try to keep going */
1795 return;
1797 (*it).cdata += data;
1798 int pos = (*it).cdata.indexOf("\r\n--IMAPDIGEST");
1799 if ( pos > 0 ) {
1800 int a = (*it).cdata.indexOf("\r\nX-uidValidity:");
1801 if (a != -1) {
1802 int b = (*it).cdata.indexOf("\r\n", a + 17);
1803 setUidValidity((*it).cdata.mid(a + 17, b - a - 17));
1805 a = (*it).cdata.indexOf("\r\nX-Access:");
1808 * Only trust X-Access (i.e. the imap select info) if we don't know
1809 * mUserRights. The latter is more accurate (checked on every sync)
1810 * whereas X-Access is only updated when selecting the folder again,
1811 * which might not happen if using RMB / Check Mail in this folder.
1812 * We don't need two (potentially conflicting) sources for the
1813 * readonly setting, in any case.
1815 if ( a != -1 && mUserRights == -1 ) {
1816 int b = (*it).cdata.indexOf( "\r\n", a + 12 );
1817 const QString access = (*it).cdata.mid( a + 12, b - a - 12 );
1818 setReadOnly( access == "Read only" );
1820 (*it).cdata.remove( 0, pos );
1821 mFoundAnIMAPDigest = true;
1823 pos = (*it).cdata.indexOf( "\r\n--IMAPDIGEST", 1 );
1825 // Start with something largish when rebuilding the cache
1826 if ( uidsOnServer.size() == 0 ) {
1827 uidsOnServer.reserve( KMail::nextPrime( 2000 ) );
1830 while ( pos >= 0 ) {
1832 KMMessage msg;
1833 msg.fromString((*it).cdata.mid(16, pos - 16));
1834 const int flags = msg.headerField("X-Flags").toInt();
1835 const ulong size = msg.headerField("X-Length").toULong();
1836 const ulong uid = msg.UID();
1838 // The below is optimized for speed, not prettiness. The commented out chunk
1839 // above was the solution copied from kmfolderimap, and it's 15-20% slower.
1840 const QByteArray& entry( (*it).cdata );
1841 const int indexOfUID = entry.indexOf("X-UID", 16);
1842 const int startOfUIDValue = indexOfUID + 7;
1843 const int indexOfLength = entry.indexOf("X-Length", startOfUIDValue ); // we know length comes after UID
1844 const int startOfLengthValue = indexOfLength + 10;
1845 const int indexOfFlags = entry.indexOf("X-Flags", startOfLengthValue ); // we know flags comes last
1846 const int startOfFlagsValue = indexOfFlags + 9;
1848 const int flags = entry.mid( startOfFlagsValue, entry.indexOf( '\r', startOfFlagsValue ) - startOfFlagsValue ).toInt();
1849 const ulong size = entry.mid( startOfLengthValue, entry.indexOf( '\r', startOfLengthValue ) - startOfLengthValue ).toULong();
1850 const ulong uid = entry.mid( startOfUIDValue, entry.indexOf( '\r', startOfUIDValue ) - startOfUIDValue ).toULong();
1852 const bool deleted = ( flags & 8 );
1854 if ( !deleted ) {
1855 if ( uid != 0 ) {
1856 if ( uidsOnServer.capacity() <= uidsOnServer.size() ) {
1857 uidsOnServer.reserve( KMail::nextPrime( uidsOnServer.size() * 2 ) );
1858 //kDebug() << "Resizing to:" << uidsOnServer.capacity();
1860 uidsOnServer.insert( uid );
1862 bool redownload = false;
1863 if ( uid <= lastUid() ) {
1865 * If this message UID is not present locally, then it must
1866 * have been deleted by the user, so we delete it on the
1867 * server also. If we don't have delete permissions on the server,
1868 * re-download the message, it must have vanished by some error, or
1869 * while we still thought we were allowed to delete (ACL change).
1871 * This relies heavily on lastUid() being correct at all times.
1873 KMMsgBase *existingMessage = findByUID( uid );
1874 if ( !existingMessage ) {
1875 #if MAIL_LOSS_DEBUGGING
1876 kDebug() << "Looking at uid " << uid << " high water is: " << lastUid() << " we should delete it";
1877 #endif
1878 // double check we deleted it since the last sync
1879 if ( mDeletedUIDsSinceLastSync.contains(uid) ) {
1880 if ( mUserRights <= 0 || ( mUserRights & KMail::ACLJobs::Delete ) ) {
1881 #if MAIL_LOSS_DEBUGGING
1882 kDebug() << "message with uid " << uid << " is gone from local cache. Must be deleted on server!!!";
1883 #endif
1884 uidsForDeletionOnServer << uid;
1885 } else {
1886 redownload = true;
1888 } else {
1889 kDebug() << "WARNING: ####### ";
1890 kDebug() << "Message locally missing but not deleted in folder: " << folder()->prettyUrl();
1891 kDebug() << "The missing UID: " << uid << ". It will be redownloaded ";
1892 redownload = true;
1895 } else {
1897 * If this is a read only folder, ignore status updates from the
1898 * server since we can't write our status back our local version
1899 * is what has to be considered correct.
1901 if ( !mReadOnly || !GlobalSettings::allowLocalFlags() ) {
1902 /* The message is OK, update flags */
1903 KMFolderImap::flagsToStatus( existingMessage, flags, false, mReadOnly ? INT_MAX : mPermanentFlags );
1904 } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) {
1905 KMFolderImap::seenFlagToStatus( existingMessage, flags );
1909 if ( uid > lastUid() || redownload ) {
1910 #if MAIL_LOSS_DEBUGGING
1911 kDebug() << "Looking at uid " << uid << " high water is: " << lastUid() << " we should download it";
1912 #endif
1914 * The message is new since the last sync, but we might have
1915 * just uploaded it, in which case the uid map already contains it.
1917 if ( !uidMap.contains( uid ) ) {
1918 mMsgsForDownload << KMail::CachedImapJob::MsgForDownload( uid, flags, size );
1919 if ( imapPath() == "/INBOX/" ) {
1920 mUidsForDownload << uid;
1923 // Remember the highest uid and once the download is completed, update mLastUid
1924 if ( uid > mTentativeHighestUid ) {
1925 #if MAIL_LOSS_DEBUGGING
1926 kDebug() << "Setting the tentative highest UID to: " << uid;
1927 #endif
1928 mTentativeHighestUid = uid;
1932 (*it).cdata.remove( 0, pos );
1933 (*it).done++;
1934 pos = (*it).cdata.indexOf( "\r\n--IMAPDIGEST", 1 );
1938 void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet )
1940 mProgress += 10;
1941 if ( !job->error() && !mFoundAnIMAPDigest ) {
1942 kWarning() << "######## Folderlisting did not complete, but there was no error! "
1943 "Aborting sync of folder: " << folder()->prettyUrl();
1944 #if MAIL_LOSS_DEBUGGING
1945 kmkernel->emergencyExit( i18n("Folder listing failed in interesting ways." ) );
1946 #endif
1948 if ( job->error() ) { // error listing messages but the user chose to continue
1949 mContentState = imapNoInformation;
1950 mSyncState = SYNC_STATE_HANDLE_INBOX; // be sure not to continue in this folder
1951 } else {
1952 if ( lastSet ) { // always true here (this comes from online-imap...)
1953 mContentState = imapFinished;
1954 mUIDsOfLocallyChangedStatuses.clear(); // we are up to date again
1955 mStatusChangedLocally = false;
1958 serverSyncInternal();
1961 void KMFolderCachedImap::slotProgress( unsigned long done, unsigned long total )
1963 int progressSpan = 100 - 5 - mProgress;
1964 int additionalProgress = ( total == 0 ) ?
1965 progressSpan :
1966 ( progressSpan * done ) / total;
1968 // Progress info while retrieving new emails
1969 // (going from mProgress to mProgress+progressSpan)
1970 newState( mProgress + additionalProgress, QString() );
1973 void KMFolderCachedImap::setAccount( KMAcctCachedImap *aAccount )
1975 assert( ::qobject_cast<KMAcctCachedImap *>( aAccount ) );
1976 mAccount = aAccount;
1977 if ( imapPath() == "/" ) {
1978 aAccount->setFolder( folder() );
1981 // Folder was renamed in a previous session, and the user didn't sync yet
1982 QString newName = mAccount->renamedFolder( imapPath() );
1983 if ( !newName.isEmpty() ) {
1984 folder()->setLabel( newName );
1987 if ( !folder() || !folder()->child() || !folder()->child()->count() ) {
1988 return;
1991 QList<KMFolderNode*>::const_iterator it;
1992 for ( it = folder()->child()->constBegin(); it != folder()->child()->constEnd(); ++it ) {
1993 KMFolderNode *node = *it;
1994 if ( !node->isDir() ) {
1995 static_cast<KMFolderCachedImap*>(
1996 static_cast<KMFolder*>( node )->storage() )->setAccount( aAccount );
2001 void KMFolderCachedImap::listNamespaces()
2003 ImapAccountBase::ListType type = ImapAccountBase::List;
2004 if ( mAccount->onlySubscribedFolders() ) {
2005 type = ImapAccountBase::ListSubscribed;
2008 kDebug() << "listNamespaces" << mNamespacesToList;
2009 if ( mNamespacesToList.isEmpty() ) {
2010 mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
2011 mPersonalNamespacesCheckDone = true;
2013 QStringList ns = mAccount->namespaces()[ImapAccountBase::OtherUsersNS];
2014 ns += mAccount->namespaces()[ImapAccountBase::SharedNS];
2015 mNamespacesToCheck = ns.count();
2016 for ( QStringList::Iterator it = ns.begin(); it != ns.end(); ++it ) {
2017 if ( (*it).isEmpty() ) {
2018 // ignore empty listings as they have been listed before
2019 --mNamespacesToCheck;
2020 continue;
2022 KMail::ListJob *job =
2023 new KMail::ListJob( mAccount, type, this, mAccount->addPathToNamespace( *it ) );
2024 job->setHonorLocalSubscription( true );
2025 connect( job, SIGNAL( receivedFolders( const QStringList&, const QStringList&,
2026 const QStringList&, const QStringList&,
2027 const ImapAccountBase::jobData& ) ),
2028 this, SLOT( slotCheckNamespace( const QStringList&, const QStringList&,
2029 const QStringList&, const QStringList&,
2030 const ImapAccountBase::jobData& ) ) );
2031 job->start();
2033 if ( mNamespacesToCheck == 0 ) {
2034 serverSyncInternal();
2036 return;
2038 mPersonalNamespacesCheckDone = false;
2040 QString ns = mNamespacesToList.front();
2041 mNamespacesToList.pop_front();
2043 mSyncState = SYNC_STATE_LIST_SUBFOLDERS2;
2044 newState( mProgress, i18n("Retrieving folders for namespace %1", ns) );
2045 KMail::ListJob *job = new KMail::ListJob( mAccount, type, this,
2046 mAccount->addPathToNamespace( ns ) );
2047 job->setNamespace( ns );
2048 job->setHonorLocalSubscription( true );
2049 connect( job, SIGNAL( receivedFolders( const QStringList&, const QStringList&,
2050 const QStringList&, const QStringList&,
2051 const ImapAccountBase::jobData& ) ),
2052 this, SLOT( slotListResult( const QStringList&, const QStringList&,
2053 const QStringList&, const QStringList&,
2054 const ImapAccountBase::jobData& ) ) );
2055 job->start();
2058 void KMFolderCachedImap::slotCheckNamespace( const QStringList &subfolderNames,
2059 const QStringList &subfolderPaths,
2060 const QStringList &subfolderMimeTypes,
2061 const QStringList &subfolderAttributes,
2062 const ImapAccountBase::jobData &jobData )
2064 Q_UNUSED( subfolderPaths );
2065 Q_UNUSED( subfolderMimeTypes );
2066 Q_UNUSED( subfolderAttributes );
2067 --mNamespacesToCheck;
2068 kDebug() << "slotCheckNamespace" << subfolderNames <<",remain=" <<
2069 mNamespacesToCheck;
2071 // get a correct foldername:
2072 // strip / and make sure it does not contain the delimiter
2073 QString name = jobData.path.mid( 1, jobData.path.length() - 2 );
2074 name.remove( mAccount->delimiterForNamespace( name ) );
2075 if ( name.isEmpty() ) {
2076 // should not happen
2077 kWarning() <<"slotCheckNamespace: ignoring empty folder!";
2078 return;
2081 folder()->createChildFolder();
2082 QList<KMFolderNode*>::const_iterator it;
2083 KMFolderNode *node = 0;
2084 for ( it = folder()->child()->constBegin(); it != folder()->child()->constEnd(); ++it ) {
2085 if ( !(*it)->isDir() && (*it)->name() == name ) {
2086 node = *it;
2087 break;
2090 if ( !subfolderNames.isEmpty() ) {
2091 if ( node ) {
2092 // folder exists so we have nothing to do - it will be listed later
2093 kDebug() << "found namespace folder" << name;
2094 } else {
2095 // create folder
2096 kDebug() << "create namespace folder" << name;
2097 KMFolder *newFolder = folder()->child()->createFolder( name, false,
2098 KMFolderTypeCachedImap );
2099 if ( newFolder ) {
2100 KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>( newFolder->storage() );
2101 f->setImapPath( mAccount->addPathToNamespace( name ) );
2102 f->setNoContent( true );
2103 f->setAccount( mAccount );
2104 f->close( "cachedimap" );
2105 kmkernel->dimapFolderMgr()->contentsChanged();
2108 } else {
2109 if ( node ) {
2110 kDebug() << "delete namespace folder" << name;
2111 KMFolder *fld = static_cast<KMFolder*>( node );
2112 kmkernel->dimapFolderMgr()->remove( fld );
2116 if ( mNamespacesToCheck == 0 ) {
2117 // all namespaces are done so continue with the next step
2118 serverSyncInternal();
2122 bool KMFolderCachedImap::listDirectory()
2124 if ( !mAccount->slave() ) { // sync aborted
2125 resetSyncState();
2126 emit folderComplete( this, false );
2127 return false;
2129 mSubfolderState = imapInProgress;
2131 // get the folders
2132 ImapAccountBase::ListType type = ImapAccountBase::List;
2133 if ( mAccount->onlySubscribedFolders() ) {
2134 type = ImapAccountBase::ListSubscribed;
2136 KMail::ListJob *job = new KMail::ListJob( mAccount, type, this );
2137 job->setHonorLocalSubscription( true );
2138 connect( job, SIGNAL( receivedFolders( const QStringList&, const QStringList&,
2139 const QStringList&, const QStringList&,
2140 const ImapAccountBase::jobData& ) ),
2141 this, SLOT( slotListResult( const QStringList&, const QStringList&,
2142 const QStringList&, const QStringList&,
2143 const ImapAccountBase::jobData& ) ) );
2144 job->start();
2146 return true;
2149 void KMFolderCachedImap::slotListResult( const QStringList &folderNames,
2150 const QStringList &folderPaths,
2151 const QStringList &folderMimeTypes,
2152 const QStringList &folderAttributes,
2153 const ImapAccountBase::jobData &jobData )
2155 Q_UNUSED( jobData );
2157 mSubfolderNames = folderNames;
2158 mSubfolderPaths = folderPaths;
2159 mSubfolderMimeTypes = folderMimeTypes;
2160 mSubfolderAttributes = folderAttributes;
2162 mSubfolderState = imapFinished;
2164 folder()->createChildFolder();
2165 bool root = ( this == mAccount->rootFolder() );
2167 QList<KMFolder*> toRemove;
2168 bool emptyList = ( root && mSubfolderNames.empty() );
2169 if ( !emptyList ) {
2170 QList<KMFolderNode*>::const_iterator it;
2171 for ( it = folder()->child()->constBegin(); it != folder()->child()->constEnd(); ++it ) {
2172 KMFolderNode *node = *it;
2173 if (!node->isDir() ) {
2174 KMFolderCachedImap *f =
2175 static_cast<KMFolderCachedImap*>( static_cast<KMFolder*>( node )->storage() );
2177 if ( !mSubfolderNames.contains(node->name()) ) {
2178 QString name = node->name();
2179 // as more than one namespace can be listed in the root folder we need to make sure
2180 // that the folder is within the current namespace
2181 bool isInNamespace = ( jobData.curNamespace.isEmpty() ||
2182 jobData.curNamespace == mAccount->namespaceForFolder( f ) );
2183 // ignore some cases
2184 bool ignore = root && ( f->imapPath() == "/INBOX/" ||
2185 mAccount->isNamespaceFolder( name ) || !isInNamespace );
2187 // This subfolder isn't present on the server
2188 if ( !f->imapPath().isEmpty() && !ignore ) {
2189 // The folder has an imap path set, so it has been
2190 // on the server before. Delete it locally.
2191 toRemove.append( f->folder() );
2192 kDebug() << node->name()
2193 << "isn't on the server. It has an imapPath -> delete it locally";
2195 } else { // folder both local and on server
2196 //kDebug() << node->name() << "is on the server.";
2199 * Store the folder attributes for every subfolder.
2201 int index = mSubfolderNames.indexOf( node->name() );
2202 f->mFolderAttributes = folderAttributes[ index ];
2204 } else {
2205 //kDebug() << "skipping dir node:" << node->name();
2210 QList<KMFolder*>::const_iterator jt;
2211 for ( jt = toRemove.constBegin(); jt != toRemove.constEnd(); ++jt ) {
2212 if ( *jt ) {
2213 rescueUnsyncedMessagesAndDeleteFolder( *jt );
2217 mProgress += 5;
2219 // just in case there is nothing to rescue
2220 slotRescueDone( 0 );
2223 void KMFolderCachedImap::listDirectory2()
2225 QString path = folder()->path();
2226 kmkernel->dimapFolderMgr()->quiet( true );
2228 bool root = ( this == mAccount->rootFolder() );
2229 if ( root && !mAccount->hasInbox() ) {
2230 KMFolderCachedImap *f = 0;
2231 KMFolderNode *node = 0;
2232 //kDebug() << "check INBOX";
2233 // create the INBOX
2234 QList<KMFolderNode*>::const_iterator it = folder()->child()->constBegin();
2235 for ( ; it != folder()->child()->constEnd(); ++it ) {
2236 if ( !(*it)->isDir() && (*it)->name() == "INBOX" ) {
2237 node = *it;
2238 break;
2241 if ( node ) {
2242 f = static_cast<KMFolderCachedImap*>( static_cast<KMFolder*>( node )->storage() );
2243 } else {
2244 KMFolder *newFolder =
2245 folder()->child()->createFolder( "INBOX", true, KMFolderTypeCachedImap );
2246 if ( newFolder ) {
2247 f = static_cast<KMFolderCachedImap*>( newFolder->storage() );
2250 if ( f ) {
2251 f->setAccount( mAccount );
2252 f->setImapPath( "/INBOX/" );
2253 f->folder()->setLabel( i18n("inbox") );
2255 if ( !node ) {
2256 if ( f ) {
2257 f->close( "cachedimap" );
2259 kmkernel->dimapFolderMgr()->contentsChanged();
2261 // so we have an INBOX
2262 mAccount->setHasInbox( true );
2265 if ( root && !mSubfolderNames.isEmpty() ) {
2266 KMFolderCachedImap *parent =
2267 findParent( mSubfolderPaths.first(), mSubfolderNames.first() );
2268 if ( parent ) {
2269 kDebug() << "Pass listing to" << parent->label();
2270 mSubfolderNames.clear();
2274 // Find all subfolders present on server but not on disk
2275 QVector<int> foldersNewOnServer;
2276 for (int i = 0; i < mSubfolderNames.count(); i++) {
2278 // Find the subdir, if already present
2279 KMFolderCachedImap *f = 0;
2280 KMFolderNode *node = 0;
2281 for ( QList<KMFolderNode*>::ConstIterator it = folder()->child()->constBegin();
2282 it != folder()->child()->constEnd(); ++it )
2283 if ( !(*it)->isDir() && (*it)->name() == mSubfolderNames[i] ) {
2284 node = *it;
2285 break;
2288 if ( !node ) {
2289 // This folder is not present here
2290 // Either it's new on the server, or we just deleted it.
2291 QString subfolderPath = mSubfolderPaths[i];
2292 // The code used to look at the uidcache to know if it was "just deleted".
2293 // But this breaks with noContent folders and with shared folders.
2294 // So instead we keep a list in the account.
2295 bool locallyDeleted = mAccount->isDeletedFolder( subfolderPath );
2296 // That list is saved/restored across sessions, but to avoid any mistake,
2297 // ask for confirmation if the folder was deleted in a previous session
2298 // (could be that the folder was deleted & recreated meanwhile from another client...)
2299 if ( !locallyDeleted && mAccount->isPreviouslyDeletedFolder( subfolderPath ) ) {
2300 locallyDeleted = KMessageBox::warningYesNo(
2302 i18n("<qt><p>It seems that the folder <b>%1</b> was deleted. "
2303 "Do you want to delete it from the server?</p></qt>",
2304 mSubfolderNames[i] ), QString(),
2305 KStandardGuiItem::del(), KStandardGuiItem::cancel() ) == KMessageBox::Yes;
2308 if ( locallyDeleted ) {
2309 kDebug() << subfolderPath
2310 << "was deleted locally => delete on server.";
2311 // grab all subsubfolders too
2312 foldersForDeletionOnServer += mAccount->deletedFolderPaths( subfolderPath );
2313 } else {
2314 kDebug() << subfolderPath
2315 << "is a new folder on the server => create local cache";
2316 foldersNewOnServer.append( i );
2318 } else { // Folder found locally
2319 if ( static_cast<KMFolder*>(node)->folderType() == KMFolderTypeCachedImap ) {
2320 f = dynamic_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
2322 if ( f ) {
2323 // kDebug() << "folder("<<f->name()<<")->imapPath()=" << f->imapPath()
2324 // << "\nSetting imapPath" << mSubfolderPaths[i];
2325 // Write folder settings
2326 f->setAccount( mAccount );
2327 f->setNoContent( mSubfolderMimeTypes[i] == "inode/directory" );
2328 f->setNoChildren( mSubfolderMimeTypes[i] == "message/digest" );
2329 f->setImapPath( mSubfolderPaths[i] );
2334 /* In case we are ignoring non-groupware folders, and this is the groupware
2335 * main account, find out the contents types of folders that have newly
2336 * appeared on the server. Otherwise just create them and finish listing.
2337 * If a folder is already known to be locally unsubscribed, it won't be
2338 * listed at all, on this level, so these are only folders that we are
2339 * seeing for the first time. */
2341 /* Note: We ask the globalsettings, and not the current state of the
2342 * kmkernel->iCalIface().isEnabled(), since that is false during the
2343 * very first sync, where we already want to filter. */
2344 if ( GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount() &&
2345 GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id() &&
2346 mAccount->hasAnnotationSupport() &&
2347 GlobalSettings::self()->theIMAPResourceEnabled() &&
2348 !foldersNewOnServer.isEmpty() ) {
2350 QStringList paths;
2351 for ( int i = 0; i < foldersNewOnServer.count(); ++i ) {
2352 paths << mSubfolderPaths[ foldersNewOnServer[i] ];
2355 AnnotationJobs::MultiUrlGetAnnotationJob *job =
2356 AnnotationJobs::multiUrlGetAnnotation(
2357 mAccount->slave(), mAccount->getUrl(), paths, KOLAB_FOLDERTYPE );
2358 const QString dummystring;
2359 ImapAccountBase::jobData jd( dummystring, folder() );
2360 jd.cancellable = true;
2361 mAccount->insertJob( job, jd );
2362 connect( job, SIGNAL( result( KJob * ) ),
2363 SLOT( slotMultiUrlGetAnnotationResult( KJob * ) ) );
2365 } else {
2366 createFoldersNewOnServerAndFinishListing( foldersNewOnServer );
2370 void KMFolderCachedImap::createFoldersNewOnServerAndFinishListing(
2371 const QVector<int> foldersNewOnServer )
2373 for ( int i = 0; i < foldersNewOnServer.count(); ++i ) {
2374 int idx = foldersNewOnServer[i];
2375 KMFolder *newFolder =
2376 folder()->child()->createFolder( mSubfolderNames[idx], false, KMFolderTypeCachedImap );
2377 if ( newFolder ) {
2378 KMFolderCachedImap *f = dynamic_cast<KMFolderCachedImap*>(newFolder->storage());
2379 kDebug() << "####### Locally creating folder" << mSubfolderNames[idx];
2380 f->close( "cachedimap" );
2381 f->setAccount( mAccount );
2382 f->mAnnotationFolderType = "FROMSERVER";
2383 f->setNoContent( mSubfolderMimeTypes[idx] == "inode/directory" );
2384 f->setNoChildren( mSubfolderMimeTypes[idx] == "message/digest" );
2385 f->setImapPath( mSubfolderPaths[idx] );
2386 f->mFolderAttributes = mSubfolderAttributes[idx];
2387 kmkernel->dimapFolderMgr()->contentsChanged();
2388 } else {
2389 kDebug() << "can't create folder" << mSubfolderNames[idx];
2393 kmkernel->dimapFolderMgr()->quiet( false );
2394 emit listComplete( this );
2396 if ( !mPersonalNamespacesCheckDone ) {
2397 // we're not done with the namespaces
2398 mSyncState = SYNC_STATE_LIST_NAMESPACES;
2400 serverSyncInternal();
2403 //-----------------------------------------------------------------------------
2404 KMFolderCachedImap *KMFolderCachedImap::findParent( const QString &path,
2405 const QString &name )
2407 QString parent = path.left( path.length() - name.length() - 2 );
2409 if ( parent.length() > 1 ) {
2410 // extract name of the parent
2411 parent = parent.right( parent.length() - 1 );
2412 if ( parent != label() ) {
2413 // look for a better parent
2414 QList<KMFolderNode*>::const_iterator it;
2415 for ( it = folder()->child()->constBegin(); it != folder()->child()->constEnd(); ++it ) {
2416 KMFolderNode *node = *it;
2417 if ( node->name() == parent ) {
2418 KMFolder *fld = static_cast<KMFolder*>( node );
2419 KMFolderCachedImap *imapFld =
2420 static_cast<KMFolderCachedImap*>( fld->storage() );
2421 return imapFld;
2426 return 0;
2429 void KMFolderCachedImap::slotSubFolderComplete( KMFolderCachedImap *sub, bool success )
2431 Q_UNUSED( sub );
2433 if ( success ) {
2434 serverSyncInternal();
2435 } else {
2436 // success == false means the sync was aborted.
2437 if ( mCurrentSubfolder ) {
2438 Q_ASSERT( sub == mCurrentSubfolder );
2439 disconnect( mCurrentSubfolder, SIGNAL( folderComplete( KMFolderCachedImap*, bool ) ),
2440 this, SLOT( slotSubFolderComplete( KMFolderCachedImap*, bool ) ) );
2441 mCurrentSubfolder = 0;
2444 mSubfoldersForSync.clear();
2445 mSyncState = SYNC_STATE_INITIAL;
2446 close( "cachedimap" );
2447 emit folderComplete( this, false );
2451 void KMFolderCachedImap::slotSimpleData( KIO::Job *job, const QByteArray &data )
2453 KMAcctCachedImap::JobIterator it = mAccount->findJob( job );
2454 if ( it == mAccount->jobsEnd() ) {
2455 return;
2458 QBuffer buff( &(*it).data );
2459 buff.open( QIODevice::WriteOnly | QIODevice::Append );
2460 buff.write( data.data(), data.size() );
2461 buff.close();
2464 FolderJob *KMFolderCachedImap::doCreateJob( KMMessage *msg,
2465 FolderJob::JobType jt,
2466 KMFolder *folder,
2467 const QString &partSpecifier,
2468 const AttachmentStrategy* ) const
2470 Q_UNUSED( partSpecifier );
2472 QList<KMMessage*> msgList;
2473 msgList.append( msg );
2475 CachedImapJob *job =
2476 new CachedImapJob(
2477 msgList, jt, folder ? static_cast<KMFolderCachedImap*>( folder->storage() ) : 0 );
2478 job->setParentFolder( this );
2479 return job;
2482 FolderJob *KMFolderCachedImap::doCreateJob( QList<KMMessage*> &msgList,
2483 const QString &sets,
2484 FolderJob::JobType jt,
2485 KMFolder *folder ) const
2487 //FIXME: how to handle sets here?
2488 Q_UNUSED( sets );
2489 CachedImapJob *job =
2490 new CachedImapJob(
2491 msgList, jt, folder ? static_cast<KMFolderCachedImap*>( folder->storage() ) : 0 );
2492 job->setParentFolder( this );
2493 return job;
2496 void KMFolderCachedImap::setUserRights( unsigned int userRights )
2498 mUserRights = userRights;
2499 writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
2502 void KMFolderCachedImap::slotReceivedUserRights( KMFolder *folder )
2504 if ( folder->storage() == this ) {
2505 disconnect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
2506 this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
2507 if ( mUserRights == 0 ) { // didn't work
2508 mUserRights = -1; // error code (used in folderdia)
2509 } else {
2510 setReadOnly( ( mUserRights & KMail::ACLJobs::Insert ) == 0 );
2512 mProgress += 5;
2513 serverSyncInternal();
2517 void KMFolderCachedImap::setReadOnly( bool readOnly )
2519 if ( readOnly != mReadOnly ) {
2520 mReadOnly = readOnly;
2521 emit readOnlyChanged( folder() );
2525 void KMFolderCachedImap::slotReceivedACL( KMFolder *folder,
2526 KIO::Job*, const KMail::ACLList &aclList )
2528 if ( folder->storage() == this ) {
2529 disconnect( mAccount, SIGNAL( receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& ) ),
2530 this, SLOT( slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& ) ) );
2531 mACLList = aclList;
2532 serverSyncInternal();
2536 void KMFolderCachedImap::slotStorageQuotaResult( const QuotaInfo &info )
2538 setQuotaInfo( info );
2541 void KMFolderCachedImap::setQuotaInfo( const QuotaInfo & info )
2543 if ( info != mQuotaInfo ) {
2544 mQuotaInfo = info;
2545 writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
2546 emit folderSizeChanged();
2550 void
2551 KMFolderCachedImap::setACLList( const ACLList& arr )
2553 mACLList = arr;
2556 void KMFolderCachedImap::slotMultiSetACLResult( KJob *job )
2558 KMAcctCachedImap::JobIterator it =
2559 mAccount->findJob( static_cast<KIO::Job*>( job ) );
2561 if ( it == mAccount->jobsEnd() ) {
2562 return; // Shouldn't happen
2564 if ( (*it).parent != folder() ) {
2565 return; // Shouldn't happen
2568 if ( job->error() ) {
2569 // Display error but don't abort the sync just for this
2570 // PENDING(dfaure) reconsider using handleJobError now that it offers continue/cancel
2571 static_cast<KIO::Job*>(job)->ui()->setWindow( 0 );
2572 static_cast<KIO::Job*>(job)->ui()->showErrorMessage();
2573 } else {
2574 kmkernel->iCalIface().addFolderChange( folder(), ACLChanged );
2577 if ( mAccount->slave() ) {
2578 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2580 serverSyncInternal();
2583 void
2584 KMFolderCachedImap::slotACLChanged( const QString &userId, int permissions )
2586 // The job indicates success in changing the permissions for this user
2587 // -> we note that it's been done.
2588 for ( ACLList::Iterator it = mACLList.begin(); it != mACLList.end(); ++it ) {
2589 if ( (*it).userId == userId && (*it).permissions == permissions ) {
2590 if ( permissions == -1 ) { // deleted
2591 mACLList.erase( it );
2592 } else { // added/modified
2593 (*it).changed = false;
2595 return;
2600 // called by KMAcctCachedImap::killAllJobs
2601 void KMFolderCachedImap::resetSyncState()
2603 if ( mSyncState == SYNC_STATE_INITIAL ) {
2604 return;
2607 mSubfoldersForSync.clear();
2608 mSyncState = SYNC_STATE_INITIAL;
2609 close( "cachedimap" );
2611 // Don't use newState here, it would revert to mProgress
2612 // (which is less than the current value when listing messages)
2613 KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem();
2614 QString str = i18n("Canceled");
2615 if ( progressItem ) {
2616 progressItem->setStatus( str );
2618 emit statusMsg( str );
2621 void KMFolderCachedImap::slotIncreaseProgress()
2623 mProgress += 5;
2626 void KMFolderCachedImap::newState( int progress, const QString &syncStatus )
2628 KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem();
2629 if ( progressItem ) {
2630 progressItem->setCompletedItems( progress );
2633 if ( !syncStatus.isEmpty() ) {
2634 QString str;
2635 // For a subfolder, show the label. But for the main folder, it's already shown.
2636 if ( mAccount->imapFolder() == this ) {
2637 str = syncStatus;
2638 } else {
2639 str = QString( "%1: %2" ).arg( label() ).arg( syncStatus );
2641 if ( progressItem ) {
2642 progressItem->setStatus( str );
2644 emit statusMsg( str );
2646 if ( progressItem ) {
2647 progressItem->updateProgress();
2651 void KMFolderCachedImap::setSubfolderState( imapState state )
2653 mSubfolderState = state;
2654 if ( state == imapNoInformation && folder()->child() ) {
2655 // pass through to children
2656 QList<KMFolderNode*>::const_iterator it;
2657 for ( it = folder()->child()->constBegin(); it != folder()->child()->constEnd(); ++it ) {
2658 KMFolderNode *node = *it;
2659 if ( node->isDir() ) {
2660 continue;
2662 KMFolder *folder = static_cast<KMFolder*>( node );
2663 static_cast<KMFolderCachedImap*>( folder->storage())->setSubfolderState( state );
2668 void KMFolderCachedImap::setImapPath( const QString &path )
2670 mImapPath = path;
2673 // mAnnotationFolderType is the annotation as known to the server (and stored in kmailrc)
2674 // It is updated from the folder contents type and whether it's a standard resource folder.
2675 // This happens during the syncing phase and during initFolder for a new folder.
2676 // Don't do it earlier, e.g. from setContentsType:
2677 // on startup, it's too early there to know if this is a standard resource folder.
2678 void KMFolderCachedImap::updateAnnotationFolderType()
2680 QString oldType = mAnnotationFolderType;
2681 QString oldSubType;
2682 int dot = oldType.indexOf( '.' );
2683 if ( dot != -1 ) {
2684 oldType.truncate( dot );
2685 oldSubType = mAnnotationFolderType.mid( dot + 1 );
2688 QString newType, newSubType;
2689 // We want to store an annotation on the folder only if using the kolab storage.
2690 if ( kmkernel->iCalIface().storageFormat( folder() ) == StorageXML ) {
2691 newType = KMailICalIfaceImpl::annotationForContentsType( mContentsType );
2692 if ( kmkernel->iCalIface().isStandardResourceFolder( folder() ) ) {
2693 newSubType = "default";
2694 } else if ( oldSubType != "default" ) {
2695 newSubType = oldSubType; // preserve unknown subtypes, like drafts etc.
2699 if ( newType != oldType || newSubType != oldSubType ) {
2700 mAnnotationFolderType = newType + ( newSubType.isEmpty() ? QString() : '.'+newSubType );
2701 mAnnotationFolderTypeChanged = true; // force a "set annotation" on next sync
2702 kDebug() << mImapPath <<": updateAnnotationFolderType: '"
2703 << mAnnotationFolderType << "', was (" << oldType
2704 << oldSubType << ") => mAnnotationFolderTypeChanged set to TRUE";
2706 // Ensure that further readConfig()s don't lose mAnnotationFolderType
2707 writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
2710 void KMFolderCachedImap::setIncidencesFor( IncidencesFor incfor )
2712 if ( mIncidencesFor != incfor ) {
2713 mIncidencesFor = incfor;
2714 mIncidencesForChanged = true;
2718 void KMFolderCachedImap::setSharedSeenFlags(bool b)
2720 if ( mSharedSeenFlags != b ) {
2721 mSharedSeenFlags = b;
2722 mSharedSeenFlagsChanged = true;
2726 void KMFolderCachedImap::slotAnnotationResult( const QString &entry,
2727 const QString &value,
2728 bool found )
2730 if ( entry == KOLAB_FOLDERTYPE ) {
2732 * There are four cases.
2733 * 1) no content-type on server -> set it
2734 * 2) different content-type on server, locally changed -> set it
2735 * (we don't even come here)
2736 * 3) different (known) content-type on server, no local change -> get it
2737 * 4) different unknown content-type on server, probably some older
2738 * version -> set it
2740 if ( found ) {
2741 QString type = value;
2742 QString subtype;
2743 int dot = value.indexOf( '.' );
2744 if ( dot != -1 ) {
2745 type.truncate( dot );
2746 subtype = value.mid( dot + 1 );
2748 bool foundKnownType = false;
2749 for ( uint i = 0 ; i <= ContentsTypeLast; ++i ) {
2750 FolderContentsType contentsType = static_cast<KMail::FolderContentsType>( i );
2751 if ( type == KMailICalIfaceImpl::annotationForContentsType( contentsType ) ) {
2752 // Case 3: known content-type on server, get it
2753 if ( contentsType != ContentsTypeMail ) {
2754 kmkernel->iCalIface().setStorageFormat( folder(), StorageXML );
2756 mAnnotationFolderType = value;
2757 if ( folder()->parent()->owner()->idString() != GlobalSettings::self()->theIMAPResourceFolderParent() &&
2758 GlobalSettings::self()->theIMAPResourceEnabled() &&
2759 subtype == "default" ) {
2760 // Truncate subtype if this folder can't be a default resource
2761 // folder for us, although it apparently is for someone else.
2762 mAnnotationFolderType = type;
2763 kDebug() << mImapPath
2764 << ": slotGetAnnotationResult: parent folder is"
2765 << folder()->parent()->owner()->idString()
2766 << "=> truncating annotation to" << value;
2768 setContentsType( contentsType );
2769 mAnnotationFolderTypeChanged = false; // we changed it, not the user
2770 foundKnownType = true;
2773 * Users don't read events/contacts/etc. in kmail, so mark them all
2774 * as read. This is done in cachedimapjob when getting new messages,
2775 * but do it here too, for the initial set of messages when we
2776 * didn't know this was a resource folder yet, for old folders, etc.
2778 if ( contentsType != ContentsTypeMail ) {
2779 markUnreadAsRead();
2782 // Ensure that further readConfig()s don't lose mAnnotationFolderType
2783 writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
2784 break;
2787 if ( !foundKnownType && !mReadOnly ) {
2788 // Case 4: server has strange content-type, set it to what we need
2789 mAnnotationFolderTypeChanged = true;
2791 // TODO handle subtype (inbox, drafts, sentitems, junkemail)
2792 } else if ( !mReadOnly ) {
2793 // Case 1: server doesn't have content-type, set it
2794 mAnnotationFolderTypeChanged = true;
2796 } else if ( entry == KOLAB_INCIDENCESFOR ) {
2797 if ( found ) {
2798 mIncidencesFor = incidencesForFromString( value );
2799 Q_ASSERT( mIncidencesForChanged == false );
2801 } else if ( entry == KOLAB_SHAREDSEEN ) {
2802 if ( found ) {
2803 mSharedSeenFlags = value == "true";
2808 void KMFolderCachedImap::slotGetAnnotationResult( KJob *job )
2810 KMAcctCachedImap::JobIterator it =
2811 mAccount->findJob( static_cast<KIO::Job*>( job ) );
2813 Q_ASSERT( it != mAccount->jobsEnd() );
2814 if ( it == mAccount->jobsEnd() ) {
2815 return; // Shouldn't happen
2817 Q_ASSERT( (*it).parent == folder() );
2818 if ( (*it).parent != folder() ) {
2819 return; // Shouldn't happen
2822 AnnotationJobs::GetAnnotationJob *annjob =
2823 static_cast<AnnotationJobs::GetAnnotationJob *>( job );
2824 if ( annjob->error() ) {
2825 if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
2826 // that's when the imap server doesn't support annotations
2827 if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML &&
2828 (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() ) {
2829 KMessageBox::error(
2831 i18n( "The IMAP server %1 does not have support for IMAP annotations. "
2832 "The XML storage cannot be used on this server; "
2833 "please re-configure KMail differently.",
2834 mAccount->host() ) );
2836 mAccount->setHasNoAnnotationSupport();
2837 } else {
2838 kWarning() <<"slotGetAnnotationResult:" << job->errorString();
2842 if ( mAccount->slave() ) {
2843 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2845 mProgress += 2;
2846 serverSyncInternal();
2849 void KMFolderCachedImap::slotMultiUrlGetAnnotationResult( KJob *job )
2851 KMAcctCachedImap::JobIterator it = mAccount->findJob( job );
2852 Q_ASSERT( it != mAccount->jobsEnd() );
2853 if ( it == mAccount->jobsEnd() ) {
2854 return; // Shouldn't happen
2856 Q_ASSERT( (*it).parent == folder() );
2857 if ( (*it).parent != folder() ) {
2858 return; // Shouldn't happen
2861 QVector<int> folders;
2862 AnnotationJobs::MultiUrlGetAnnotationJob *annjob =
2863 static_cast<AnnotationJobs::MultiUrlGetAnnotationJob *>( job );
2864 if ( annjob->error() ) {
2865 if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
2866 // that's when the imap server doesn't support annotations
2867 if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML &&
2868 (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() ) {
2869 KMessageBox::error(
2871 i18n( "The IMAP server %1 does not support annotations. "
2872 "The XML storage cannot be used on this server, "
2873 "please re-configure KMail differently",
2874 mAccount->host() ) );
2876 mAccount->setHasNoAnnotationSupport();
2877 } else {
2878 kWarning() <<"slotGetMultiUrlAnnotationResult:" << job->errorString();
2880 } else {
2881 // we got the annotation allright, let's filter out the ones with the wrong type
2882 QMap<QString, QString> annotations = annjob->annotations();
2883 QMap<QString, QString>::Iterator it = annotations.begin();
2884 for ( ; it != annotations.end(); ++it ) {
2885 const QString folderPath = it.key();
2886 const QString annotation = it.value();
2887 kDebug() << "Folder:" << folderPath << "has type:" << annotation;
2888 // we're only interested in the main type
2889 QString type( annotation );
2890 int dot = annotation.indexOf( '.' );
2891 if ( dot != -1 ) {
2892 type.truncate( dot );
2894 type = type.simplified();
2896 const int idx = mSubfolderPaths.indexOf( folderPath );
2897 const bool isNoContent = mSubfolderMimeTypes[idx] == "inode/directory";
2898 if ( ( isNoContent && type.isEmpty() ) ||
2899 ( !type.isEmpty() &&
2900 type != KMailICalIfaceImpl::annotationForContentsType( ContentsTypeMail ) ) ) {
2901 folders.append( idx );
2902 kDebug() << "subscribing to:" << folderPath;
2903 } else {
2904 kDebug() << "automatically unsubscribing from:" << folderPath;
2905 mAccount->changeLocalSubscription( folderPath, false );
2910 if ( mAccount->slave() ) {
2911 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2913 createFoldersNewOnServerAndFinishListing( folders );
2916 void KMFolderCachedImap::slotQuotaResult( KJob *job )
2918 KMAcctCachedImap::JobIterator it = mAccount->findJob( static_cast<KIO::Job*>( job ) );
2919 Q_ASSERT( it != mAccount->jobsEnd() );
2920 if ( it == mAccount->jobsEnd() ) {
2921 return; // Shouldn't happen
2923 Q_ASSERT( (*it).parent == folder() );
2924 if ( (*it).parent != folder() ) {
2925 return; // Shouldn't happen
2928 QuotaJobs::GetStorageQuotaJob *quotajob = static_cast<QuotaJobs::GetStorageQuotaJob *>( job );
2929 QuotaInfo empty;
2930 if ( quotajob->error() ) {
2931 if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
2932 // that's when the imap server doesn't support quota
2933 mAccount->setHasNoQuotaSupport();
2934 setQuotaInfo( empty );
2935 } else {
2936 kWarning() <<"slotGetQuotaResult:" << job->errorString();
2940 if ( mAccount->slave() ) {
2941 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2943 mProgress += 2;
2944 serverSyncInternal();
2947 void KMFolderCachedImap::slotAnnotationChanged( const QString &entry,
2948 const QString &attribute,
2949 const QString &value )
2951 kDebug() << entry << attribute << value;
2952 if ( entry == KOLAB_FOLDERTYPE ) {
2953 mAnnotationFolderTypeChanged = false;
2954 } else if ( entry == KOLAB_INCIDENCESFOR ) {
2955 mIncidencesForChanged = false;
2957 * The incidences-for changed, we must trigger the freebusy creation.
2958 * HACK: in theory we would need a new enum value for this.
2960 kmkernel->iCalIface().addFolderChange( folder(), ACLChanged );
2961 } else if ( entry == KOLAB_SHAREDSEEN ) {
2962 mSharedSeenFlagsChanged = false;
2966 void KMFolderCachedImap::slotTestAnnotationResult( KJob *job )
2968 KMAcctCachedImap::JobIterator it =
2969 mAccount->findJob( static_cast<KIO::Job*>( job ) );
2971 Q_ASSERT( it != mAccount->jobsEnd() );
2972 if ( it == mAccount->jobsEnd() ) {
2973 return; // Shouldn't happen
2975 Q_ASSERT( (*it).parent == folder() );
2976 if ( (*it).parent != folder() ) {
2977 return; // Shouldn't happen
2980 mAccount->setAnnotationCheckPassed( true );
2981 if ( job->error() ) {
2982 kDebug() << "Test Annotation was not passed, disabling annotation support";
2983 mAccount->setHasNoAnnotationSupport( );
2984 } else {
2985 kDebug() << "Test Annotation was passed OK";
2987 if ( mAccount->slave() ) {
2988 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
2990 serverSyncInternal();
2993 void KMFolderCachedImap::slotSetAnnotationResult( KJob *job )
2995 KMAcctCachedImap::JobIterator it =
2996 mAccount->findJob( static_cast<KIO::Job*>( job ) );
2998 if ( it == mAccount->jobsEnd() ) {
2999 return; // Shouldn't happen
3001 if ( (*it).parent != folder() ) {
3002 return; // Shouldn't happen
3005 bool cont = true;
3006 if ( job->error() ) {
3008 * Don't show error if the server doesn't support ANNOTATEMORE
3009 * and this folder only contains mail.
3011 if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION &&
3012 contentsType() == ContentsTypeMail ) {
3013 if ( mAccount->slave() ) {
3014 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
3016 } else {
3017 cont =
3018 mAccount->handleJobError( static_cast<KIO::Job*>( job ),
3019 i18n( "Error while setting annotation: " ) + '\n' );
3021 } else {
3022 if ( mAccount->slave() ) {
3023 mAccount->removeJob( static_cast<KIO::Job*>( job ) );
3026 if ( cont ) {
3027 serverSyncInternal();
3031 void KMFolderCachedImap::slotUpdateLastUid()
3033 if ( mTentativeHighestUid != 0 ) {
3035 // Sanity checking:
3036 // By now all new mails should be downloaded, which means
3037 // that iteration over the folder should yield only UIDs
3038 // lower or equal to what we think the highes ist, and the
3039 // highest one as well. If not, our notion of the highest
3040 // uid we've seen thus far is wrong, which is dangerous, so
3041 // don't update the mLastUid, then.
3042 // Not entirely true though, mails might have been moved out
3043 // of the folder already by filters, thus giving us a higher tentative
3044 // uid than we actually observe here.
3045 bool sane = count() == 0;
3047 for ( int i = 0;i < count(); i++ ) {
3048 ulong uid = getMsgBase(i)->UID();
3049 if ( uid > mTentativeHighestUid && uid > lastUid() ) {
3050 kWarning() << "DANGER: Either the server listed a wrong highest uid, "
3051 "or we parsed it wrong. Send email to adam@kde.org, please, and include this log.";
3052 kWarning() << "uid: " << uid << " mTentativeHighestUid: " << mTentativeHighestUid;
3053 assert( false );
3054 break;
3055 } else {
3056 sane = true;
3059 if ( sane ) {
3060 #if MAIL_LOSS_DEBUGGING
3061 kDebug() << "Tentative highest UID test was sane, writing out: " << mTentativeHighestUid;
3062 #endif
3063 setLastUid( mTentativeHighestUid );
3066 mTentativeHighestUid = 0;
3069 bool KMFolderCachedImap::isMoveable() const
3071 return ( hasChildren() == HasNoChildren &&
3072 !folder()->isSystemFolder() ) ? true : false;
3075 void KMFolderCachedImap::slotFolderDeletionOnServerFinished()
3077 for ( QStringList::const_iterator it = foldersForDeletionOnServer.constBegin();
3078 it != foldersForDeletionOnServer.constEnd(); ++it ) {
3079 KUrl url( mAccount->getUrl() );
3080 url.setPath( *it );
3081 kmkernel->iCalIface().folderDeletedOnServer( url );
3083 serverSyncInternal();
3086 int KMFolderCachedImap::createIndexFromContentsRecursive()
3088 if ( !folder() || !folder()->child() ) {
3089 return 0;
3092 for ( QList<KMFolderNode*>::Iterator it = folder()->child()->begin();
3093 it != folder()->child()->end(); ++it ) {
3094 if ( !(*it)->isDir() ) {
3095 KMFolderCachedImap *storage =
3096 static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(*it)->storage());
3097 kDebug() << "Re-indexing:" << storage->folder()->label();
3098 int rv = storage->createIndexFromContentsRecursive();
3099 if ( rv > 0 ) {
3100 return rv;
3105 return createIndexFromContents();
3108 void KMFolderCachedImap::setAlarmsBlocked( bool blocked )
3110 mAlarmsBlocked = blocked;
3113 bool KMFolderCachedImap::alarmsBlocked() const
3115 return mAlarmsBlocked;
3118 bool KMFolderCachedImap::isCloseToQuota() const
3120 bool closeToQuota = false;
3121 if ( mQuotaInfo.isValid() && mQuotaInfo.max().toInt() > 0 ) {
3122 const int ratio = mQuotaInfo.current().toInt() * 100 / mQuotaInfo.max().toInt();
3123 //kDebug() << "Quota ratio:" << ratio <<"%" << mQuotaInfo.toString();
3124 closeToQuota = ( ratio > 0 && ratio >= GlobalSettings::closeToQuotaThreshold() );
3126 //kDebug() << "Folder:" << folder()->prettyUrl() << "is over quota:" << closeToQuota;
3127 return closeToQuota;
3130 KMCommand* KMFolderCachedImap::rescueUnsyncedMessages()
3132 QList<unsigned long> newMsgs = findNewMessages();
3133 kDebug() << newMsgs << "of" << count();
3134 if ( newMsgs.isEmpty() )
3135 return 0;
3136 KMFolder *dest = 0;
3137 bool manualMove = true;
3138 while ( GlobalSettings::autoLostFoundMove() ) {
3139 // find the inbox of this account
3140 KMFolder *inboxFolder = kmkernel->findFolderById( QString(".%1.directory/INBOX").arg( account()->id() ) );
3141 if ( !inboxFolder ) {
3142 kWarning() <<"inbox not found!";
3143 break;
3145 KMFolderDir *inboxDir = inboxFolder->child();
3146 if ( !inboxDir || !inboxFolder->storage() )
3147 break;
3148 assert( inboxFolder->storage()->folderType() == KMFolderTypeCachedImap );
3150 // create lost+found folder if needed
3151 KMFolderNode *node;
3152 KMFolder *lfFolder = 0;
3153 if ( !(node = inboxDir->hasNamedFolder( i18n("lost+found") )) ) {
3154 kDebug() << "creating lost+found folder";
3155 KMFolder* folder = kmkernel->dimapFolderMgr()->createFolder(
3156 i18n("lost+found"), false, KMFolderTypeCachedImap, inboxDir );
3157 if ( !folder || !folder->storage() )
3158 break;
3159 static_cast<KMFolderCachedImap*>( folder->storage() )->initializeFrom(
3160 static_cast<KMFolderCachedImap*>( inboxFolder->storage() ) );
3161 folder->storage()->setContentsType( KMail::ContentsTypeMail );
3162 folder->storage()->writeConfig();
3163 lfFolder = folder;
3164 } else {
3165 kDebug() << "found lost+found folder";
3166 lfFolder = dynamic_cast<KMFolder*>( node );
3168 if ( !lfFolder || !lfFolder->createChildFolder() || !lfFolder->storage() )
3169 break;
3171 // create subfolder for this incident
3172 QDate today = QDate::currentDate();
3173 QString baseName = folder()->label() + '-' + QString::number( today.year() )
3174 + (today.month() < 10 ? "0" : "" ) + QString::number( today.month() )
3175 + (today.day() < 10 ? "0" : "" ) + QString::number( today.day() );
3176 QString name = baseName;
3177 int suffix = 0;
3178 while ( (node = lfFolder->child()->hasNamedFolder( name )) ) {
3179 ++suffix;
3180 name = baseName + '-' + QString::number( suffix );
3182 kDebug() << "creating lost+found folder" << name;
3183 dest = kmkernel->dimapFolderMgr()->createFolder( name, false, KMFolderTypeCachedImap, lfFolder->child() );
3184 if ( !dest || !dest->storage() )
3185 break;
3186 static_cast<KMFolderCachedImap*>( dest->storage() )->initializeFrom(
3187 static_cast<KMFolderCachedImap*>( lfFolder->storage() ) );
3188 dest->storage()->setContentsType( contentsType() );
3189 dest->storage()->writeConfig();
3191 KMessageBox::sorry( 0, i18n("<p>There are new messages in folder <b>%1</b>, which "
3192 "have not been uploaded to the server yet, but the folder has been deleted "
3193 "on the server or you do not "
3194 "have sufficient access rights on the folder to upload them.</p>"
3195 "<p>All affected messages will therefore be moved to <b>%2</b> "
3196 "to avoid data loss.</p>", folder()->prettyUrl(), dest->prettyUrl() ),
3197 i18n("Insufficient access rights") );
3198 manualMove = false;
3199 break;
3202 if ( manualMove ) {
3203 const QString msg ( i18n( "<p>There are new messages in this folder (%1), which "
3204 "have not been uploaded to the server yet, but the folder has been deleted "
3205 "on the server or you do not "
3206 "have sufficient access rights on the folder now to upload them. "
3207 "Please contact your administrator to allow upload of new messages "
3208 "to you, or move them out of this folder.</p> "
3209 "<p>Do you want to move these messages to another folder now?</p>", folder()->prettyUrl() ) );
3210 if ( KMessageBox::warningYesNo( 0, msg, QString(), KGuiItem( i18n("Move") ), KGuiItem( i18n("Do Not Move") ) )
3211 == KMessageBox::Yes ) {
3212 AutoQPointer<KMail::FolderSelectionDialog> dlg;
3213 dlg = new KMail::FolderSelectionDialog( kmkernel->getKMMainWidget(),
3214 i18n("Move Messages to Folder"), true );
3215 if ( dlg->exec() && dlg ) {
3216 dest = dlg->folder();
3220 if ( dest ) {
3221 QList<KMMsgBase*> msgs;
3222 for( int i = 0; i < count(); ++i ) {
3223 KMMsgBase *msg = getMsgBase( i );
3224 if( !msg ) continue; /* what goes on if getMsg() returns 0? */
3225 if ( msg->UID() == 0 )
3226 msgs.append( msg );
3228 KMCommand *command = new KMMoveCommand( dest, msgs );
3229 command->start();
3230 return command;
3232 return 0;
3235 void KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder( KMFolder *folder, bool root )
3237 kDebug() << folder << root;
3238 if ( root )
3239 mToBeDeletedAfterRescue.append( folder );
3240 folder->open( "KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder" );
3241 KMFolderCachedImap* storage = dynamic_cast<KMFolderCachedImap*>( folder->storage() );
3242 if ( storage ) {
3243 KMCommand *command = storage->rescueUnsyncedMessages();
3244 if ( command ) {
3245 connect( command, SIGNAL(completed(KMCommand*)),
3246 SLOT(slotRescueDone(KMCommand*)) );
3247 ++mRescueCommandCount;
3248 } else {
3249 // nothing to rescue, close folder
3250 // (we don't need to close it in the other case, it will be deleted anyway)
3251 folder->close( "KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder" );
3254 if ( folder->child() ) {
3255 for ( KMFolderNodeList::ConstIterator it = folder->child()->constBegin(); it != folder->child()->constEnd(); ++it ) {
3256 KMFolderNode *node = *it;
3257 if ( node && !node->isDir() ) {
3258 KMFolder *subFolder = static_cast<KMFolder*>( node );
3259 rescueUnsyncedMessagesAndDeleteFolder( subFolder, false );
3265 void KMFolderCachedImap::slotRescueDone(KMCommand * command)
3267 // FIXME: check command result
3268 if ( command )
3269 --mRescueCommandCount;
3270 if ( mRescueCommandCount > 0 )
3271 return;
3272 for ( QList<KMFolder*>::ConstIterator it = mToBeDeletedAfterRescue.constBegin();
3273 it != mToBeDeletedAfterRescue.constEnd(); ++it ) {
3274 kmkernel->dimapFolderMgr()->remove( *it );
3276 mToBeDeletedAfterRescue.clear();
3277 serverSyncInternal();
3280 bool KMFolderCachedImap::canDeleteMessages() const
3282 if ( isReadOnly() )
3283 return false;
3284 if ( userRights() > 0 && !(userRights() & ACLJobs::Delete) )
3285 return false;
3286 return true;
3289 #include "kmfoldercachedimap.moc"