You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3229 lines
123 KiB
3229 lines
123 KiB
/**
|
|
* kmfoldercachedimap.cpp
|
|
*
|
|
* Copyright (c) 2002-2004 Bo Thorsen <bo@sonofthor.dk>
|
|
* Copyright (c) 2002-2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* In addition, as a special exception, the copyright holders give
|
|
* permission to link the code of this program with any edition of
|
|
* the TQt library by Trolltech AS, Norway (or with modified versions
|
|
* of TQt that use the same license as TQt), and distribute linked
|
|
* combinations including the two. You must obey the GNU General
|
|
* Public License in all respects for all of the code used other than
|
|
* TQt. If you modify this file, you may extend this exception to
|
|
* your version of the file, but you are not obligated to do so. If
|
|
* you do not wish to do so, delete this exception statement from
|
|
* your version.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
|
|
#include <tqvaluevector.h>
|
|
|
|
#include "kmkernel.h"
|
|
#include "kmfoldercachedimap.h"
|
|
#include "undostack.h"
|
|
#include "kmfoldermgr.h"
|
|
#include "kmacctcachedimap.h"
|
|
#include "accountmanager.h"
|
|
using KMail::AccountManager;
|
|
#include "kmailicalifaceimpl.h"
|
|
#include "kmfolder.h"
|
|
#include "kmglobal.h"
|
|
#include "acljobs.h"
|
|
#include "broadcaststatus.h"
|
|
using KPIM::BroadcastStatus;
|
|
#include "progressmanager.h"
|
|
|
|
using KMail::CachedImapJob;
|
|
#include "imapaccountbase.h"
|
|
using KMail::ImapAccountBase;
|
|
#include "listjob.h"
|
|
using KMail::ListJob;
|
|
|
|
#include "kmfolderseldlg.h"
|
|
#include "kmcommands.h"
|
|
#include "kmmainwidget.h"
|
|
|
|
#include <kapplication.h>
|
|
#include <kmessagebox.h>
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
#include <kconfig.h>
|
|
#include <kio/global.h>
|
|
#include <kio/scheduler.h>
|
|
#include <tqbuffer.h>
|
|
#include <tqbuttongroup.h>
|
|
#include <tqcombobox.h>
|
|
#include <tqfile.h>
|
|
#include <tqhbox.h>
|
|
#include <tqlabel.h>
|
|
#include <layout.h>
|
|
#include <tqradiobutton.h>
|
|
#include <tqvaluelist.h>
|
|
#include "annotationjobs.h"
|
|
#include "quotajobs.h"
|
|
using namespace KMail;
|
|
#include <globalsettings.h>
|
|
|
|
#define UIDCACHE_VERSION 1
|
|
#define MAIL_LOSS_DEBUGGING 0
|
|
|
|
static TQString incidencesForToString( KMFolderCachedImap::IncidencesFor r ) {
|
|
switch (r) {
|
|
case KMFolderCachedImap::IncForNobody: return "nobody";
|
|
case KMFolderCachedImap::IncForAdmins: return "admins";
|
|
case KMFolderCachedImap::IncForReaders: return "readers";
|
|
}
|
|
return TQString(); // can't happen
|
|
}
|
|
|
|
static KMFolderCachedImap::IncidencesFor incidencesForFromString( const TQString& str ) {
|
|
if ( str == "nobody" ) return KMFolderCachedImap::IncForNobody;
|
|
if ( str == "admins" ) return KMFolderCachedImap::IncForAdmins;
|
|
if ( str == "readers" ) return KMFolderCachedImap::IncForReaders;
|
|
return KMFolderCachedImap::IncForAdmins; // by default
|
|
}
|
|
|
|
DImapTroubleShootDialog::DImapTroubleShootDialog( TQWidget* parent,
|
|
const char* name )
|
|
: KDialogBase( Plain, i18n( "Troubleshooting IMAP Cache" ),
|
|
Ok | Cancel, Cancel, parent, name, true ),
|
|
rc( None )
|
|
{
|
|
TQFrame* page = plainPage();
|
|
TQVBoxLayout *topLayout = new TQVBoxLayout( page, 0 );
|
|
// spell "lose" correctly. but don't cause a fuzzy.
|
|
TQString txt = i18n( "<p><b>Troubleshooting the IMAP cache.</b></p>"
|
|
"<p>If you have problems with synchronizing an IMAP "
|
|
"folder, you should first try rebuilding the index "
|
|
"file. This will take some time to rebuild, but will "
|
|
"not cause any problems.</p><p>If that is not enough, "
|
|
"you can try refreshing the IMAP cache. If you do this, "
|
|
"you will loose all your local changes for this folder "
|
|
"and all its subfolders.</p>",
|
|
"<p><b>Troubleshooting the IMAP cache.</b></p>"
|
|
"<p>If you have problems with synchronizing an IMAP "
|
|
"folder, you should first try rebuilding the index "
|
|
"file. This will take some time to rebuild, but will "
|
|
"not cause any problems.</p><p>If that is not enough, "
|
|
"you can try refreshing the IMAP cache. If you do this, "
|
|
"you will lose all your local changes for this folder "
|
|
"and all its subfolders.</p>" );
|
|
topLayout->addWidget( new TQLabel( txt, page ) );
|
|
|
|
mButtonGroup = new TQButtonGroup( 0 );
|
|
|
|
mIndexButton = new TQRadioButton( page );
|
|
mIndexButton->setText( i18n( "Rebuild &Index" ) );
|
|
mButtonGroup->insert( mIndexButton );
|
|
topLayout->addWidget( mIndexButton );
|
|
|
|
TQHBox *hbox = new TQHBox( page );
|
|
TQLabel *scopeLabel = new TQLabel( i18n( "Scope:" ), hbox );
|
|
scopeLabel->setEnabled( false );
|
|
mIndexScope = new TQComboBox( hbox );
|
|
mIndexScope->insertItem( i18n( "Only current folder" ) );
|
|
mIndexScope->insertItem( i18n( "Current folder and all subfolders" ) );
|
|
mIndexScope->insertItem( i18n( "All folders of this account" ) );
|
|
mIndexScope->setEnabled( false );
|
|
topLayout->addWidget( hbox );
|
|
|
|
mCacheButton = new TQRadioButton( page );
|
|
mCacheButton->setText( i18n( "Refresh &Cache" ) );
|
|
mButtonGroup->insert( mCacheButton );
|
|
topLayout->addWidget( mCacheButton );
|
|
|
|
enableButtonSeparator( true );
|
|
|
|
connect ( mIndexButton, TQT_SIGNAL(toggled(bool)), mIndexScope, TQT_SLOT(setEnabled(bool)) );
|
|
connect ( mIndexButton, TQT_SIGNAL(toggled(bool)), scopeLabel, TQT_SLOT(setEnabled(bool)) );
|
|
connect( mButtonGroup, TQT_SIGNAL( clicked( int ) ), TQT_SLOT( slotChanged() ) );
|
|
connect( this, TQT_SIGNAL( okClicked () ), this, TQT_SLOT( slotDone() ) );
|
|
enableButtonOK( false );
|
|
}
|
|
|
|
int DImapTroubleShootDialog::run()
|
|
{
|
|
DImapTroubleShootDialog d;
|
|
d.exec();
|
|
return d.rc;
|
|
}
|
|
|
|
void DImapTroubleShootDialog::slotChanged()
|
|
{
|
|
enableButtonOK( mButtonGroup->selected() != 0 );
|
|
}
|
|
|
|
void DImapTroubleShootDialog::slotDone()
|
|
{
|
|
rc = None;
|
|
if ( mIndexButton->isOn() )
|
|
rc = mIndexScope->currentItem();
|
|
else if ( mCacheButton->isOn() )
|
|
rc = RefreshCache;
|
|
done( Ok );
|
|
}
|
|
|
|
KMFolderCachedImap::KMFolderCachedImap( KMFolder* folder, const char* aName )
|
|
: KMFolderMaildir( folder, aName ),
|
|
mSyncState( SYNC_STATE_INITIAL ), mContentState( imapNoInformation ),
|
|
mSubfolderState( imapNoInformation ),
|
|
mIncidencesFor( IncForAdmins ),
|
|
mSharedSeenFlags( false ),
|
|
mIsSelected( false ),
|
|
mCheckFlags( true ), mReadOnly( false ), mAccount( NULL ), uidMapDirty( true ),
|
|
uidWriteTimer( -1 ), mLastUid( 0 ), mTentativeHighestUid( 0 ),
|
|
mFoundAnIMAPDigest( false ),
|
|
mUserRights( 0 ), mOldUserRights( 0 ), mUserRightsState( KMail::ACLJobs::NotFetchedYet ),
|
|
mACLListState( KMail::ACLJobs::NotFetchedYet ),
|
|
mSilentUpload( false ),
|
|
/*mHoldSyncs( false ),*/
|
|
mFolderRemoved( false ),
|
|
mRecurse( true ),
|
|
mQuotaOnly( false ),
|
|
mAnnotationFolderTypeChanged( false ),
|
|
mIncidencesForChanged( false ),
|
|
mSharedSeenFlagsChanged( false ),
|
|
mStatusChangedLocally( false ),
|
|
mPersonalNamespacesCheckDone( true ),
|
|
mQuotaInfo(), mSomeSubFolderCloseToQuotaChanged( false ), mAlarmsBlocked( false ),
|
|
mRescueCommandCount( 0 ),
|
|
mPermanentFlags( 31 ) // assume standard flags by default (see imap4/imapinfo.h for bit fields values)
|
|
{
|
|
setUidValidity("");
|
|
// if we fail to read a uid file but there is one, nuke it
|
|
if ( readUidCache() == -1 ) {
|
|
if ( TQFile::exists( uidCacheLocation() ) ) {
|
|
KMessageBox::error( 0,
|
|
i18n( "The UID cache file for folder %1 could not be read. There "
|
|
"could be a problem with file system permission, or it is corrupted."
|
|
).arg( folder->prettyURL() ) );
|
|
// try to unlink it, in case it was corruped. If it couldn't be read
|
|
// because of permissions, this will fail, which is fine
|
|
unlink( TQFile::encodeName( uidCacheLocation() ) );
|
|
}
|
|
}
|
|
|
|
mProgress = 0;
|
|
}
|
|
|
|
KMFolderCachedImap::~KMFolderCachedImap()
|
|
{
|
|
if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() );
|
|
writeConfig();
|
|
}
|
|
|
|
void KMFolderCachedImap::reallyDoClose( const char* owner )
|
|
{
|
|
if( !mFolderRemoved ) {
|
|
writeUidCache();
|
|
}
|
|
KMFolderMaildir::reallyDoClose( owner );
|
|
}
|
|
|
|
void KMFolderCachedImap::initializeFrom( KMFolderCachedImap* parent )
|
|
{
|
|
setAccount( parent->account() );
|
|
// Now that we have an account, tell it that this folder was created:
|
|
// if this folder was just removed, then we don't really want to remove it from the server.
|
|
mAccount->removeDeletedFolder( imapPath() );
|
|
setUserRights( parent->userRights(), parent->userRightsState() );
|
|
}
|
|
|
|
void KMFolderCachedImap::readConfig()
|
|
{
|
|
KConfig* config = KMKernel::config();
|
|
KConfigGroupSaver saver( config, "Folder-" + folder()->idString() );
|
|
if( mImapPath.isEmpty() ) mImapPath = config->readEntry( "ImapPath" );
|
|
if( TQString( name() ).upper() == "INBOX" && mImapPath == "/INBOX/" )
|
|
{
|
|
folder()->setLabel( i18n( "inbox" ) );
|
|
// for the icon
|
|
folder()->setSystemFolder( true );
|
|
}
|
|
mNoContent = config->readBoolEntry( "NoContent", false );
|
|
mReadOnly = config->readBoolEntry( "ReadOnly", false );
|
|
if ( !config->readEntry( "FolderAttributes" ).isEmpty() )
|
|
mFolderAttributes = config->readEntry( "FolderAttributes" );
|
|
|
|
if ( mAnnotationFolderType != "FROMSERVER" ) {
|
|
mAnnotationFolderType = config->readEntry( "Annotation-FolderType" );
|
|
// if there is an annotation, it has to be XML
|
|
if ( !mAnnotationFolderType.isEmpty() && !mAnnotationFolderType.startsWith( "mail" ) )
|
|
kmkernel->iCalIface().setStorageFormat( folder(), KMailICalIfaceImpl::StorageXML );
|
|
// kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath )
|
|
// << " readConfig: mAnnotationFolderType=" << mAnnotationFolderType << endl;
|
|
}
|
|
mIncidencesFor = incidencesForFromString( config->readEntry( "IncidencesFor" ) );
|
|
mAlarmsBlocked = config->readBoolEntry( "AlarmsBlocked", false );
|
|
// kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath )
|
|
// << " readConfig: mIncidencesFor=" << mIncidencesFor << endl;
|
|
mSharedSeenFlags = config->readBoolEntry( "SharedSeenFlags", false );
|
|
|
|
mUserRights = config->readNumEntry( "UserRights", 0 );
|
|
mUserRightsState = static_cast<KMail::ACLJobs::ACLFetchState>(
|
|
config->readNumEntry( "UserRightsState", KMail::ACLJobs::NotFetchedYet ) );
|
|
mOldUserRights = mUserRights;
|
|
|
|
int storageQuotaUsage = config->readNumEntry( "StorageQuotaUsage", -1 );
|
|
int storageQuotaLimit = config->readNumEntry( "StorageQuotaLimit", -1 );
|
|
TQString storageQuotaRoot = config->readEntry( "StorageQuotaRoot", TQString() );
|
|
if ( !storageQuotaRoot.isNull() ) { // isEmpty() means we know there is no quota set
|
|
mQuotaInfo.setName( "STORAGE" );
|
|
mQuotaInfo.setRoot( storageQuotaRoot );
|
|
|
|
if ( storageQuotaUsage > -1 )
|
|
mQuotaInfo.setCurrent( storageQuotaUsage );
|
|
if ( storageQuotaLimit > -1 )
|
|
mQuotaInfo.setMax( storageQuotaLimit );
|
|
}
|
|
|
|
KMFolderMaildir::readConfig();
|
|
|
|
mStatusChangedLocally =
|
|
config->readBoolEntry( "StatusChangedLocally", false );
|
|
TQStringList uidsChanged = config->readListEntry( "UIDStatusChangedLocally" );
|
|
for ( TQStringList::iterator it = uidsChanged.begin(); it != uidsChanged.end(); it++ ) {
|
|
mUIDsOfLocallyChangedStatuses.insert( ( *it ).toUInt() );
|
|
}
|
|
|
|
mAnnotationFolderTypeChanged = config->readBoolEntry( "AnnotationFolderTypeChanged", false );
|
|
mIncidencesForChanged = config->readBoolEntry( "IncidencesForChanged", false );
|
|
mSharedSeenFlagsChanged = config->readBoolEntry( "SharedSeenFlagsChanged", false );
|
|
|
|
if ( mImapPath.isEmpty() ) {
|
|
mImapPathCreation = config->readEntry("ImapPathCreation");
|
|
}
|
|
|
|
TQStringList delUids = config->readListEntry( "UIDSDeletedSinceLastSync" );
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug( 5006 ) << "READING IN UIDSDeletedSinceLastSync: " << folder()->prettyURL() << endl << uids << endl;
|
|
#endif
|
|
for ( TQStringList::iterator it = delUids.begin(); it != delUids.end(); it++ ) {
|
|
mDeletedUIDsSinceLastSync.insert( (*it).toULong(), 0);
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::writeConfig()
|
|
{
|
|
// don't re-write the config of a removed folder, this has just been deleted in
|
|
// the folder manager
|
|
if ( mFolderRemoved )
|
|
return;
|
|
|
|
KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() );
|
|
configGroup.writeEntry( "ImapPath", mImapPath );
|
|
configGroup.writeEntry( "NoContent", mNoContent );
|
|
configGroup.writeEntry( "ReadOnly", mReadOnly );
|
|
configGroup.writeEntry( "FolderAttributes", mFolderAttributes );
|
|
|
|
// StatusChangedLocally is always false, as we use UIDStatusChangedLocally now
|
|
configGroup.writeEntry( "StatusChangedLocally", false );
|
|
TQStringList uidsToWrite;
|
|
for( std::set<ulong>::iterator it = mUIDsOfLocallyChangedStatuses.begin();
|
|
it != mUIDsOfLocallyChangedStatuses.end(); it++ ) {
|
|
uidsToWrite.append( TQString::number( (*it) ) );
|
|
}
|
|
configGroup.writeEntry( "UIDStatusChangedLocally", uidsToWrite );
|
|
if ( !mImapPathCreation.isEmpty() ) {
|
|
if ( mImapPath.isEmpty() ) {
|
|
configGroup.writeEntry( "ImapPathCreation", mImapPathCreation );
|
|
} else {
|
|
configGroup.deleteEntry( "ImapPathCreation" );
|
|
}
|
|
}
|
|
if ( !mDeletedUIDsSinceLastSync.isEmpty() ) {
|
|
TQValueList<ulong> uids = mDeletedUIDsSinceLastSync.keys();
|
|
TQStringList uidstrings;
|
|
for( TQValueList<ulong>::iterator it = uids.begin(); it != uids.end(); it++ ) {
|
|
uidstrings.append( TQString::number( (*it) ) );
|
|
}
|
|
configGroup.writeEntry( "UIDSDeletedSinceLastSync", uidstrings );
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug( 5006 ) << "WRITING OUT UIDSDeletedSinceLastSync in: " << folder( )->prettyURL( ) << endl << uidstrings << endl;
|
|
#endif
|
|
} else {
|
|
configGroup.deleteEntry( "UIDSDeletedSinceLastSync" );
|
|
}
|
|
writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
|
|
KMFolderMaildir::writeConfig();
|
|
}
|
|
|
|
void KMFolderCachedImap::writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig()
|
|
{
|
|
KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() );
|
|
if ( !folder()->noContent() )
|
|
{
|
|
configGroup.writeEntry( "AnnotationFolderTypeChanged", mAnnotationFolderTypeChanged );
|
|
configGroup.writeEntry( "Annotation-FolderType", mAnnotationFolderType );
|
|
configGroup.writeEntry( "IncidencesForChanged", mIncidencesForChanged );
|
|
configGroup.writeEntry( "IncidencesFor", incidencesForToString( mIncidencesFor ) );
|
|
configGroup.writeEntry( "AlarmsBlocked", mAlarmsBlocked );
|
|
configGroup.writeEntry( "SharedSeenFlags", mSharedSeenFlags );
|
|
configGroup.writeEntry( "SharedSeenFlagsChanged", mSharedSeenFlagsChanged );
|
|
if ( mUserRightsState != KMail::ACLJobs::FetchFailed ) { // No point in overwriting valid results with invalid ones
|
|
configGroup.writeEntry( "UserRights", mUserRights );
|
|
configGroup.writeEntry( "UserRightsState", mUserRightsState );
|
|
}
|
|
|
|
configGroup.deleteEntry( "StorageQuotaUsage");
|
|
configGroup.deleteEntry( "StorageQuotaRoot");
|
|
configGroup.deleteEntry( "StorageQuotaLimit");
|
|
|
|
if ( mQuotaInfo.isValid() ) {
|
|
if ( mQuotaInfo.current().isValid() ) {
|
|
configGroup.writeEntry( "StorageQuotaUsage", mQuotaInfo.current().toInt() );
|
|
}
|
|
if ( mQuotaInfo.max().isValid() ) {
|
|
configGroup.writeEntry( "StorageQuotaLimit", mQuotaInfo.max().toInt() );
|
|
}
|
|
configGroup.writeEntry( "StorageQuotaRoot", mQuotaInfo.root() );
|
|
}
|
|
}
|
|
}
|
|
|
|
int KMFolderCachedImap::create()
|
|
{
|
|
int rc = KMFolderMaildir::create();
|
|
// FIXME why the below? - till
|
|
readConfig();
|
|
mUnreadMsgs = -1;
|
|
return rc;
|
|
}
|
|
|
|
void KMFolderCachedImap::remove()
|
|
{
|
|
mFolderRemoved = true;
|
|
|
|
TQString part1 = folder()->path() + "/." + dotEscape(name());
|
|
TQString uidCacheFile = part1 + ".uidcache";
|
|
// This is the account folder of an account that was just removed
|
|
// When this happens, be sure to delete all traces of the cache
|
|
if( TQFile::exists(uidCacheFile) )
|
|
unlink( TQFile::encodeName( uidCacheFile ) );
|
|
|
|
FolderStorage::remove();
|
|
}
|
|
|
|
TQString KMFolderCachedImap::uidCacheLocation() const
|
|
{
|
|
TQString sLocation(folder()->path());
|
|
if (!sLocation.isEmpty()) sLocation += '/';
|
|
return sLocation + '.' + dotEscape(fileName()) + ".uidcache";
|
|
}
|
|
|
|
int KMFolderCachedImap::readUidCache()
|
|
{
|
|
TQFile uidcache( uidCacheLocation() );
|
|
if( uidcache.open( IO_ReadOnly ) ) {
|
|
char buf[1024];
|
|
int len = uidcache.readLine( buf, sizeof(buf) );
|
|
if( len > 0 ) {
|
|
int cacheVersion;
|
|
sscanf( buf, "# KMail-UidCache V%d\n", &cacheVersion );
|
|
if( cacheVersion == UIDCACHE_VERSION ) {
|
|
len = uidcache.readLine( buf, sizeof(buf) );
|
|
if( len > 0 ) {
|
|
setUidValidity( TQString(TQString::fromLocal8Bit(buf)).stripWhiteSpace() );
|
|
len = uidcache.readLine( buf, sizeof(buf) );
|
|
if( len > 0 ) {
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug(5006) << "Reading in last uid from cache: " << TQString::fromLocal8Bit(buf).stripWhiteSpace() << " in " << folder()->prettyURL() << endl;
|
|
#endif
|
|
// load the last known highest uid from the on disk cache
|
|
setLastUid( TQString(TQString::fromLocal8Bit(buf)).stripWhiteSpace().toULong() );
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int KMFolderCachedImap::writeUidCache()
|
|
{
|
|
if( uidValidity().isEmpty() || uidValidity() == "INVALID" ) {
|
|
// No info from the server yet, remove the file.
|
|
if( TQFile::exists( uidCacheLocation() ) )
|
|
return unlink( TQFile::encodeName( uidCacheLocation() ) );
|
|
return 0;
|
|
}
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug(5006) << "Writing out UID cache lastuid: " << lastUid() << " in: " << folder()->prettyURL() << endl;
|
|
#endif
|
|
TQFile uidcache( uidCacheLocation() );
|
|
if( uidcache.open( IO_WriteOnly ) ) {
|
|
TQTextStream str( &uidcache );
|
|
str << "# KMail-UidCache V" << UIDCACHE_VERSION << endl;
|
|
str << uidValidity() << endl;
|
|
str << lastUid() << endl;
|
|
uidcache.flush();
|
|
if ( uidcache.status() == IO_Ok ) {
|
|
fsync( uidcache.handle() ); /* this is probably overkill */
|
|
uidcache.close();
|
|
if ( uidcache.status() == IO_Ok )
|
|
return 0;
|
|
}
|
|
}
|
|
KMessageBox::error( 0,
|
|
i18n( "The UID cache file for folder %1 could not be written. There "
|
|
"could be a problem with file system permission." ).arg( folder()->prettyURL() ) );
|
|
|
|
return -1;
|
|
}
|
|
|
|
void KMFolderCachedImap::reloadUidMap()
|
|
{
|
|
//kdDebug(5006) << "Reloading Uid Map " << endl;
|
|
uidMap.clear();
|
|
open("reloadUdi");
|
|
for( int i = 0; i < count(); ++i ) {
|
|
KMMsgBase *msg = getMsgBase( i );
|
|
if( !msg ) continue;
|
|
ulong uid = msg->UID();
|
|
//kdDebug(5006) << "Inserting: " << i << " with uid: " << uid << endl;
|
|
uidMap.insert( uid, i );
|
|
}
|
|
close("reloadUdi");
|
|
uidMapDirty = false;
|
|
}
|
|
|
|
KMMessage* KMFolderCachedImap::take(int idx)
|
|
{
|
|
uidMapDirty = true;
|
|
rememberDeletion( idx );
|
|
return KMFolderMaildir::take(idx);
|
|
}
|
|
|
|
void KMFolderCachedImap::takeTemporarily( int idx )
|
|
{
|
|
KMFolderMaildir::take( idx );
|
|
}
|
|
|
|
// Add a message without clearing it's X-UID field.
|
|
int KMFolderCachedImap::addMsgInternal( KMMessage* msg, bool newMail,
|
|
int* index_return )
|
|
{
|
|
// Possible optimization: Only dirty if not filtered below
|
|
ulong uid = msg->UID();
|
|
if( uid != 0 ) {
|
|
uidMapDirty = true;
|
|
}
|
|
|
|
KMFolderOpener openThis(folder(), "KMFolderCachedImap::addMsgInternal");
|
|
int rc = openThis.openResult();
|
|
if ( rc ) {
|
|
kdDebug(5006) << k_funcinfo << "open: " << rc << " of folder: " << label() << endl;
|
|
return rc;
|
|
}
|
|
|
|
// Add the message
|
|
rc = KMFolderMaildir::addMsg(msg, index_return);
|
|
|
|
if( newMail && ( imapPath() == "/INBOX/" ||
|
|
( ( mUserRights != ACLJobs::Ok || userRights() & ACLJobs::Administer)
|
|
&& (contentsType() == ContentsTypeMail || GlobalSettings::self()->filterGroupwareFolders()) ) ) )
|
|
{
|
|
// This is a new message. Filter it - maybe
|
|
bool filter = false;
|
|
if ( GlobalSettings::filterSourceFolders().isEmpty() ) {
|
|
if ( imapPath() == "/INBOX/" )
|
|
filter = true;
|
|
} else {
|
|
if ( GlobalSettings::filterSourceFolders().contains( folder()->id() ) )
|
|
filter = true;
|
|
}
|
|
if ( filter )
|
|
mAccount->processNewMsg( msg );
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Reimplemented from KMFolderMaildir */
|
|
int KMFolderCachedImap::addMsg(KMMessage* msg, int* index_return)
|
|
{
|
|
if ( !canAddMsgNow( msg, index_return ) ) return 0;
|
|
// Add it to storage
|
|
int rc = KMFolderMaildir::addMsgInternal(msg, index_return, true /*stripUID*/);
|
|
return rc;
|
|
}
|
|
|
|
void KMFolderCachedImap::rememberDeletion( int idx )
|
|
{
|
|
KMMsgBase *msg = getMsgBase( idx );
|
|
assert(msg);
|
|
long uid = msg->UID();
|
|
assert(uid>=0);
|
|
mDeletedUIDsSinceLastSync.insert(uid, 0);
|
|
kdDebug(5006) << "Explicit delete of UID " << uid << " at index: " << idx << " in " << folder()->prettyURL() << endl;
|
|
}
|
|
|
|
/* Reimplemented from KMFolderMaildir */
|
|
void KMFolderCachedImap::removeMsg(int idx, bool imapQuiet)
|
|
{
|
|
if ( contentsType() != ContentsTypeMail ) {
|
|
kdDebug(5006) << k_funcinfo << "Deleting message with idx " << idx << " in folder " << label() << endl;
|
|
}
|
|
uidMapDirty = true;
|
|
rememberDeletion( idx );
|
|
// Remove it from disk
|
|
KMFolderMaildir::removeMsg(idx,imapQuiet);
|
|
}
|
|
|
|
bool KMFolderCachedImap::canRemoveFolder() const {
|
|
// If this has subfolders it can't be removed
|
|
if( folder() && folder()->child() && folder()->child()->count() > 0 )
|
|
return false;
|
|
|
|
#if 0
|
|
// No special condition here, so let base class decide
|
|
return KMFolderMaildir::canRemoveFolder();
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
/* Reimplemented from KMFolderDir */
|
|
int KMFolderCachedImap::rename( const TQString& aName,
|
|
KMFolderDir* /*aParent*/ )
|
|
{
|
|
if ( account() == 0 || imapPath().isEmpty() ) {
|
|
// This can happen when creating a folder and then renaming it without syncing before,
|
|
// see https://issues.kolab.org/issue3658
|
|
TQString err = i18n("You must synchronize with the server before renaming IMAP folders.");
|
|
KMessageBox::error( 0, err );
|
|
return -1;
|
|
}
|
|
|
|
TQString oldName = mAccount->renamedFolder( imapPath() );
|
|
if ( oldName.isEmpty() ) oldName = name();
|
|
if ( aName == oldName )
|
|
// Stupid user trying to rename it to it's old name :)
|
|
return 0;
|
|
|
|
// Make the change appear to the user with setLabel, but we'll do the change
|
|
// on the server during the next sync. The name() is the name at the time of
|
|
// the last sync. Only rename if the new one is different. If it's the same,
|
|
// don't rename, but also make sure the rename is reset, in the case of
|
|
// A -> B -> A renames.
|
|
if ( name() != aName )
|
|
mAccount->addRenamedFolder( imapPath(), folder()->label(), aName );
|
|
else
|
|
mAccount->removeRenamedFolder( imapPath() );
|
|
|
|
folder()->setLabel( aName );
|
|
emit nameChanged(); // for kmailicalifaceimpl
|
|
|
|
return 0;
|
|
}
|
|
|
|
KMFolder* KMFolderCachedImap::trashFolder() const
|
|
{
|
|
TQString trashStr = account()->trash();
|
|
return kmkernel->dimapFolderMgr()->findIdString( trashStr );
|
|
}
|
|
|
|
void KMFolderCachedImap::setLastUid( ulong uid )
|
|
{
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug(5006) << "Setting mLastUid to: " << uid << " in " << folder()->prettyURL() << endl;
|
|
#endif
|
|
mLastUid = uid;
|
|
if( uidWriteTimer == -1 )
|
|
// Write in one minute
|
|
uidWriteTimer = startTimer( 60000 );
|
|
}
|
|
|
|
void KMFolderCachedImap::timerEvent( TQTimerEvent* )
|
|
{
|
|
killTimer( uidWriteTimer );
|
|
uidWriteTimer = -1;
|
|
if ( writeUidCache() == -1 )
|
|
unlink( TQFile::encodeName( uidCacheLocation() ) );
|
|
}
|
|
|
|
ulong KMFolderCachedImap::lastUid()
|
|
{
|
|
return mLastUid;
|
|
}
|
|
|
|
KMMsgBase* KMFolderCachedImap::findByUID( ulong uid )
|
|
{
|
|
bool mapReloaded = false;
|
|
if( uidMapDirty ) {
|
|
reloadUidMap();
|
|
mapReloaded = true;
|
|
}
|
|
|
|
TQMap<ulong,int>::Iterator it = uidMap.find( uid );
|
|
if( it != uidMap.end() ) {
|
|
KMMsgBase *msg = getMsgBase( *it );
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug(5006) << "Folder: " << folder()->prettyURL() << endl;
|
|
kdDebug(5006) << "UID " << uid << " is supposed to be in the map" << endl;
|
|
kdDebug(5006) << "UID's index is to be " << *it << endl;
|
|
kdDebug(5006) << "There is a message there? " << (msg != 0) << endl;
|
|
if ( msg ) {
|
|
kdDebug(5006) << "Its UID is: " << msg->UID() << endl;
|
|
}
|
|
#endif
|
|
|
|
if( msg && msg->UID() == uid )
|
|
return msg;
|
|
kdDebug(5006) << "########## Didn't find uid: " << uid << "in cache athough it's supposed to be there!" << endl;
|
|
} else {
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug(5006) << "Didn't find uid: " << uid << "in cache!" << endl;
|
|
#endif
|
|
}
|
|
// Not found by now
|
|
// if( mapReloaded )
|
|
// Not here then
|
|
return 0;
|
|
// There could be a problem in the maps. Rebuild them and try again
|
|
reloadUidMap();
|
|
it = uidMap.find( uid );
|
|
if( it != uidMap.end() )
|
|
// Since the uid map is just rebuilt, no need for the sanity check
|
|
return getMsgBase( *it );
|
|
#if MAIL_LOSS_DEBUGGING
|
|
else
|
|
kdDebug(5006) << "Reloaded, but stil didn't find uid: " << uid << endl;
|
|
#endif
|
|
// Then it's not here
|
|
return 0;
|
|
}
|
|
|
|
// This finds and sets the proper account for this folder if it has
|
|
// not been done
|
|
KMAcctCachedImap *KMFolderCachedImap::account() const
|
|
{
|
|
if( (KMAcctCachedImap *)mAccount == 0 && kmkernel && kmkernel->acctMgr() ) {
|
|
// Find the account
|
|
mAccount = static_cast<KMAcctCachedImap *>( kmkernel->acctMgr()->findByName( name() ) );
|
|
}
|
|
|
|
return mAccount;
|
|
}
|
|
|
|
void KMFolderCachedImap::slotTroubleshoot()
|
|
{
|
|
const int rc = DImapTroubleShootDialog::run();
|
|
|
|
if( rc == DImapTroubleShootDialog::RefreshCache ) {
|
|
// Refresh cache
|
|
if( !account() ) {
|
|
KMessageBox::sorry( 0, i18n("No account setup for this folder.\n"
|
|
"Please try running a sync before this.") );
|
|
return;
|
|
}
|
|
TQString str = i18n("Are you sure you want to refresh the IMAP cache of "
|
|
"the folder %1 and all its subfolders?\nThis will "
|
|
"remove all changes you have done locally to your "
|
|
"folders.").arg( label() );
|
|
TQString s1 = i18n("Refresh IMAP Cache");
|
|
TQString s2 = i18n("&Refresh");
|
|
if( KMessageBox::warningContinueCancel( 0, str, s1, s2 ) ==
|
|
KMessageBox::Continue )
|
|
account()->invalidateIMAPFolders( this );
|
|
} else {
|
|
// Rebuild index file
|
|
switch ( rc ) {
|
|
case DImapTroubleShootDialog::ReindexAll:
|
|
{
|
|
KMFolderCachedImap *rootStorage = dynamic_cast<KMFolderCachedImap*>( account()->rootFolder() );
|
|
if ( rootStorage )
|
|
rootStorage->createIndexFromContentsRecursive();
|
|
break;
|
|
}
|
|
case DImapTroubleShootDialog::ReindexCurrent:
|
|
createIndexFromContents();
|
|
break;
|
|
case DImapTroubleShootDialog::ReindexRecursive:
|
|
createIndexFromContentsRecursive();
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
KMessageBox::information( 0, i18n( "The index of this folder has been "
|
|
"recreated." ) );
|
|
writeIndex();
|
|
kmkernel->getKMMainWidget()->folderSelected();
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::serverSync( bool recurse, bool quotaOnly )
|
|
{
|
|
if( mSyncState != SYNC_STATE_INITIAL ) {
|
|
if( KMessageBox::warningYesNo( 0, i18n("Folder %1 is not in initial sync state (state was %2). Do you want to reset it to initial sync state and sync anyway?" ).arg( imapPath() ).arg( mSyncState ), TQString(), i18n("Reset && Sync"), KStdGuiItem::cancel() ) == KMessageBox::Yes ) {
|
|
mSyncState = SYNC_STATE_INITIAL;
|
|
} else return;
|
|
}
|
|
|
|
mRecurse = recurse;
|
|
mQuotaOnly = quotaOnly;
|
|
assert( account() );
|
|
|
|
ProgressItem *progressItem = mAccount->mailCheckProgressItem();
|
|
if ( progressItem ) {
|
|
progressItem->reset();
|
|
progressItem->setTotalItems( 100 );
|
|
}
|
|
mProgress = 0;
|
|
|
|
#if 0
|
|
if( mHoldSyncs ) {
|
|
// All done for this folder.
|
|
account()->mailCheckProgressItem()->setProgress( 100 );
|
|
mProgress = 100; // all done
|
|
newState( mProgress, i18n("Synchronization skipped"));
|
|
mSyncState = SYNC_STATE_INITIAL;
|
|
emit folderComplete( this, true );
|
|
return;
|
|
}
|
|
#endif
|
|
mTentativeHighestUid = 0; // reset, last sync could have been canceled
|
|
|
|
serverSyncInternal();
|
|
}
|
|
|
|
TQString KMFolderCachedImap::state2String( int state ) const
|
|
{
|
|
switch( state ) {
|
|
case SYNC_STATE_INITIAL: return "SYNC_STATE_INITIAL";
|
|
case SYNC_STATE_GET_USERRIGHTS: return "SYNC_STATE_GET_USERRIGHTS";
|
|
case SYNC_STATE_PUT_MESSAGES: return "SYNC_STATE_PUT_MESSAGES";
|
|
case SYNC_STATE_UPLOAD_FLAGS: return "SYNC_STATE_UPLOAD_FLAGS";
|
|
case SYNC_STATE_CREATE_SUBFOLDERS: return "SYNC_STATE_CREATE_SUBFOLDERS";
|
|
case SYNC_STATE_LIST_SUBFOLDERS: return "SYNC_STATE_LIST_SUBFOLDERS";
|
|
case SYNC_STATE_LIST_NAMESPACES: return "SYNC_STATE_LIST_NAMESPACES";
|
|
case SYNC_STATE_LIST_SUBFOLDERS2: return "SYNC_STATE_LIST_SUBFOLDERS2";
|
|
case SYNC_STATE_DELETE_SUBFOLDERS: return "SYNC_STATE_DELETE_SUBFOLDERS";
|
|
case SYNC_STATE_LIST_MESSAGES: return "SYNC_STATE_LIST_MESSAGES";
|
|
case SYNC_STATE_DELETE_MESSAGES: return "SYNC_STATE_DELETE_MESSAGES";
|
|
case SYNC_STATE_GET_MESSAGES: return "SYNC_STATE_GET_MESSAGES";
|
|
case SYNC_STATE_EXPUNGE_MESSAGES: return "SYNC_STATE_EXPUNGE_MESSAGES";
|
|
case SYNC_STATE_HANDLE_INBOX: return "SYNC_STATE_HANDLE_INBOX";
|
|
case SYNC_STATE_TEST_ANNOTATIONS: return "SYNC_STATE_TEST_ANNOTATIONS";
|
|
case SYNC_STATE_GET_ANNOTATIONS: return "SYNC_STATE_GET_ANNOTATIONS";
|
|
case SYNC_STATE_SET_ANNOTATIONS: return "SYNC_STATE_SET_ANNOTATIONS";
|
|
case SYNC_STATE_GET_ACLS: return "SYNC_STATE_GET_ACLS";
|
|
case SYNC_STATE_SET_ACLS: return "SYNC_STATE_SET_ACLS";
|
|
case SYNC_STATE_GET_TQUOTA: return "SYNC_STATE_GET_TQUOTA";
|
|
case SYNC_STATE_FIND_SUBFOLDERS: return "SYNC_STATE_FIND_SUBFOLDERS";
|
|
case SYNC_STATE_SYNC_SUBFOLDERS: return "SYNC_STATE_SYNC_SUBFOLDERS";
|
|
case SYNC_STATE_RENAME_FOLDER: return "SYNC_STATE_RENAME_FOLDER";
|
|
case SYNC_STATE_CHECK_UIDVALIDITY: return "SYNC_STATE_CHECK_UIDVALIDITY";
|
|
case SYNC_STATE_CLOSE: return "SYNC_STATE_CLOSE";
|
|
case SYNC_STATE_GET_SUBFOLDER_TQUOTA: return "SYNC_STATE_GET_SUBFOLDER_TQUOTA";
|
|
default: return "Unknown state";
|
|
}
|
|
}
|
|
|
|
/*
|
|
Progress calculation: each step is assigned a span. Initially the total is 100.
|
|
But if we skip a step, don't increase the progress.
|
|
This leaves more room for the step a with variable size (get_messages)
|
|
connecting 5
|
|
getuserrights 5
|
|
rename 5
|
|
check_uidvalidity 5
|
|
create_subfolders 5
|
|
put_messages 10 (but it can take a very long time, with many messages....)
|
|
upload_flags 5
|
|
list_subfolders 5
|
|
list_subfolders2 0 (all local)
|
|
delete_subfolders 5
|
|
list_messages 10
|
|
delete_messages 10
|
|
expunge_messages 5
|
|
get_messages variable (remaining-5) i.e. minimum 15.
|
|
check_annotations 0 (rare)
|
|
set_annotations 0 (rare)
|
|
get_annotations 2
|
|
set_acls 0 (rare)
|
|
get_acls 3
|
|
|
|
noContent folders have only a few of the above steps
|
|
(permissions, and all subfolder stuff), so its steps should be given more span
|
|
|
|
*/
|
|
|
|
// While the server synchronization is running, mSyncState will hold
|
|
// the state that should be executed next
|
|
void KMFolderCachedImap::serverSyncInternal()
|
|
{
|
|
// This is used to stop processing when we're about to exit
|
|
// and the current job wasn't cancellable.
|
|
// For user-requested abort, we'll use signalAbortRequested instead.
|
|
if( kmkernel->mailCheckAborted() ) {
|
|
resetSyncState();
|
|
emit folderComplete( this, false );
|
|
return;
|
|
}
|
|
|
|
//kdDebug(5006) << label() << ": " << state2String( mSyncState ) << endl;
|
|
switch( mSyncState ) {
|
|
case SYNC_STATE_INITIAL:
|
|
{
|
|
mProgress = 0;
|
|
foldersForDeletionOnServer.clear();
|
|
newState( mProgress, i18n("Synchronizing"));
|
|
|
|
open("cachedimap");
|
|
if ( !noContent() )
|
|
mAccount->addLastUnreadMsgCount( this, countUnread() );
|
|
|
|
// Connect to the server (i.e. prepare the slave)
|
|
ImapAccountBase::ConnectionState cs = mAccount->makeConnection();
|
|
if ( cs == ImapAccountBase::Error ) {
|
|
// Cancelled by user, or slave can't start
|
|
// kdDebug(5006) << "makeConnection said Error, aborting." << endl;
|
|
// We stop here. We're already in SYNC_STATE_INITIAL for the next time.
|
|
newState( mProgress, i18n( "Error connecting to server %1" ).arg( mAccount->host() ) );
|
|
close("cachedimap");
|
|
emit folderComplete(this, false);
|
|
break;
|
|
} else if ( cs == ImapAccountBase::Connecting ) {
|
|
mAccount->setAnnotationCheckPassed( false );
|
|
// kdDebug(5006) << "makeConnection said Connecting, waiting for signal." << endl;
|
|
newState( mProgress, i18n("Connecting to %1").arg( mAccount->host() ) );
|
|
// We'll wait for the connectionResult signal from the account.
|
|
connect( mAccount, TQT_SIGNAL( connectionResult(int, const TQString&) ),
|
|
this, TQT_SLOT( slotConnectionResult(int, const TQString&) ) );
|
|
break;
|
|
} else {
|
|
// Connected
|
|
// kdDebug(5006) << "makeConnection said Connected, proceeding." << endl;
|
|
mSyncState = SYNC_STATE_GET_USERRIGHTS;
|
|
// Fall through to next state
|
|
}
|
|
}
|
|
|
|
|
|
case SYNC_STATE_GET_USERRIGHTS:
|
|
|
|
// Now we have started the sync, emit changed() so that the folder tree can update the status
|
|
emit syncStateChanged();
|
|
//kdDebug(5006) << "===== Syncing " << ( mImapPath.isEmpty() ? label() : mImapPath ) << endl;
|
|
|
|
mSyncState = SYNC_STATE_RENAME_FOLDER;
|
|
|
|
if( !mQuotaOnly && !noContent() && mAccount->hasACLSupport() ) {
|
|
// Check the user's own rights. We do this every time in case they changed.
|
|
mOldUserRights = mUserRights;
|
|
newState( mProgress, i18n("Checking permissions"));
|
|
connect( mAccount, TQT_SIGNAL( receivedUserRights( KMFolder* ) ),
|
|
this, TQT_SLOT( slotReceivedUserRights( KMFolder* ) ) );
|
|
mAccount->getUserRights( folder(), imapPath() ); // after connecting, due to the INBOX case
|
|
break;
|
|
}
|
|
|
|
else if ( !mQuotaOnly && noContent() && mAccount->hasACLSupport() ) {
|
|
// This is a no content folder. The server would simply say that mailbox does not exist when
|
|
// querying the rights for it. So pretend we have no rights.
|
|
mUserRights = 0;
|
|
mUserRightsState = KMail::ACLJobs::Ok;
|
|
writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
|
|
}
|
|
|
|
case SYNC_STATE_RENAME_FOLDER:
|
|
{
|
|
mSyncState = SYNC_STATE_CHECK_UIDVALIDITY;
|
|
// Returns the new name if the folder was renamed, empty otherwise.
|
|
bool isResourceFolder = kmkernel->iCalIface().isStandardResourceFolder( folder() );
|
|
TQString newName = mAccount->renamedFolder( imapPath() );
|
|
if ( !newName.isEmpty() && !folder()->isSystemFolder() && !isResourceFolder ) {
|
|
newState( mProgress, i18n("Renaming folder") );
|
|
CachedImapJob *job = new CachedImapJob( newName, CachedImapJob::tRenameFolder, this );
|
|
connect( job, TQT_SIGNAL( result(KMail::FolderJob *) ), this, TQT_SLOT( slotIncreaseProgress() ) );
|
|
connect( job, TQT_SIGNAL( finished() ), this, TQT_SLOT( slotRenameFolderFinished() ) );
|
|
job->start();
|
|
break;
|
|
}
|
|
}
|
|
|
|
case SYNC_STATE_CHECK_UIDVALIDITY:
|
|
mSyncState = SYNC_STATE_CREATE_SUBFOLDERS;
|
|
if( !mQuotaOnly && !noContent() ) {
|
|
checkUidValidity();
|
|
break;
|
|
}
|
|
// Else carry on
|
|
|
|
case SYNC_STATE_CREATE_SUBFOLDERS:
|
|
mSyncState = SYNC_STATE_PUT_MESSAGES;
|
|
if ( !mQuotaOnly ) {
|
|
createNewFolders();
|
|
break;
|
|
}
|
|
|
|
case SYNC_STATE_PUT_MESSAGES:
|
|
mSyncState = SYNC_STATE_UPLOAD_FLAGS;
|
|
if( !mQuotaOnly && !noContent() ) {
|
|
uploadNewMessages();
|
|
break;
|
|
}
|
|
// Else carry on
|
|
case SYNC_STATE_UPLOAD_FLAGS:
|
|
mSyncState = SYNC_STATE_LIST_NAMESPACES;
|
|
if( !mQuotaOnly && !noContent() ) {
|
|
// We haven't downloaded messages yet, so we need to build the map.
|
|
if( uidMapDirty )
|
|
reloadUidMap();
|
|
// Upload flags, unless we know from the ACL that we're not allowed
|
|
// to do that or they did not change locally
|
|
if ( mUserRightsState != KMail::ACLJobs::Ok ||
|
|
( mUserRights & (KMail::ACLJobs::WriteFlags ) ) ) {
|
|
if ( !mUIDsOfLocallyChangedStatuses.empty() || mStatusChangedLocally ) {
|
|
uploadFlags();
|
|
break;
|
|
} else {
|
|
//kdDebug(5006) << "Skipping flags upload, folder unchanged: " << label() << endl;
|
|
}
|
|
} else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) {
|
|
if ( !mUIDsOfLocallyChangedStatuses.empty() || mStatusChangedLocally ) {
|
|
uploadSeenFlags();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Else carry on
|
|
|
|
case SYNC_STATE_LIST_NAMESPACES:
|
|
if ( !mQuotaOnly && this == mAccount->rootFolder() ) {
|
|
listNamespaces();
|
|
break;
|
|
}
|
|
mSyncState = SYNC_STATE_LIST_SUBFOLDERS;
|
|
// Else carry on
|
|
|
|
case SYNC_STATE_LIST_SUBFOLDERS:
|
|
newState( mProgress, i18n("Retrieving folderlist"));
|
|
mSyncState = SYNC_STATE_LIST_SUBFOLDERS2;
|
|
if ( !mQuotaOnly ) {
|
|
if( !listDirectory() ) {
|
|
mSyncState = SYNC_STATE_INITIAL;
|
|
KMessageBox::error(0, i18n("Error while retrieving the folderlist"));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SYNC_STATE_LIST_SUBFOLDERS2:
|
|
mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
|
|
mProgress += 10;
|
|
if ( !mQuotaOnly ) {
|
|
newState( mProgress, i18n("Retrieving subfolders"));
|
|
listDirectory2();
|
|
break;
|
|
}
|
|
|
|
case SYNC_STATE_DELETE_SUBFOLDERS:
|
|
mSyncState = SYNC_STATE_LIST_MESSAGES;
|
|
if( !mQuotaOnly && !foldersForDeletionOnServer.isEmpty() ) {
|
|
newState( mProgress, i18n("Deleting folders from server"));
|
|
CachedImapJob* job = new CachedImapJob( foldersForDeletionOnServer,
|
|
CachedImapJob::tDeleteFolders, this );
|
|
connect( job, TQT_SIGNAL( result(KMail::FolderJob *) ), this, TQT_SLOT( slotIncreaseProgress() ) );
|
|
connect( job, TQT_SIGNAL( finished() ), this, TQT_SLOT( slotFolderDeletionOnServerFinished() ) );
|
|
job->start();
|
|
break;
|
|
}
|
|
// Not needed, the next step emits newState very quick
|
|
//newState( mProgress, i18n("No folders to delete from server"));
|
|
// Carry on
|
|
|
|
case SYNC_STATE_LIST_MESSAGES:
|
|
mSyncState = SYNC_STATE_DELETE_MESSAGES;
|
|
if( !mQuotaOnly && !noContent() ) {
|
|
newState( mProgress, i18n("Retrieving message list"));
|
|
listMessages();
|
|
break;
|
|
}
|
|
// Else carry on
|
|
|
|
case SYNC_STATE_DELETE_MESSAGES:
|
|
mSyncState = SYNC_STATE_EXPUNGE_MESSAGES;
|
|
if( !mQuotaOnly && !noContent() ) {
|
|
if( deleteMessages() ) {
|
|
// Fine, we will continue with the next state
|
|
} else {
|
|
// No messages to delete, skip to GET_MESSAGES
|
|
newState( mProgress, i18n("No messages to delete..."));
|
|
mSyncState = SYNC_STATE_GET_MESSAGES;
|
|
serverSyncInternal();
|
|
}
|
|
break;
|
|
}
|
|
// Else carry on
|
|
|
|
case SYNC_STATE_EXPUNGE_MESSAGES:
|
|
mSyncState = SYNC_STATE_GET_MESSAGES;
|
|
if( !mQuotaOnly && !noContent() ) {
|
|
newState( mProgress, i18n("Expunging deleted messages"));
|
|
CachedImapJob *job = new CachedImapJob( TQString(),
|
|
CachedImapJob::tExpungeFolder, this );
|
|
connect( job, TQT_SIGNAL( result(KMail::FolderJob *) ), this, TQT_SLOT( slotIncreaseProgress() ) );
|
|
connect( job, TQT_SIGNAL( finished() ), this, TQT_SLOT( serverSyncInternal() ) );
|
|
job->start();
|
|
break;
|
|
}
|
|
// Else carry on
|
|
|
|
case SYNC_STATE_GET_MESSAGES:
|
|
mSyncState = SYNC_STATE_HANDLE_INBOX;
|
|
if( !mQuotaOnly && !noContent() ) {
|
|
if( !mMsgsForDownload.isEmpty() ) {
|
|
newState( mProgress, i18n("Retrieving one new message","Retrieving %n new messages",mMsgsForDownload.size()));
|
|
CachedImapJob *job = new CachedImapJob( mMsgsForDownload,
|
|
CachedImapJob::tGetMessage,
|
|
this );
|
|
connect( job, TQT_SIGNAL( progress(unsigned long, unsigned long) ),
|
|
this, TQT_SLOT( slotProgress(unsigned long, unsigned long) ) );
|
|
connect( job, TQT_SIGNAL( finished() ), this, TQT_SLOT( slotUpdateLastUid() ) );
|
|
connect( job, TQT_SIGNAL( finished() ), this, TQT_SLOT( serverSyncInternal() ) );
|
|
job->start();
|
|
mMsgsForDownload.clear();
|
|
break;
|
|
} else {
|
|
newState( mProgress, i18n("No new messages from server"));
|
|
/* There were no messages to download, but it could be that we uploaded some
|
|
which we didn't need to download again because we already knew the uid.
|
|
Now that we are sure there is nothing to download, and everything that had
|
|
to be deleted on the server has been deleted, adjust our local notion of the
|
|
highes uid seen thus far. */
|
|
slotUpdateLastUid();
|
|
if( mLastUid == 0 && uidWriteTimer == -1 ) {
|
|
// This is probably a new and empty folder. Write the UID cache
|
|
if ( writeUidCache() == -1 ) {
|
|
resetSyncState();
|
|
emit folderComplete( this, false );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Else carry on
|
|
|
|
case SYNC_STATE_HANDLE_INBOX:
|
|
// Wrap up the 'download emails' stage. We always end up at 95 here.
|
|
mProgress = 95;
|
|
mSyncState = SYNC_STATE_TEST_ANNOTATIONS;
|
|
|
|
#define KOLAB_FOLDERTEST "/vendor/kolab/folder-test"
|
|
case SYNC_STATE_TEST_ANNOTATIONS:
|
|
mSyncState = SYNC_STATE_GET_ANNOTATIONS;
|
|
// The first folder with user rights to write annotations
|
|
if( !mQuotaOnly && !mAccount->annotationCheckPassed() &&
|
|
( mUserRightsState != KMail::ACLJobs::Ok || ( mUserRights & ACLJobs::Administer ) )
|
|
&& !imapPath().isEmpty() && imapPath() != "/" ) {
|
|
kdDebug(5006) << "Setting test attribute on folder: "<< folder()->prettyURL() << endl;
|
|
newState( mProgress, i18n("Checking annotation support"));
|
|
|
|
KURL url = mAccount->getUrl();
|
|
url.setPath( imapPath() );
|
|
KMail::AnnotationList annotations; // to be set
|
|
|
|
KMail::AnnotationAttribute attr( KOLAB_FOLDERTEST, "value.shared", "true" );
|
|
annotations.append( attr );
|
|
|
|
kdDebug(5006) << "Setting test attribute to "<< url << endl;
|
|
KIO::Job* job = AnnotationJobs::multiSetAnnotation( mAccount->slave(),
|
|
url, annotations );
|
|
ImapAccountBase::jobData jd( url.url(), folder() );
|
|
jd.cancellable = true; // we can always do so later
|
|
mAccount->insertJob(job, jd);
|
|
connect(job, TQT_SIGNAL(result(KIO::Job *)),
|
|
TQT_SLOT(slotTestAnnotationResult(KIO::Job *)));
|
|
break;
|
|
}
|
|
|
|
case SYNC_STATE_GET_ANNOTATIONS: {
|
|
#define KOLAB_FOLDERTYPE "/vendor/kolab/folder-type"
|
|
#define KOLAB_INCIDENCESFOR "/vendor/kolab/incidences-for"
|
|
#define KOLAB_SHAREDSEEN "/vendor/cmu/cyrus-imapd/sharedseen"
|
|
//#define KOLAB_FOLDERTYPE "/comment" //for testing, while cyrus-imap doesn't support /vendor/*
|
|
mSyncState = SYNC_STATE_SET_ANNOTATIONS;
|
|
|
|
bool needToGetInitialAnnotations = false;
|
|
if ( !mQuotaOnly && !noContent() ) {
|
|
// for a folder we didn't create ourselves: get annotation from server
|
|
if ( mAnnotationFolderType == "FROMSERVER" ) {
|
|
needToGetInitialAnnotations = true;
|
|
mAnnotationFolderType = TQString();
|
|
} else {
|
|
updateAnnotationFolderType();
|
|
}
|
|
}
|
|
|
|
// First retrieve the annotation, so that we know we have to set it if it's not set.
|
|
// On the other hand, if the user changed the contentstype, there's no need to get first.
|
|
if ( !mQuotaOnly && !noContent() && mAccount->hasAnnotationSupport() &&
|
|
( kmkernel->iCalIface().isEnabled() || needToGetInitialAnnotations ) ) {
|
|
TQStringList annotations; // list of annotations to be fetched
|
|
if ( !mAnnotationFolderTypeChanged || mAnnotationFolderType.isEmpty() )
|
|
annotations << KOLAB_FOLDERTYPE;
|
|
if ( !mIncidencesForChanged )
|
|
annotations << KOLAB_INCIDENCESFOR;
|
|
if ( !mSharedSeenFlagsChanged )
|
|
annotations << KOLAB_SHAREDSEEN;
|
|
if ( !annotations.isEmpty() ) {
|
|
newState( mProgress, i18n("Retrieving annotations"));
|
|
KURL url = mAccount->getUrl();
|
|
url.setPath( imapPath() );
|
|
AnnotationJobs::MultiGetAnnotationJob* job =
|
|
AnnotationJobs::multiGetAnnotation( mAccount->slave(), url, annotations );
|
|
ImapAccountBase::jobData jd( url.url(), folder() );
|
|
jd.cancellable = true;
|
|
mAccount->insertJob(job, jd);
|
|
|
|
connect( job, TQT_SIGNAL(annotationResult(const TQString&, const TQString&, bool)),
|
|
TQT_SLOT(slotAnnotationResult(const TQString&, const TQString&, bool)) );
|
|
connect( job, TQT_SIGNAL(result(KIO::Job *)),
|
|
TQT_SLOT(slotGetAnnotationResult(KIO::Job *)) );
|
|
break;
|
|
}
|
|
}
|
|
} // case
|
|
case SYNC_STATE_SET_ANNOTATIONS:
|
|
|
|
mSyncState = SYNC_STATE_SET_ACLS;
|
|
if ( !mQuotaOnly && !noContent() && mAccount->hasAnnotationSupport() &&
|
|
( mUserRightsState != KMail::ACLJobs::Ok || ( mUserRights & ACLJobs::Administer ) ) ) {
|
|
newState( mProgress, i18n("Setting annotations"));
|
|
KURL url = mAccount->getUrl();
|
|
url.setPath( imapPath() );
|
|
KMail::AnnotationList annotations; // to be set
|
|
if ( mAnnotationFolderTypeChanged && !mAnnotationFolderType.isEmpty() ) {
|
|
KMail::AnnotationAttribute attr( KOLAB_FOLDERTYPE, "value.shared", mAnnotationFolderType );
|
|
annotations.append( attr );
|
|
kdDebug(5006) << "Setting folder-type annotation for " << label() << " to " << mAnnotationFolderType << endl;
|
|
}
|
|
if ( mIncidencesForChanged ) {
|
|
const TQString val = incidencesForToString( mIncidencesFor );
|
|
KMail::AnnotationAttribute attr( KOLAB_INCIDENCESFOR, "value.shared", val );
|
|
annotations.append( attr );
|
|
kdDebug(5006) << "Setting incidences-for annotation for " << label() << " to " << val << endl;
|
|
}
|
|
if ( mSharedSeenFlagsChanged ) {
|
|
const TQString val = mSharedSeenFlags ? "true" : "false";
|
|
KMail::AnnotationAttribute attr( KOLAB_SHAREDSEEN, "value.shared", val );
|
|
annotations.append( attr );
|
|
kdDebug(5006) << k_funcinfo << "Setting sharedseen annotation for " << label() << " to " << val << endl;
|
|
}
|
|
if ( !annotations.isEmpty() ) {
|
|
KIO::Job* job =
|
|
AnnotationJobs::multiSetAnnotation( mAccount->slave(), url, annotations );
|
|
ImapAccountBase::jobData jd( url.url(), folder() );
|
|
jd.cancellable = true; // we can always do so later
|
|
mAccount->insertJob(job, jd);
|
|
|
|
connect(job, TQT_SIGNAL(annotationChanged( const TQString&, const TQString&, const TQString& ) ),
|
|
TQT_SLOT( slotAnnotationChanged( const TQString&, const TQString&, const TQString& ) ));
|
|
connect(job, TQT_SIGNAL(result(KIO::Job *)),
|
|
TQT_SLOT(slotSetAnnotationResult(KIO::Job *)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
case SYNC_STATE_SET_ACLS:
|
|
mSyncState = SYNC_STATE_GET_ACLS;
|
|
|
|
if( !mQuotaOnly && !noContent() && mAccount->hasACLSupport() &&
|
|
( mUserRightsState != KMail::ACLJobs::Ok || ( mUserRights & ACLJobs::Administer ) ) ) {
|
|
bool hasChangedACLs = false;
|
|
ACLList::ConstIterator it = mACLList.begin();
|
|
for ( ; it != mACLList.end() && !hasChangedACLs; ++it ) {
|
|
hasChangedACLs = (*it).changed;
|
|
}
|
|
if ( hasChangedACLs ) {
|
|
newState( mProgress, i18n("Setting permissions"));
|
|
KURL url = mAccount->getUrl();
|
|
url.setPath( imapPath() );
|
|
KIO::Job* job = KMail::ACLJobs::multiSetACL( mAccount->slave(), url, mACLList );
|
|
ImapAccountBase::jobData jd( url.url(), folder() );
|
|
mAccount->insertJob(job, jd);
|
|
|
|
connect(job, TQT_SIGNAL(result(KIO::Job *)),
|
|
TQT_SLOT(slotMultiSetACLResult(KIO::Job *)));
|
|
connect(job, TQT_SIGNAL(aclChanged( const TQString&, int )),
|
|
TQT_SLOT(slotACLChanged( const TQString&, int )) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
case SYNC_STATE_GET_ACLS:
|
|
mSyncState = SYNC_STATE_FIND_SUBFOLDERS;
|
|
|
|
if( !mQuotaOnly && !noContent() && mAccount->hasACLSupport() ) {
|
|
newState( mProgress, i18n( "Retrieving permissions" ) );
|
|
mAccount->getACL( folder(), mImapPath );
|
|
connect( mAccount, TQT_SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
|
|
this, TQT_SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
|
|
break;
|
|
}
|
|
case SYNC_STATE_FIND_SUBFOLDERS:
|
|
{
|
|
mSyncState = SYNC_STATE_SYNC_SUBFOLDERS;
|
|
mSomeSubFolderCloseToQuotaChanged = false;
|
|
buildSubFolderList();
|
|
}
|
|
|
|
// Carry on
|
|
case SYNC_STATE_SYNC_SUBFOLDERS:
|
|
syncNextSubFolder( false );
|
|
break;
|
|
case SYNC_STATE_GET_SUBFOLDER_TQUOTA:
|
|
|
|
// Sync the subfolders again, so that the quota information is updated for all. This state is
|
|
// only entered if the close to quota property of one subfolder changed in the previous state.
|
|
syncNextSubFolder( true );
|
|
break;
|
|
case SYNC_STATE_GET_TQUOTA:
|
|
mSyncState = SYNC_STATE_CLOSE;
|
|
if( !noContent() && mAccount->hasQuotaSupport() ) {
|
|
mProgress = 98;
|
|
newState( mProgress, i18n("Getting quota information"));
|
|
KURL url = mAccount->getUrl();
|
|
url.setPath( imapPath() );
|
|
KIO::Job* job = KMail::QuotaJobs::getStorageQuota( mAccount->slave(), url );
|
|
ImapAccountBase::jobData jd( url.url(), folder() );
|
|
mAccount->insertJob(job, jd);
|
|
connect( job, TQT_SIGNAL( storageQuotaResult( const QuotaInfo& ) ),
|
|
TQT_SLOT( slotStorageQuotaResult( const QuotaInfo& ) ) );
|
|
connect( job, TQT_SIGNAL(result(KIO::Job *)),
|
|
TQT_SLOT(slotQuotaResult(KIO::Job *)) );
|
|
break;
|
|
}
|
|
case SYNC_STATE_CLOSE:
|
|
{
|
|
mProgress = 100; // all done
|
|
newState( mProgress, i18n("Synchronization done"));
|
|
KURL url = mAccount->getUrl();
|
|
url.setPath( imapPath() );
|
|
kmkernel->iCalIface().folderSynced( folder(), url );
|
|
|
|
mSyncState = SYNC_STATE_INITIAL;
|
|
mAccount->addUnreadMsgCount( this, countUnread() ); // before closing
|
|
close( "cachedimap" );
|
|
emit syncStateChanged();
|
|
emit folderComplete( this, true );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
kdDebug(5006) << "KMFolderCachedImap::serverSyncInternal() WARNING: no such state "
|
|
<< mSyncState << endl;
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::syncNextSubFolder( bool secondSync )
|
|
{
|
|
if( mCurrentSubfolder ) {
|
|
disconnectSubFolderSignals();
|
|
}
|
|
|
|
if( mSubfoldersForSync.isEmpty() ) {
|
|
|
|
// Sync finished, and a close to quota property of an subfolder changed, therefore go into
|
|
// the SYNC_STATE_GET_SUBFOLDER_TQUOTA state and sync again
|
|
if ( mSomeSubFolderCloseToQuotaChanged && mRecurse && !secondSync ) {
|
|
buildSubFolderList();
|
|
mSyncState = SYNC_STATE_GET_SUBFOLDER_TQUOTA;
|
|
serverSyncInternal();
|
|
}
|
|
|
|
else {
|
|
|
|
// Quota checking has to come after syncing subfolder, otherwise the quota information would
|
|
// be outdated, since the subfolders can change in size during the syncing.
|
|
// https://issues.kolab.org/issue4066
|
|
mSyncState = SYNC_STATE_GET_TQUOTA;
|
|
serverSyncInternal();
|
|
}
|
|
} else {
|
|
mCurrentSubfolder = mSubfoldersForSync.front();
|
|
mSubfoldersForSync.pop_front();
|
|
if ( mCurrentSubfolder ) {
|
|
connect( mCurrentSubfolder, TQT_SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
|
|
this, TQT_SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
|
|
connect( mCurrentSubfolder, TQT_SIGNAL( closeToQuotaChanged() ),
|
|
this, TQT_SLOT( slotSubFolderCloseToQuotaChanged() ) );
|
|
|
|
//kdDebug(5006) << "Sync'ing subfolder " << mCurrentSubfolder->imapPath() << endl;
|
|
assert( !mCurrentSubfolder->imapPath().isEmpty() );
|
|
mCurrentSubfolder->setAccount( account() );
|
|
const bool recurse = mCurrentSubfolder->noChildren() ? false : true;
|
|
const bool quotaOnly = secondSync || mQuotaOnly;
|
|
mCurrentSubfolder->serverSync( recurse, quotaOnly );
|
|
}
|
|
else {
|
|
// mCurrentSubfolder is empty, probably because it was deleted while syncing. Go on with the
|
|
// next subfolder instead.
|
|
syncNextSubFolder( secondSync );
|
|
}
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::buildSubFolderList()
|
|
{
|
|
mSubfoldersForSync.clear();
|
|
mCurrentSubfolder = 0;
|
|
if( folder() && folder()->child() ) {
|
|
KMFolderNode *node = folder()->child()->first();
|
|
while( node ) {
|
|
if( !node->isDir() ) {
|
|
KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
|
|
const bool folderIsNew = mNewlyCreatedSubfolders.contains( TQGuardedPtr<KMFolderCachedImap>( storage ) );
|
|
// Only sync folders that have been accepted by the server
|
|
if ( !storage->imapPath().isEmpty()
|
|
// and that were not just deleted from it
|
|
&& !foldersForDeletionOnServer.contains( storage->imapPath() ) ) {
|
|
if ( mRecurse || folderIsNew ) {
|
|
mSubfoldersForSync << storage;
|
|
}
|
|
} else {
|
|
kdDebug(5006) << "Do not add " << storage->label()
|
|
<< " to synclist" << endl;
|
|
}
|
|
}
|
|
node = folder()->child()->next();
|
|
}
|
|
}
|
|
|
|
mNewlyCreatedSubfolders.clear();
|
|
}
|
|
|
|
void KMFolderCachedImap::disconnectSubFolderSignals()
|
|
{
|
|
disconnect( mCurrentSubfolder, TQT_SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
|
|
this, TQT_SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
|
|
disconnect( mCurrentSubfolder, TQT_SIGNAL( closeToQuotaChanged() ),
|
|
this, TQT_SLOT( slotSubFolderCloseToQuotaChanged() ) );
|
|
mCurrentSubfolder = 0;
|
|
}
|
|
|
|
/* Connected to the imap account's connectionResult signal.
|
|
Emitted when the slave connected or failed to connect.
|
|
*/
|
|
void KMFolderCachedImap::slotConnectionResult( int errorCode, const TQString& errorMsg )
|
|
{
|
|
disconnect( mAccount, TQT_SIGNAL( connectionResult(int, const TQString&) ),
|
|
this, TQT_SLOT( slotConnectionResult(int, const TQString&) ) );
|
|
if ( !errorCode ) {
|
|
// Success
|
|
mSyncState = SYNC_STATE_GET_USERRIGHTS;
|
|
mProgress += 5;
|
|
serverSyncInternal();
|
|
} else {
|
|
// Error (error message already shown by the account)
|
|
newState( mProgress, KIO::buildErrorString( errorCode, errorMsg ));
|
|
emit folderComplete(this, false);
|
|
}
|
|
}
|
|
|
|
/* find new messages (messages without a UID) */
|
|
TQValueList<unsigned long> KMFolderCachedImap::findNewMessages()
|
|
{
|
|
TQValueList<unsigned long> result;
|
|
for( int i = 0; i < count(); ++i ) {
|
|
KMMsgBase *msg = getMsgBase( i );
|
|
if( !msg ) continue; /* what goes on if getMsg() returns 0? */
|
|
if ( msg->UID() == 0 )
|
|
result.append( msg->getMsgSerNum() );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Upload new messages to server */
|
|
void KMFolderCachedImap::uploadNewMessages()
|
|
{
|
|
TQValueList<unsigned long> newMsgs = findNewMessages();
|
|
if( !newMsgs.isEmpty() ) {
|
|
if ( mUserRightsState != KMail::ACLJobs::Ok || ( mUserRights & ( KMail::ACLJobs::Insert ) ) ) {
|
|
newState( mProgress, i18n("Uploading messages to server"));
|
|
CachedImapJob *job = new CachedImapJob( newMsgs, CachedImapJob::tPutMessage, this );
|
|
connect( job, TQT_SIGNAL( progress( unsigned long, unsigned long) ),
|
|
this, TQT_SLOT( slotPutProgress(unsigned long, unsigned long) ) );
|
|
connect( job, TQT_SIGNAL( finished() ), this, TQT_SLOT( serverSyncInternal() ) );
|
|
job->start();
|
|
return;
|
|
} else {
|
|
KMCommand *command = rescueUnsyncedMessages();
|
|
connect( command, TQT_SIGNAL( completed( KMCommand * ) ),
|
|
this, TQT_SLOT( serverSyncInternal() ) );
|
|
}
|
|
} else { // nothing to upload
|
|
if ( mUserRights != mOldUserRights && (mOldUserRights & KMail::ACLJobs::Insert)
|
|
&& !(mUserRights & KMail::ACLJobs::Insert) ) {
|
|
// write access revoked
|
|
KMessageBox::information( 0, i18n("<p>Your access rights to folder <b>%1</b> have been restricted, "
|
|
"it will no longer be possible to add messages to this folder.</p>").arg( folder()->prettyURL() ),
|
|
i18n("Acces rights revoked"), "KMailACLRevocationNotification" );
|
|
}
|
|
}
|
|
newState( mProgress, i18n("No messages to upload to server"));
|
|
serverSyncInternal();
|
|
}
|
|
|
|
/* Progress info during uploadNewMessages */
|
|
void KMFolderCachedImap::slotPutProgress( unsigned long done, unsigned long total )
|
|
{
|
|
// (going from mProgress to mProgress+10)
|
|
int progressSpan = 10;
|
|
newState( mProgress + (progressSpan * done) / total, TQString() );
|
|
if ( done == total ) // we're done
|
|
mProgress += progressSpan;
|
|
}
|
|
|
|
/* Upload message flags to server */
|
|
void KMFolderCachedImap::uploadFlags()
|
|
{
|
|
if ( !uidMap.isEmpty() ) {
|
|
mStatusFlagsJobs = 0;
|
|
newState( mProgress, i18n("Uploading status of messages to server"));
|
|
|
|
// FIXME DUPLICATED FROM KMFOLDERIMAP
|
|
TQMap< TQString, TQStringList > groups;
|
|
//open(); //already done
|
|
for( int i = 0; i < count(); ++i ) {
|
|
KMMsgBase* msg = getMsgBase( i );
|
|
if( !msg || msg->UID() == 0 )
|
|
// Either not a valid message or not one that is on the server yet
|
|
continue;
|
|
if ( mUIDsOfLocallyChangedStatuses.find( msg->UID() ) == mUIDsOfLocallyChangedStatuses.end()
|
|
&& !mStatusChangedLocally ) {
|
|
// This message has not had its status changed locally
|
|
continue;
|
|
}
|
|
|
|
TQString flags = KMFolderImap::statusToFlags(msg->status(), mPermanentFlags);
|
|
// Collect uids for each typem of flags.
|
|
TQString uid;
|
|
uid.setNum( msg->UID() );
|
|
groups[flags].append(uid);
|
|
}
|
|
TQMapIterator< TQString, TQStringList > dit;
|
|
for( dit = groups.begin(); dit != groups.end(); ++dit ) {
|
|
TQCString flags = dit.key().latin1();
|
|
TQStringList sets = KMFolderImap::makeSets( (*dit), true );
|
|
mStatusFlagsJobs += sets.count(); // ### that's not in kmfolderimap....
|
|
// Send off a status setting job for each set.
|
|
for( TQStringList::Iterator slit = sets.begin(); slit != sets.end(); ++slit ) {
|
|
TQString imappath = imapPath() + ";UID=" + ( *slit );
|
|
mAccount->setImapStatus(folder(), imappath, flags);
|
|
}
|
|
}
|
|
// FIXME END DUPLICATED FROM KMFOLDERIMAP
|
|
|
|
if ( mStatusFlagsJobs ) {
|
|
connect( mAccount, TQT_SIGNAL( imapStatusChanged(KMFolder*, const TQString&, bool) ),
|
|
this, TQT_SLOT( slotImapStatusChanged(KMFolder*, const TQString&, bool) ) );
|
|
return;
|
|
}
|
|
}
|
|
newState( mProgress, i18n("No messages to upload to server"));
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void KMFolderCachedImap::uploadSeenFlags()
|
|
{
|
|
if ( !uidMap.isEmpty() ) {
|
|
mStatusFlagsJobs = 0;
|
|
newState( mProgress, i18n("Uploading status of messages to server"));
|
|
|
|
TQValueList<ulong> seenUids, unseenUids;
|
|
for( int i = 0; i < count(); ++i ) {
|
|
KMMsgBase* msg = getMsgBase( i );
|
|
if( !msg || msg->UID() == 0 )
|
|
// Either not a valid message or not one that is on the server yet
|
|
continue;
|
|
|
|
if ( mUIDsOfLocallyChangedStatuses.find( msg->UID() ) == mUIDsOfLocallyChangedStatuses.end()
|
|
&& !mStatusChangedLocally ) {
|
|
// This message has not had its status changed locally
|
|
continue;
|
|
}
|
|
|
|
if ( msg->status() & KMMsgStatusOld || msg->status() & KMMsgStatusRead )
|
|
seenUids.append( msg->UID() );
|
|
else
|
|
unseenUids.append( msg->UID() );
|
|
}
|
|
if ( !seenUids.isEmpty() ) {
|
|
TQStringList sets = KMFolderImap::makeSets( seenUids, true );
|
|
mStatusFlagsJobs += sets.count();
|
|
for( TQStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) {
|
|
TQString imappath = imapPath() + ";UID=" + ( *it );
|
|
mAccount->setImapSeenStatus( folder(), imappath, true );
|
|
}
|
|
}
|
|
if ( !unseenUids.isEmpty() ) {
|
|
TQStringList sets = KMFolderImap::makeSets( unseenUids, true );
|
|
mStatusFlagsJobs += sets.count();
|
|
for( TQStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) {
|
|
TQString imappath = imapPath() + ";UID=" + ( *it );
|
|
mAccount->setImapSeenStatus( folder(), imappath, false );
|
|
}
|
|
}
|
|
|
|
if ( mStatusFlagsJobs ) {
|
|
connect( mAccount, TQT_SIGNAL( imapStatusChanged(KMFolder*, const TQString&, bool) ),
|
|
this, TQT_SLOT( slotImapStatusChanged(KMFolder*, const TQString&, bool) ) );
|
|
return;
|
|
}
|
|
}
|
|
newState( mProgress, i18n("No messages to upload to server"));
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void KMFolderCachedImap::slotImapStatusChanged(KMFolder* folder, const TQString&, bool cont)
|
|
{
|
|
if ( mSyncState == SYNC_STATE_INITIAL ){
|
|
//kdDebug(5006) << "IMAP status changed but reset " << endl;
|
|
return; // we were reset
|
|
}
|
|
//kdDebug(5006) << "IMAP status changed for folder: " << folder->prettyURL() << endl;
|
|
if ( folder->storage() == this ) {
|
|
--mStatusFlagsJobs;
|
|
if ( mStatusFlagsJobs == 0 || !cont ) // done or aborting
|
|
disconnect( mAccount, TQT_SIGNAL( imapStatusChanged(KMFolder*, const TQString&, bool) ),
|
|
this, TQT_SLOT( slotImapStatusChanged(KMFolder*, const TQString&, bool) ) );
|
|
if ( mStatusFlagsJobs == 0 && cont ) {
|
|
mProgress += 5;
|
|
serverSyncInternal();
|
|
//kdDebug(5006) << "Proceeding with mailcheck." << endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is not perfect, what if the status didn't really change? Oh well ...
|
|
void KMFolderCachedImap::seStatus( int idx, KMMsgStatus status, bool toggle)
|
|
{
|
|
KMFolderMaildir::seStatus( idx, status, toggle );
|
|
const KMMsgBase *msg = getMsgBase( idx );
|
|
Q_ASSERT( msg );
|
|
if ( msg )
|
|
mUIDsOfLocallyChangedStatuses.insert( msg->UID() );
|
|
}
|
|
|
|
void KMFolderCachedImap::seStatus(TQValueList<int>& ids, KMMsgStatus status, bool toggle)
|
|
{
|
|
KMFolderMaildir::seStatus(ids, status, toggle);
|
|
for (TQValueList<int>::iterator it = ids.begin(); it != ids.end(); it++ ) {
|
|
const KMMsgBase *msg = getMsgBase( *it );
|
|
Q_ASSERT( msg );
|
|
if ( msg )
|
|
mUIDsOfLocallyChangedStatuses.insert( msg->UID() );
|
|
}
|
|
}
|
|
|
|
/* Upload new folders to server */
|
|
void KMFolderCachedImap::createNewFolders()
|
|
{
|
|
TQValueList<KMFolderCachedImap*> newFolders = findNewFolders();
|
|
//kdDebug(5006) << label() << " createNewFolders:" << newFolders.count() << " new folders." << endl;
|
|
if( !newFolders.isEmpty() ) {
|
|
newState( mProgress, i18n("Creating subfolders on server"));
|
|
CachedImapJob *job = new CachedImapJob( newFolders, CachedImapJob::tAddSubfolders, this );
|
|
connect( job, TQT_SIGNAL( result(KMail::FolderJob *) ), this, TQT_SLOT( slotIncreaseProgress() ) );
|
|
connect( job, TQT_SIGNAL( finished() ), this, TQT_SLOT( serverSyncInternal() ) );
|
|
job->start();
|
|
} else {
|
|
serverSyncInternal();
|
|
}
|
|
}
|
|
|
|
TQValueList<KMFolderCachedImap*> KMFolderCachedImap::findNewFolders()
|
|
{
|
|
TQValueList<KMFolderCachedImap*> newFolders;
|
|
if( folder() && folder()->child() ) {
|
|
KMFolderNode *node = folder()->child()->first();
|
|
while( node ) {
|
|
if( !node->isDir() ) {
|
|
if( static_cast<KMFolder*>(node)->folderType() != KMFolderTypeCachedImap ) {
|
|
kdError(5006) << "KMFolderCachedImap::findNewFolders(): ARGH!!! "
|
|
<< node->name() << " is not an IMAP folder\n";
|
|
node = folder()->child()->next();
|
|
assert(0);
|
|
}
|
|
KMFolderCachedImap* folder = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
|
|
if( folder->imapPath().isEmpty() ) {
|
|
newFolders << folder;
|
|
}
|
|
}
|
|
node = folder()->child()->next();
|
|
}
|
|
}
|
|
return newFolders;
|
|
}
|
|
|
|
bool KMFolderCachedImap::deleteMessages()
|
|
{
|
|
/* Delete messages from cache that are gone from the server */
|
|
TQPtrList<KMMsgBase> msgsForDeletion;
|
|
|
|
// It is not possible to just go over all indices and remove
|
|
// them one by one because the index list can get resized under
|
|
// us. So use msg pointers instead
|
|
|
|
TQStringList uids;
|
|
TQMap<ulong,int>::const_iterator it = uidMap.constBegin();
|
|
for( ; it != uidMap.end(); it++ ) {
|
|
ulong uid ( it.key() );
|
|
if( uid!=0 && !uidsOnServer.find( uid ) ) {
|
|
uids << TQString::number( uid );
|
|
msgsForDeletion.append( getMsgBase( *it ) );
|
|
}
|
|
}
|
|
|
|
if( !msgsForDeletion.isEmpty() ) {
|
|
if ( contentsType() != ContentsTypeMail ) {
|
|
kdDebug(5006) << k_funcinfo << label() << " Going to locally delete " << msgsForDeletion.count()
|
|
<< " messages, with the uids " << uids.join( "," ) << endl;
|
|
}
|
|
#if MAIL_LOSS_DEBUGGING
|
|
if ( KMessageBox::warningYesNo(
|
|
0, i18n( "<qt><p>Mails on the server in folder <b>%1</b> were deleted. "
|
|
"Do you want to delete them locally?<br>UIDs: %2</p></qt>" )
|
|
.arg( folder()->prettyURL() ).arg( uids.join(",") ) ) == KMessageBox::Yes )
|
|
#endif
|
|
removeMsg( msgsForDeletion );
|
|
}
|
|
|
|
if ( mUserRightsState == KMail::ACLJobs::Ok && !( mUserRights & KMail::ACLJobs::Delete ) )
|
|
return false;
|
|
|
|
/* Delete messages from the server that we dont have anymore */
|
|
if( !uidsForDeletionOnServer.isEmpty() ) {
|
|
newState( mProgress, i18n("Deleting removed messages from server"));
|
|
TQStringList sets = KMFolderImap::makeSets( uidsForDeletionOnServer, true );
|
|
uidsForDeletionOnServer.clear();
|
|
kdDebug(5006) << "Deleting " << sets.count() << " sets of messages from server folder " << imapPath() << endl;
|
|
CachedImapJob *job = new CachedImapJob( sets, CachedImapJob::tDeleteMessage, this );
|
|
connect( job, TQT_SIGNAL( result(KMail::FolderJob *) ),
|
|
this, TQT_SLOT( slotDeleteMessagesResult(KMail::FolderJob *) ) );
|
|
job->start();
|
|
return true;
|
|
} else {
|
|
|
|
// Nothing to delete on the server, make sure the map is clear again.
|
|
// Normally this wouldn't be necessary, but there can be stale maps because of
|
|
// https://issues.kolab.org/issue3833.
|
|
mDeletedUIDsSinceLastSync.clear();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::slotDeleteMessagesResult( KMail::FolderJob* job )
|
|
{
|
|
if ( job->error() ) {
|
|
// Skip the EXPUNGE state if deleting didn't work, no need to show two error messages
|
|
mSyncState = SYNC_STATE_GET_MESSAGES;
|
|
} else {
|
|
// deleting on the server went fine, clear the pending deletions cache
|
|
mDeletedUIDsSinceLastSync.clear();
|
|
}
|
|
mProgress += 10;
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void KMFolderCachedImap::checkUidValidity() {
|
|
// IMAP root folders don't seem to have a UID validity setting.
|
|
// Also, don't try the uid validity on new folders
|
|
if( imapPath().isEmpty() || imapPath() == "/" )
|
|
// Just proceed
|
|
serverSyncInternal();
|
|
else {
|
|
newState( mProgress, i18n("Checking folder validity"));
|
|
CachedImapJob *job = new CachedImapJob( FolderJob::tCheckUidValidity, this );
|
|
connect( job, TQT_SIGNAL(permanentFlags(int)), TQT_SLOT(slotPermanentFlags(int)) );
|
|
connect( job, TQT_SIGNAL( result( KMail::FolderJob* ) ),
|
|
this, TQT_SLOT( slotCheckUidValidityResult( KMail::FolderJob* ) ) );
|
|
job->start();
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::slotCheckUidValidityResult( KMail::FolderJob* job )
|
|
{
|
|
if ( job->error() ) { // there was an error and the user chose "continue"
|
|
// We can't continue doing anything in the same folder though, it would delete all mails.
|
|
// But we can continue to subfolders if any. Well we can also try annotation/acl stuff...
|
|
mSyncState = SYNC_STATE_HANDLE_INBOX;
|
|
}
|
|
mProgress += 5;
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void KMFolderCachedImap::slotPermanentFlags(int flags)
|
|
{
|
|
mPermanentFlags = flags;
|
|
}
|
|
|
|
/* This will only list the messages in a folder.
|
|
No directory listing done*/
|
|
void KMFolderCachedImap::listMessages() {
|
|
bool groupwareOnly = GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount()
|
|
&& GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id()
|
|
&& folder()->isSystemFolder()
|
|
&& mImapPath == "/INBOX/";
|
|
// Don't list messages on the root folder, and skip the inbox, if this is
|
|
// the inbox of a groupware-only dimap account
|
|
if( imapPath() == "/" || groupwareOnly ) {
|
|
serverSyncInternal();
|
|
return;
|
|
}
|
|
|
|
if( !mAccount->slave() ) { // sync aborted
|
|
resetSyncState();
|
|
emit folderComplete( this, false );
|
|
return;
|
|
}
|
|
uidsOnServer.clear();
|
|
uidsOnServer.resize( count() * 2 );
|
|
uidsForDeletionOnServer.clear();
|
|
mMsgsForDownload.clear();
|
|
mUidsForDownload.clear();
|
|
// listing is only considered successful if saw a syntactically correct imapdigest
|
|
mFoundAnIMAPDigest = false;
|
|
|
|
CachedImapJob* job = new CachedImapJob( FolderJob::tListMessages, this );
|
|
connect( job, TQT_SIGNAL( result(KMail::FolderJob *) ),
|
|
this, TQT_SLOT( slotGetLastMessagesResult(KMail::FolderJob *) ) );
|
|
job->start();
|
|
}
|
|
|
|
void KMFolderCachedImap::slotGetLastMessagesResult(KMail::FolderJob *job)
|
|
{
|
|
getMessagesResult(job, true);
|
|
}
|
|
|
|
// Connected to the listMessages job in CachedImapJob
|
|
void KMFolderCachedImap::slotGetMessagesData(KIO::Job * job, const TQByteArray & data)
|
|
{
|
|
KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
|
|
if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
|
|
kdDebug(5006) << "could not find job!?!?!" << endl;
|
|
// be sure to reset the sync state, if the listing was partial we would
|
|
// otherwise delete not-listed mail locally, and on the next sync on the server
|
|
// as well
|
|
mSyncState = SYNC_STATE_HANDLE_INBOX;
|
|
serverSyncInternal(); /* HACK^W Fix: we should at least try to keep going */
|
|
return;
|
|
}
|
|
(*it).cdata += TQCString(data, data.size() + 1);
|
|
int pos = (*it).cdata.find("\r\n--IMAPDIGEST");
|
|
if (pos > 0) {
|
|
int a = (*it).cdata.find("\r\nX-uidValidity:");
|
|
if (a != -1) {
|
|
int b = (*it).cdata.find("\r\n", a + 17);
|
|
setUidValidity((*it).cdata.mid(a + 17, b - a - 17));
|
|
}
|
|
a = (*it).cdata.find("\r\nX-Access:");
|
|
// Only trust X-Access (i.e. the imap select info) if we don't know mUserRights.
|
|
// The latter is more accurate (checked on every sync) whereas X-Access is only
|
|
// updated when selecting the folder again, which might not happen if using
|
|
// RMB / Check Mail in this folder. We don't need two (potentially conflicting)
|
|
// sources for the readonly setting, in any case.
|
|
if (a != -1 && mUserRightsState != KMail::ACLJobs::Ok ) {
|
|
int b = (*it).cdata.find("\r\n", a + 12);
|
|
const TQString access = (*it).cdata.mid(a + 12, b - a - 12);
|
|
setReadOnly( access == "Read only" );
|
|
}
|
|
(*it).cdata.remove(0, pos);
|
|
mFoundAnIMAPDigest = true;
|
|
}
|
|
pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1);
|
|
// Start with something largish when rebuilding the cache
|
|
if ( uidsOnServer.size() == 0 )
|
|
uidsOnServer.resize( KMail::nextPrime( 2000 ) );
|
|
const int v = 42;
|
|
while (pos >= 0) {
|
|
/*
|
|
KMMessage msg;
|
|
msg.fromString((*it).cdata.mid(16, pos - 16));
|
|
const int flags = msg.headerField("X-Flags").toInt();
|
|
const ulong size = msg.headerField("X-Length").toULong();
|
|
const ulong uid = msg.UID();
|
|
*/
|
|
// The below is optimized for speed, not prettiness. The commented out chunk
|
|
// above was the solution copied from kmfolderimap, and it's 15-20% slower.
|
|
const TQCString& entry( (*it).cdata );
|
|
const int indexOfUID = entry.find("X-UID", 16);
|
|
const int startOfUIDValue = indexOfUID + 7;
|
|
const int indexOfLength = entry.find("X-Length", startOfUIDValue ); // we know length comes after UID
|
|
const int startOfLengthValue = indexOfLength + 10;
|
|
const int indexOfFlags = entry.find("X-Flags", startOfLengthValue ); // we know flags comes last
|
|
const int startOfFlagsValue = indexOfFlags + 9;
|
|
|
|
const int flags = entry.mid( startOfFlagsValue, entry.find( '\r', startOfFlagsValue ) - startOfFlagsValue ).toInt();
|
|
const ulong size = entry.mid( startOfLengthValue, entry.find( '\r', startOfLengthValue ) - startOfLengthValue ).toULong();
|
|
const ulong uid = entry.mid( startOfUIDValue, entry.find( '\r', startOfUIDValue ) - startOfUIDValue ).toULong();
|
|
|
|
const bool deleted = ( flags & 8 );
|
|
if ( !deleted ) {
|
|
if( uid != 0 ) {
|
|
if ( uidsOnServer.count() == uidsOnServer.size() ) {
|
|
uidsOnServer.resize( KMail::nextPrime( uidsOnServer.size() * 2 ) );
|
|
//kdDebug( 5006 ) << "Resizing to: " << uidsOnServer.size() << endl;
|
|
}
|
|
uidsOnServer.insert( uid, &v );
|
|
}
|
|
bool redownload = false;
|
|
if ( uid <= lastUid() ) {
|
|
/*
|
|
* If this message UID is not present locally, then it must
|
|
* have been deleted by the user, so we delete it on the
|
|
* server also. If we don't have delete permissions on the server,
|
|
* re-download the message, it must have vanished by some error, or
|
|
* while we still thought we were allowed to delete (ACL change).
|
|
*
|
|
* This relies heavily on lastUid() being correct at all times.
|
|
*/
|
|
// kdDebug(5006) << "KMFolderCachedImap::slotGetMessagesData() : folder "<<label()<<" already has msg="<<msg->headerField("Subject") << ", UID="<<uid << ", lastUid = " << mLastUid << endl;
|
|
KMMsgBase *existingMessage = findByUID(uid);
|
|
if( !existingMessage ) {
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug(5006) << "Looking at uid " << uid << " high water is: " << lastUid() << " we should delete it" << endl;
|
|
#endif
|
|
// double check we deleted it since the last sync
|
|
if ( mDeletedUIDsSinceLastSync.contains(uid) ) {
|
|
if ( mUserRightsState != KMail::ACLJobs::Ok || ( mUserRights & KMail::ACLJobs::Delete ) ) {
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug(5006) << "message with uid " << uid << " is gone from local cache. Must be deleted on server!!!" << endl;
|
|
#endif
|
|
uidsForDeletionOnServer << uid;
|
|
} else {
|
|
redownload = true;
|
|
}
|
|
} else {
|
|
kdDebug(5006) << "WARNING: ####### " << endl;
|
|
kdDebug(5006) << "Message locally missing but not deleted in folder: " << folder()->prettyURL() << endl;
|
|
kdDebug(5006) << "The missing UID: " << uid << ". It will be redownloaded " << endl;
|
|
redownload = true;
|
|
}
|
|
|
|
} else {
|
|
// if this is a read only folder, ignore status updates from the server
|
|
// since we can't write our status back our local version is what has to
|
|
// be considered correct.
|
|
if ( !mReadOnly || !GlobalSettings::allowLocalFlags() ) {
|
|
/* The message is OK, update flags */
|
|
KMFolderImap::flagsToStatus( existingMessage, flags, false, mReadOnly ? INT_MAX : mPermanentFlags );
|
|
} else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) {
|
|
KMFolderImap::seenFlagToStatus( existingMessage, flags );
|
|
}
|
|
}
|
|
// kdDebug(5006) << "message with uid " << uid << " found in the local cache. " << endl;
|
|
}
|
|
if ( uid > lastUid() || redownload ) {
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug(5006) << "Looking at uid " << uid << " high water is: " << lastUid() << " we should download it" << endl;
|
|
#endif
|
|
// The message is new since the last sync, but we might have just uploaded it, in which case
|
|
// the uid map already contains it.
|
|
if ( !uidMap.contains( uid ) ) {
|
|
mMsgsForDownload << KMail::CachedImapJob::MsgForDownload(uid, flags, size);
|
|
if( imapPath() == "/INBOX/" )
|
|
mUidsForDownload << uid;
|
|
}
|
|
// Remember the highest uid and once the download is completed, update mLastUid
|
|
if ( uid > mTentativeHighestUid ) {
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug(5006) << "Setting the tentative highest UID to: " << uid << endl;
|
|
#endif
|
|
mTentativeHighestUid = uid;
|
|
}
|
|
}
|
|
}
|
|
(*it).cdata.remove(0, pos);
|
|
(*it).done++;
|
|
pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1);
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet )
|
|
{
|
|
mProgress += 10;
|
|
if ( !job->error() && !mFoundAnIMAPDigest ) {
|
|
kdWarning(5006) << "######## Folderlisting did not complete, but there was no error! "
|
|
"Aborting sync of folder: " << folder()->prettyURL() << endl;
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kmkernel->emergencyExit( i18n("Folder listing failed in interesting ways." ) );
|
|
#endif
|
|
}
|
|
if( job->error() ) { // error listing messages but the user chose to continue
|
|
mContentState = imapNoInformation;
|
|
mSyncState = SYNC_STATE_HANDLE_INBOX; // be sure not to continue in this folder
|
|
} else {
|
|
if( lastSet ) { // always true here (this comes from online-imap...)
|
|
mContentState = imapFinished;
|
|
mUIDsOfLocallyChangedStatuses.clear(); // we are up to date again
|
|
mStatusChangedLocally = false;
|
|
}
|
|
}
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void KMFolderCachedImap::slotProgress(unsigned long done, unsigned long total)
|
|
{
|
|
int progressSpan = 100 - 5 - mProgress;
|
|
int additionalProgress = ( total == 0 ) ?
|
|
progressSpan :
|
|
( progressSpan * done ) / total;
|
|
|
|
// Progress info while retrieving new emails
|
|
// (going from mProgress to mProgress+progressSpan)
|
|
newState( mProgress + additionalProgress, TQString() );
|
|
}
|
|
|
|
void KMFolderCachedImap::setAccount(KMAcctCachedImap *aAccount)
|
|
{
|
|
assert( aAccount->isA("KMAcctCachedImap") );
|
|
mAccount = aAccount;
|
|
if( imapPath()=="/" ) aAccount->setFolder( folder() );
|
|
|
|
// Folder was renamed in a previous session, and the user didn't sync yet
|
|
TQString newName = mAccount->renamedFolder( imapPath() );
|
|
if ( !newName.isEmpty() )
|
|
folder()->setLabel( newName );
|
|
|
|
if( !folder() || !folder()->child() || !folder()->child()->count() ) return;
|
|
for( KMFolderNode* node = folder()->child()->first(); node;
|
|
node = folder()->child()->next() )
|
|
if (!node->isDir())
|
|
static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage())->setAccount(aAccount);
|
|
}
|
|
|
|
void KMFolderCachedImap::listNamespaces()
|
|
{
|
|
ImapAccountBase::ListType type = ImapAccountBase::List;
|
|
if ( mAccount->onlySubscribedFolders() )
|
|
type = ImapAccountBase::ListSubscribed;
|
|
|
|
kdDebug(5006) << "listNamespaces " << mNamespacesToList << endl;
|
|
if ( mNamespacesToList.isEmpty() ) {
|
|
mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
|
|
mPersonalNamespacesCheckDone = true;
|
|
|
|
TQStringList ns = mAccount->namespaces()[ImapAccountBase::OtherUsersNS];
|
|
ns += mAccount->namespaces()[ImapAccountBase::SharedNS];
|
|
mNamespacesToCheck = ns.count();
|
|
for ( TQStringList::Iterator it = ns.begin(); it != ns.end(); ++it )
|
|
{
|
|
if ( (*it).isEmpty() ) {
|
|
// ignore empty listings as they have been listed before
|
|
--mNamespacesToCheck;
|
|
continue;
|
|
}
|
|
KMail::ListJob* job = new KMail::ListJob( mAccount, type, this, mAccount->addPathToNamespace( *it ) );
|
|
job->setHonorLocalSubscription( true );
|
|
connect( job, TQT_SIGNAL(receivedFolders(const TQStringList&, const TQStringList&,
|
|
const TQStringList&, const TQStringList&, const ImapAccountBase::jobData&)),
|
|
this, TQT_SLOT(slotCheckNamespace(const TQStringList&, const TQStringList&,
|
|
const TQStringList&, const TQStringList&, const ImapAccountBase::jobData&)));
|
|
job->start();
|
|
}
|
|
if ( mNamespacesToCheck == 0 ) {
|
|
serverSyncInternal();
|
|
}
|
|
return;
|
|
}
|
|
mPersonalNamespacesCheckDone = false;
|
|
|
|
TQString ns = mNamespacesToList.front();
|
|
mNamespacesToList.pop_front();
|
|
|
|
mSyncState = SYNC_STATE_LIST_SUBFOLDERS2;
|
|
newState( mProgress, i18n("Retrieving folders for namespace %1").arg(ns));
|
|
KMail::ListJob* job = new KMail::ListJob( mAccount, type, this,
|
|
mAccount->addPathToNamespace( ns ) );
|
|
job->setNamespace( ns );
|
|
job->setHonorLocalSubscription( true );
|
|
connect( job, TQT_SIGNAL(receivedFolders(const TQStringList&, const TQStringList&,
|
|
const TQStringList&, const TQStringList&, const ImapAccountBase::jobData&)),
|
|
this, TQT_SLOT(slotListResult(const TQStringList&, const TQStringList&,
|
|
const TQStringList&, const TQStringList&, const ImapAccountBase::jobData&)));
|
|
job->start();
|
|
}
|
|
|
|
void KMFolderCachedImap::slotCheckNamespace( const TQStringList& subfolderNames,
|
|
const TQStringList& subfolderPaths,
|
|
const TQStringList& subfolderMimeTypes,
|
|
const TQStringList& subfolderAttributes,
|
|
const ImapAccountBase::jobData& jobData )
|
|
{
|
|
Q_UNUSED( subfolderPaths );
|
|
Q_UNUSED( subfolderMimeTypes );
|
|
Q_UNUSED( subfolderAttributes );
|
|
--mNamespacesToCheck;
|
|
kdDebug(5006) << "slotCheckNamespace " << subfolderNames << ",remain=" <<
|
|
mNamespacesToCheck << endl;
|
|
|
|
// get a correct foldername:
|
|
// strip / and make sure it does not contain the delimiter
|
|
TQString name = jobData.path.mid( 1, jobData.path.length()-2 );
|
|
name.remove( mAccount->delimiterForNamespace( name ) );
|
|
if ( name.isEmpty() ) {
|
|
// should not happen
|
|
kdWarning(5006) << "slotCheckNamespace: ignoring empty folder!" << endl;
|
|
return;
|
|
}
|
|
|
|
folder()->createChildFolder();
|
|
KMFolderNode *node = 0;
|
|
for ( node = folder()->child()->first(); node;
|
|
node = folder()->child()->next())
|
|
{
|
|
if ( !node->isDir() && node->name() == name )
|
|
break;
|
|
}
|
|
if ( !subfolderNames.isEmpty() ) {
|
|
if ( node ) {
|
|
// folder exists so we have nothing to do - it will be listed later
|
|
kdDebug(5006) << "found namespace folder " << name << endl;
|
|
} else
|
|
{
|
|
// create folder
|
|
kdDebug(5006) << "create namespace folder " << name << endl;
|
|
KMFolder* newFolder = folder()->child()->createFolder( name, false,
|
|
KMFolderTypeCachedImap );
|
|
if ( newFolder ) {
|
|
KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>( newFolder->storage() );
|
|
f->setImapPath( mAccount->addPathToNamespace( name ) );
|
|
f->setNoContent( true );
|
|
f->setAccount( mAccount );
|
|
f->close("cachedimap");
|
|
kmkernel->dimapFolderMgr()->contentsChanged();
|
|
}
|
|
}
|
|
} else {
|
|
if ( node ) {
|
|
kdDebug(5006) << "delete namespace folder " << name << endl;
|
|
KMFolder* fld = static_cast<KMFolder*>(node);
|
|
kmkernel->dimapFolderMgr()->remove( fld );
|
|
}
|
|
}
|
|
|
|
if ( mNamespacesToCheck == 0 ) {
|
|
// all namespaces are done so continue with the next step
|
|
serverSyncInternal();
|
|
}
|
|
}
|
|
|
|
// This lists the subfolders on the server
|
|
// and (in slotListResult) takes care of folders that have been removed on the server
|
|
bool KMFolderCachedImap::listDirectory()
|
|
{
|
|
if( !mAccount->slave() ) { // sync aborted
|
|
resetSyncState();
|
|
emit folderComplete( this, false );
|
|
return false;
|
|
}
|
|
mSubfolderState = imapInProgress;
|
|
|
|
// get the folders
|
|
ImapAccountBase::ListType type = ImapAccountBase::List;
|
|
if ( mAccount->onlySubscribedFolders() )
|
|
type = ImapAccountBase::ListSubscribed;
|
|
KMail::ListJob* job = new KMail::ListJob( mAccount, type, this );
|
|
job->setHonorLocalSubscription( true );
|
|
connect( job, TQT_SIGNAL(receivedFolders(const TQStringList&, const TQStringList&,
|
|
const TQStringList&, const TQStringList&, const ImapAccountBase::jobData&)),
|
|
this, TQT_SLOT(slotListResult(const TQStringList&, const TQStringList&,
|
|
const TQStringList&, const TQStringList&, const ImapAccountBase::jobData&)));
|
|
job->start();
|
|
|
|
return true;
|
|
}
|
|
|
|
void KMFolderCachedImap::slotListResult( const TQStringList& folderNames,
|
|
const TQStringList& folderPaths,
|
|
const TQStringList& folderMimeTypes,
|
|
const TQStringList& folderAttributes,
|
|
const ImapAccountBase::jobData& jobData )
|
|
{
|
|
Q_UNUSED( jobData );
|
|
//kdDebug(5006) << label() << ": folderNames=" << folderNames << " folderPaths="
|
|
//<< folderPaths << " mimeTypes=" << folderMimeTypes << endl;
|
|
mSubfolderNames = folderNames;
|
|
mSubfolderPaths = folderPaths;
|
|
mSubfolderMimeTypes = folderMimeTypes;
|
|
mSubfolderState = imapFinished;
|
|
mSubfolderAttributes = folderAttributes;
|
|
//kdDebug(5006) << "##### setting subfolder attributes: " << mSubfolderAttributes << endl;
|
|
|
|
folder()->createChildFolder();
|
|
KMFolderNode *node = folder()->child()->first();
|
|
bool root = ( this == mAccount->rootFolder() );
|
|
|
|
TQPtrList<KMFolder> toRemove;
|
|
bool emptyList = ( root && mSubfolderNames.empty() );
|
|
if ( !emptyList ) {
|
|
while (node) {
|
|
if (!node->isDir() ) {
|
|
KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
|
|
|
|
if ( mSubfolderNames.findIndex(node->name()) == -1 ) {
|
|
TQString name = node->name();
|
|
// as more than one namespace can be listed in the root folder we need to make sure
|
|
// that the folder is within the current namespace
|
|
bool isInNamespace = ( jobData.curNamespace.isEmpty() ||
|
|
jobData.curNamespace == mAccount->namespaceForFolder( f ) );
|
|
// ignore some cases
|
|
bool ignore = root && ( f->imapPath() == "/INBOX/" ||
|
|
mAccount->isNamespaceFolder( name ) || !isInNamespace );
|
|
|
|
// This subfolder isn't present on the server
|
|
if( !f->imapPath().isEmpty() && !ignore ) {
|
|
// The folder has an imap path set, so it has been
|
|
// on the server before. Delete it locally.
|
|
toRemove.append( f->folder() );
|
|
kdDebug(5006) << node->name() << " isn't on the server. It has an imapPath -> delete it locally" << endl;
|
|
}
|
|
} else { // folder both local and on server
|
|
//kdDebug(5006) << node->name() << " is on the server." << endl;
|
|
|
|
/**
|
|
* Store the folder attributes for every subfolder.
|
|
*/
|
|
int index = mSubfolderNames.findIndex( node->name() );
|
|
f->mFolderAttributes = folderAttributes[ index ];
|
|
}
|
|
} else {
|
|
//kdDebug(5006) << "skipping dir node:" << node->name() << endl;
|
|
}
|
|
node = folder()->child()->next();
|
|
}
|
|
}
|
|
|
|
for ( KMFolder* doomed=toRemove.first(); doomed; doomed = toRemove.next() ) {
|
|
rescueUnsyncedMessagesAndDeleteFolder( doomed );
|
|
}
|
|
|
|
mProgress += 5;
|
|
|
|
// just in case there is nothing to rescue
|
|
slotRescueDone( 0 );
|
|
}
|
|
|
|
// This synchronizes the local folders as needed (creation/deletion). No network communication here.
|
|
void KMFolderCachedImap::listDirectory2()
|
|
{
|
|
TQString path = folder()->path();
|
|
kmkernel->dimapFolderMgr()->quiet(true);
|
|
|
|
bool root = ( this == mAccount->rootFolder() );
|
|
if ( root && !mAccount->hasInbox() )
|
|
{
|
|
KMFolderCachedImap *f = 0;
|
|
KMFolderNode *node;
|
|
// create the INBOX
|
|
for (node = folder()->child()->first(); node; node = folder()->child()->next())
|
|
if (!node->isDir() && node->name() == "INBOX") break;
|
|
if (node) {
|
|
f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
|
|
} else {
|
|
KMFolder* newFolder = folder()->child()->createFolder("INBOX", true, KMFolderTypeCachedImap);
|
|
if ( newFolder ) {
|
|
f = static_cast<KMFolderCachedImap*>(newFolder->storage());
|
|
}
|
|
}
|
|
if ( f ) {
|
|
f->setAccount( mAccount );
|
|
f->setImapPath( "/INBOX/" );
|
|
f->folder()->setLabel( i18n("inbox") );
|
|
}
|
|
if (!node) {
|
|
if ( f )
|
|
f->close("cachedimap");
|
|
kmkernel->dimapFolderMgr()->contentsChanged();
|
|
}
|
|
// so we have an INBOX
|
|
mAccount->setHasInbox( true );
|
|
}
|
|
|
|
if ( root && !mSubfolderNames.isEmpty() ) {
|
|
KMFolderCachedImap* parent =
|
|
findParent( mSubfolderPaths.first(), mSubfolderNames.first() );
|
|
if ( parent ) {
|
|
kdDebug(5006) << "KMFolderCachedImap::listDirectory2 - pass listing to "
|
|
<< parent->label() << endl;
|
|
mSubfolderNames.clear();
|
|
}
|
|
}
|
|
|
|
// Find all subfolders present on server but not on disk
|
|
TQValueVector<int> foldersNewOnServer;
|
|
for (uint i = 0; i < mSubfolderNames.count(); i++) {
|
|
|
|
// Find the subdir, if already present
|
|
KMFolderCachedImap *f = 0;
|
|
KMFolderNode *node = 0;
|
|
for (node = folder()->child()->first(); node;
|
|
node = folder()->child()->next())
|
|
if (!node->isDir() && node->name() == mSubfolderNames[i]) break;
|
|
|
|
if (!node) {
|
|
// This folder is not present here
|
|
// Either it's new on the server, or we just deleted it.
|
|
TQString subfolderPath = mSubfolderPaths[i];
|
|
// The code used to look at the uidcache to know if it was "just deleted".
|
|
// But this breaks with noContent folders and with shared folders.
|
|
// So instead we keep a list in the account.
|
|
bool locallyDeleted = mAccount->isDeletedFolder( subfolderPath );
|
|
// That list is saved/restored across sessions, but to avoid any mistake,
|
|
// ask for confirmation if the folder was deleted in a previous session
|
|
// (could be that the folder was deleted & recreated meanwhile from another client...)
|
|
if ( !locallyDeleted && mAccount->isPreviouslyDeletedFolder( subfolderPath ) ) {
|
|
locallyDeleted = KMessageBox::warningYesNo(
|
|
0, i18n( "<qt><p>It seems that the folder <b>%1</b> was deleted. Do you want to delete it from the server?</p></qt>" ).arg( mSubfolderNames[i] ), TQString(), KStdGuiItem::del(), KStdGuiItem::cancel() ) == KMessageBox::Yes;
|
|
}
|
|
|
|
if ( locallyDeleted ) {
|
|
kdDebug(5006) << subfolderPath << " was deleted locally => delete on server." << endl;
|
|
foldersForDeletionOnServer += mAccount->deletedFolderPaths( subfolderPath ); // grab all subsubfolders too
|
|
} else {
|
|
kdDebug(5006) << subfolderPath << " is a new folder on the server => create local cache" << endl;
|
|
foldersNewOnServer.append( i );
|
|
}
|
|
} else { // Folder found locally
|
|
if( static_cast<KMFolder*>(node)->folderType() == KMFolderTypeCachedImap )
|
|
f = dynamic_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
|
|
if( f ) {
|
|
// kdDebug(5006) << "folder("<<f->name()<<")->imapPath()=" << f->imapPath()
|
|
// << "\nSetting imapPath " << mSubfolderPaths[i] << endl;
|
|
// Write folder settings
|
|
f->setAccount(mAccount);
|
|
f->setNoContent(mSubfolderMimeTypes[i] == "inode/directory");
|
|
f->setNoChildren(mSubfolderMimeTypes[i] == "message/digest");
|
|
f->setImapPath(mSubfolderPaths[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* In case we are ignoring non-groupware folders, and this is the groupware
|
|
* main account, find out the contents types of folders that have newly
|
|
* appeared on the server. Otherwise just create them and finish listing.
|
|
* If a folder is already known to be locally unsubscribed, it won't be
|
|
* listed at all, on this level, so these are only folders that we are
|
|
* seeing for the first time. */
|
|
|
|
/* Note: We ask the globalsettings, and not the current state of the
|
|
* kmkernel->iCalIface().isEnabled(), since that is false during the
|
|
* very first sync, where we already want to filter. */
|
|
if ( GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount()
|
|
&& GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id()
|
|
&& mAccount->hasAnnotationSupport()
|
|
&& GlobalSettings::self()->theIMAPResourceEnabled()
|
|
&& !foldersNewOnServer.isEmpty() ) {
|
|
|
|
TQStringList paths;
|
|
for ( uint i = 0; i < foldersNewOnServer.count(); ++i )
|
|
paths << mSubfolderPaths[ foldersNewOnServer[i] ];
|
|
|
|
AnnotationJobs::MultiUrlGetAnnotationJob* job =
|
|
AnnotationJobs::multiUrlGetAnnotation( mAccount->slave(), mAccount->getUrl(), paths, KOLAB_FOLDERTYPE );
|
|
ImapAccountBase::jobData jd( TQString(), folder() );
|
|
jd.cancellable = true;
|
|
mAccount->insertJob(job, jd);
|
|
connect( job, TQT_SIGNAL(result(KIO::Job *)),
|
|
TQT_SLOT(slotMultiUrlGetAnnotationResult(KIO::Job *)) );
|
|
|
|
} else {
|
|
createFoldersNewOnServerAndFinishListing( foldersNewOnServer );
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::createFoldersNewOnServerAndFinishListing( const TQValueVector<int> foldersNewOnServer )
|
|
{
|
|
for ( uint i = 0; i < foldersNewOnServer.count(); ++i ) {
|
|
int idx = foldersNewOnServer[i];
|
|
KMFolder* newFolder = folder()->child()->createFolder( mSubfolderNames[idx], false, KMFolderTypeCachedImap);
|
|
if (newFolder) {
|
|
KMFolderCachedImap *f = dynamic_cast<KMFolderCachedImap*>(newFolder->storage());
|
|
kdDebug(5006) << " ####### Locally creating folder " << mSubfolderNames[idx] <<endl;
|
|
f->close("cachedimap");
|
|
f->setAccount(mAccount);
|
|
f->mAnnotationFolderType = "FROMSERVER";
|
|
f->setNoContent(mSubfolderMimeTypes[idx] == "inode/directory");
|
|
f->setNoChildren(mSubfolderMimeTypes[idx] == "message/digest");
|
|
f->setImapPath(mSubfolderPaths[idx]);
|
|
f->mFolderAttributes = mSubfolderAttributes[idx];
|
|
mNewlyCreatedSubfolders.append( TQGuardedPtr<KMFolderCachedImap>( f ) );
|
|
kdDebug(5006) << " ####### Attributes: " << f->mFolderAttributes <<endl;
|
|
//kdDebug(5006) << subfolderPath << ": mAnnotationFolderType set to FROMSERVER" << endl;
|
|
kmkernel->dimapFolderMgr()->contentsChanged();
|
|
} else {
|
|
kdDebug(5006) << "can't create folder " << mSubfolderNames[idx] <<endl;
|
|
}
|
|
}
|
|
|
|
kmkernel->dimapFolderMgr()->quiet(false);
|
|
emit listComplete(this);
|
|
if ( !mPersonalNamespacesCheckDone ) {
|
|
// we're not done with the namespaces
|
|
mSyncState = SYNC_STATE_LIST_NAMESPACES;
|
|
}
|
|
serverSyncInternal();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
KMFolderCachedImap* KMFolderCachedImap::findParent( const TQString& path,
|
|
const TQString& name )
|
|
{
|
|
TQString parent = path.left( path.length() - name.length() - 2 );
|
|
if ( parent.length() > 1 )
|
|
{
|
|
// extract name of the parent
|
|
parent = parent.right( parent.length() - 1 );
|
|
if ( parent != label() )
|
|
{
|
|
KMFolderNode *node = folder()->child()->first();
|
|
// look for a better parent
|
|
while ( node )
|
|
{
|
|
if ( node->name() == parent )
|
|
{
|
|
KMFolder* fld = static_cast<KMFolder*>(node);
|
|
KMFolderCachedImap* imapFld =
|
|
static_cast<KMFolderCachedImap*>( fld->storage() );
|
|
return imapFld;
|
|
}
|
|
node = folder()->child()->next();
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void KMFolderCachedImap::slotSubFolderComplete(KMFolderCachedImap* sub, bool success)
|
|
{
|
|
Q_UNUSED(sub);
|
|
//kdDebug(5006) << label() << " slotSubFolderComplete: " << sub->label() << endl;
|
|
if ( success ) {
|
|
serverSyncInternal();
|
|
}
|
|
else
|
|
{
|
|
// success == false means the sync was aborted.
|
|
if ( mCurrentSubfolder ) {
|
|
Q_ASSERT( sub == mCurrentSubfolder );
|
|
disconnectSubFolderSignals();
|
|
}
|
|
|
|
// Next step would be to check quota limits and then to close the folder, but don't bother with
|
|
// both and close the folder right here, since we aborted.
|
|
mSubfoldersForSync.clear();
|
|
mSyncState = SYNC_STATE_INITIAL;
|
|
close("cachedimap");
|
|
emit syncStateChanged();
|
|
emit folderComplete( this, false );
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::slotSubFolderCloseToQuotaChanged()
|
|
{
|
|
if ( !mQuotaOnly ) {
|
|
mSomeSubFolderCloseToQuotaChanged = true;
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::slotSimpleData(KIO::Job * job, const TQByteArray & data)
|
|
{
|
|
KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
|
|
if (it == mAccount->jobsEnd()) return;
|
|
TQBuffer buff((*it).data);
|
|
buff.open(IO_WriteOnly | IO_Append);
|
|
buff.writeBlock(data.data(), data.size());
|
|
buff.close();
|
|
}
|
|
|
|
FolderJob*
|
|
KMFolderCachedImap::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder,
|
|
TQString, const AttachmentStrategy* ) const
|
|
{
|
|
TQPtrList<KMMessage> msgList;
|
|
msgList.append( msg );
|
|
CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast<KMFolderCachedImap*>( folder->storage() ):0 );
|
|
job->setParentFolder( this );
|
|
return job;
|
|
}
|
|
|
|
FolderJob*
|
|
KMFolderCachedImap::doCreateJob( TQPtrList<KMMessage>& msgList, const TQString& sets,
|
|
FolderJob::JobType jt, KMFolder *folder ) const
|
|
{
|
|
//FIXME: how to handle sets here?
|
|
Q_UNUSED( sets );
|
|
CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast<KMFolderCachedImap*>( folder->storage() ):0 );
|
|
job->setParentFolder( this );
|
|
return job;
|
|
}
|
|
|
|
void
|
|
KMFolderCachedImap::setUserRights( unsigned int userRights, KMail::ACLJobs::ACLFetchState state )
|
|
{
|
|
mUserRights = userRights;
|
|
mUserRightsState = state;
|
|
writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
|
|
}
|
|
|
|
void
|
|
KMFolderCachedImap::slotReceivedUserRights( KMFolder* folder )
|
|
{
|
|
if ( folder->storage() == this ) {
|
|
disconnect( mAccount, TQT_SIGNAL( receivedUserRights( KMFolder* ) ),
|
|
this, TQT_SLOT( slotReceivedUserRights( KMFolder* ) ) );
|
|
if ( mUserRightsState == KMail::ACLJobs::Ok ) {
|
|
setReadOnly( ( mUserRights & KMail::ACLJobs::Insert ) == 0 );
|
|
}
|
|
mProgress += 5;
|
|
serverSyncInternal();
|
|
}
|
|
}
|
|
|
|
void
|
|
KMFolderCachedImap::setReadOnly( bool readOnly )
|
|
{
|
|
if ( readOnly != mReadOnly ) {
|
|
mReadOnly = readOnly;
|
|
emit readOnlyChanged( folder() );
|
|
}
|
|
}
|
|
|
|
void
|
|
KMFolderCachedImap::slotReceivedACL( KMFolder* folder, KIO::Job* job, const KMail::ACLList& aclList )
|
|
{
|
|
if ( folder->storage() == this ) {
|
|
disconnect( mAccount, TQT_SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
|
|
this, TQT_SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
|
|
mACLListState = job->error() ? KMail::ACLJobs::FetchFailed : KMail::ACLJobs::Ok;
|
|
mACLList = aclList;
|
|
serverSyncInternal();
|
|
}
|
|
}
|
|
|
|
void
|
|
KMFolderCachedImap::slotStorageQuotaResult( const QuotaInfo& info )
|
|
{
|
|
setQuotaInfo( info );
|
|
}
|
|
|
|
void KMFolderCachedImap::setQuotaInfo( const QuotaInfo & info )
|
|
{
|
|
if ( info != mQuotaInfo ) {
|
|
const bool wasCloseToQuota = isCloseToQuota();
|
|
mQuotaInfo = info;
|
|
writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
|
|
if ( wasCloseToQuota != isCloseToQuota() ) {
|
|
emit closeToQuotaChanged();
|
|
}
|
|
emit folderSizeChanged();
|
|
}
|
|
}
|
|
|
|
void
|
|
KMFolderCachedImap::setACLList( const ACLList& arr )
|
|
{
|
|
mACLList = arr;
|
|
mACLListState = KMail::ACLJobs::Ok;
|
|
}
|
|
|
|
void
|
|
KMFolderCachedImap::slotMultiSetACLResult(KIO::Job *job)
|
|
{
|
|
KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
|
|
if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
|
|
if ( (*it).parent != folder() ) return; // Shouldn't happen
|
|
|
|
if ( job->error() )
|
|
// Display error but don't abort the sync just for this
|
|
// PENDING(dfaure) reconsider using handleJobError now that it offers continue/cancel
|
|
job->showErrorDialog();
|
|
else
|
|
kmkernel->iCalIface().addFolderChange( folder(), KMailICalIfaceImpl::ACL );
|
|
|
|
if (mAccount->slave()) mAccount->removeJob(job);
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void
|
|
KMFolderCachedImap::slotACLChanged( const TQString& userId, int permissions )
|
|
{
|
|
// The job indicates success in changing the permissions for this user
|
|
// -> we note that it's been done.
|
|
for( ACLList::Iterator it = mACLList.begin(); it != mACLList.end(); ++it ) {
|
|
if ( (*it).userId == userId && (*it).permissions == permissions ) {
|
|
if ( permissions == -1 ) // deleted
|
|
mACLList.erase( it );
|
|
else // added/modified
|
|
(*it).changed = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// called by KMAcctCachedImap::killAllJobs
|
|
void KMFolderCachedImap::resetSyncState()
|
|
{
|
|
if ( mSyncState == SYNC_STATE_INITIAL ) return;
|
|
mSubfoldersForSync.clear();
|
|
mNewlyCreatedSubfolders.clear();
|
|
mSyncState = SYNC_STATE_INITIAL;
|
|
close("cachedimap");
|
|
// Don't use newState here, it would revert to mProgress (which is < current value when listing messages)
|
|
KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem();
|
|
TQString str = i18n("Aborted");
|
|
if (progressItem)
|
|
progressItem->seStatus( str );
|
|
emit statusMsg( str );
|
|
emit syncStateChanged();
|
|
}
|
|
|
|
void KMFolderCachedImap::slotIncreaseProgress()
|
|
{
|
|
mProgress += 5;
|
|
}
|
|
|
|
void KMFolderCachedImap::newState( int progress, const TQString& syncStatus )
|
|
{
|
|
//kdDebug() << k_funcinfo << folder() << " " << mProgress << " " << syncStatus << endl;
|
|
KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem();
|
|
if( progressItem )
|
|
progressItem->setCompletedItems( progress );
|
|
if ( !syncStatus.isEmpty() ) {
|
|
TQString str;
|
|
// For a subfolder, show the label. But for the main folder, it's already shown.
|
|
if ( mAccount->imapFolder() == this )
|
|
str = syncStatus;
|
|
else
|
|
str = TQString( "%1: %2" ).arg( label() ).arg( syncStatus );
|
|
if( progressItem )
|
|
progressItem->seStatus( str );
|
|
emit statusMsg( str );
|
|
}
|
|
if( progressItem )
|
|
progressItem->updateProgress();
|
|
}
|
|
|
|
void KMFolderCachedImap::setSubfolderState( imapState state )
|
|
{
|
|
mSubfolderState = state;
|
|
if ( state == imapNoInformation && folder()->child() )
|
|
{
|
|
// pass through to childs
|
|
KMFolderNode* node;
|
|
TQPtrListIterator<KMFolderNode> it( *folder()->child() );
|
|
for ( ; (node = it.current()); )
|
|
{
|
|
++it;
|
|
if (node->isDir()) continue;
|
|
KMFolder *folder = static_cast<KMFolder*>(node);
|
|
static_cast<KMFolderCachedImap*>(folder->storage())->setSubfolderState( state );
|
|
}
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::setImapPath(const TQString &path)
|
|
{
|
|
mImapPath = path;
|
|
}
|
|
|
|
static bool isFolderTypeKnownToUs( const TQString &type )
|
|
{
|
|
for ( uint i = 0 ; i <= ContentsTypeLast; ++i ) {
|
|
FolderContentsType contentsType = static_cast<KMail::FolderContentsType>( i );
|
|
if ( type == KMailICalIfaceImpl::annotationForContentsType( contentsType ) )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// mAnnotationFolderType is the annotation as known to the server (and stored in kmailrc)
|
|
// It is updated from the folder contents type and whether it's a standard resource folder.
|
|
// This happens during the syncing phase and during initFolder for a new folder.
|
|
// Don't do it earlier, e.g. from setContentsType:
|
|
// on startup, it's too early there to know if this is a standard resource folder.
|
|
void KMFolderCachedImap::updateAnnotationFolderType()
|
|
{
|
|
TQString oldType = mAnnotationFolderType;
|
|
TQString oldSubType;
|
|
int dot = oldType.find( '.' );
|
|
if ( dot != -1 ) {
|
|
oldType.truncate( dot );
|
|
oldSubType = mAnnotationFolderType.mid( dot + 1 );
|
|
}
|
|
|
|
TQString newType, newSubType;
|
|
// We want to store an annotation on the folder only if using the kolab storage.
|
|
if ( kmkernel->iCalIface().storageFormat( folder() ) == KMailICalIfaceImpl::StorageXML ) {
|
|
newType = KMailICalIfaceImpl::annotationForContentsType( mContentsType );
|
|
if ( kmkernel->iCalIface().isStandardResourceFolder( folder() ) )
|
|
newSubType = "default";
|
|
else if ( oldSubType != "default" )
|
|
newSubType = oldSubType; // preserve unknown subtypes, like drafts etc.
|
|
}
|
|
|
|
// We do not want to overwrite custom folder types (which we treat as mail folders).
|
|
// So only overwrite custom folder types if the user changed the folder type himself to something
|
|
// other than mail.
|
|
const bool changingTypeAllowed = isFolderTypeKnownToUs( oldType ) ||
|
|
( mContentsType != ContentsTypeMail );
|
|
|
|
//kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: " << newType << " " << newSubType << endl;
|
|
if ( ( newType != oldType || newSubType != oldSubType ) && changingTypeAllowed ) {
|
|
mAnnotationFolderType = newType + ( newSubType.isEmpty() ? TQString() : "."+newSubType );
|
|
mAnnotationFolderTypeChanged = true; // force a "set annotation" on next sync
|
|
kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: '" << mAnnotationFolderType << "', was (" << oldType << " " << oldSubType << ") => mAnnotationFolderTypeChanged set to TRUE" << endl;
|
|
}
|
|
// Ensure that further readConfig()s don't lose mAnnotationFolderType
|
|
writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
|
|
}
|
|
|
|
void KMFolderCachedImap::setIncidencesFor( IncidencesFor incfor )
|
|
{
|
|
if ( mIncidencesFor != incfor ) {
|
|
mIncidencesFor = incfor;
|
|
mIncidencesForChanged = true;
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::setSharedSeenFlags(bool b)
|
|
{
|
|
if ( mSharedSeenFlags != b ) {
|
|
mSharedSeenFlags = b;
|
|
mSharedSeenFlagsChanged = true;
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::slotAnnotationResult(const TQString& entry, const TQString& value, bool found)
|
|
{
|
|
if ( entry == KOLAB_FOLDERTYPE ) {
|
|
// There are four cases.
|
|
// 1) no content-type on server -> set it
|
|
// 2) different content-type on server, locally changed -> set it (we don't even come here)
|
|
// 3) different (known) content-type on server, no local change -> get it
|
|
// 4) different unknown content-type on server, probably some older version -> set it
|
|
if ( found ) {
|
|
TQString type = value;
|
|
TQString subtype;
|
|
int dot = value.find( '.' );
|
|
if ( dot != -1 ) {
|
|
type.truncate( dot );
|
|
subtype = value.mid( dot + 1 );
|
|
}
|
|
bool foundKnownType = false;
|
|
for ( uint i = 0 ; i <= ContentsTypeLast; ++i ) {
|
|
FolderContentsType contentsType = static_cast<KMail::FolderContentsType>( i );
|
|
if ( type == KMailICalIfaceImpl::annotationForContentsType( contentsType ) ) {
|
|
// Case 3: known content-type on server, get it
|
|
//kdDebug(5006) << mImapPath << ": slotGetAnnotationResult: found known type of annotation" << endl;
|
|
if ( contentsType != ContentsTypeMail )
|
|
kmkernel->iCalIface().setStorageFormat( folder(), KMailICalIfaceImpl::StorageXML );
|
|
mAnnotationFolderType = value;
|
|
if ( folder()->parent()->owner()->idString() != GlobalSettings::self()->theIMAPResourceFolderParent()
|
|
&& GlobalSettings::self()->theIMAPResourceEnabled()
|
|
&& subtype == "default" ) {
|
|
// Truncate subtype if this folder can't be a default resource folder for us,
|
|
// although it apparently is for someone else.
|
|
mAnnotationFolderType = type;
|
|
kdDebug(5006) << mImapPath << ": slotGetAnnotationResult: parent folder is " << folder()->parent()->owner()->idString() << " => truncating annotation to " << value << endl;
|
|
}
|
|
setContentsType( contentsType );
|
|
mAnnotationFolderTypeChanged = false; // we changed it, not the user
|
|
foundKnownType = true;
|
|
|
|
// Users don't read events/contacts/etc. in kmail, so mark them all as read.
|
|
// This is done in cachedimapjob when getting new messages, but do it here too,
|
|
// for the initial set of messages when we didn't know this was a resource folder yet,
|
|
// for old folders, etc.
|
|
if ( contentsType != ContentsTypeMail )
|
|
markUnreadAsRead();
|
|
|
|
break;
|
|
}
|
|
}
|
|
if ( !foundKnownType ) {
|
|
//kdDebug(5006) << "slotGetAnnotationResult: no known type of annotation found, leaving it untouched" << endl;
|
|
|
|
// Case 4: Server has strange content-type. We must not overwrite it, see https://issues.kolab.org/issue2069.
|
|
// Treat the content-type as mail until we change it ourselves.
|
|
mAnnotationFolderTypeChanged = false;
|
|
mAnnotationFolderType = value;
|
|
setContentsType( ContentsTypeMail );
|
|
}
|
|
|
|
// Ensure that further readConfig()s don't lose mAnnotationFolderType
|
|
writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
|
|
// TODO handle subtype (inbox, drafts, sentitems, junkemail)
|
|
}
|
|
else if ( !mReadOnly ) {
|
|
// Case 1: server doesn't have content-type, set it
|
|
//kdDebug(5006) << "slotGetAnnotationResult: no annotation found, will need to set it" << endl;
|
|
mAnnotationFolderTypeChanged = true;
|
|
}
|
|
} else if ( entry == KOLAB_INCIDENCESFOR ) {
|
|
if ( found ) {
|
|
mIncidencesFor = incidencesForFromString( value );
|
|
Q_ASSERT( mIncidencesForChanged == false );
|
|
}
|
|
} else if ( entry == KOLAB_SHAREDSEEN ) {
|
|
if ( found ) {
|
|
mSharedSeenFlags = value == "true";
|
|
}
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::slotGetAnnotationResult( KIO::Job* job )
|
|
{
|
|
KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
|
|
Q_ASSERT( it != mAccount->jobsEnd() );
|
|
if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
|
|
Q_ASSERT( (*it).parent == folder() );
|
|
if ( (*it).parent != folder() ) return; // Shouldn't happen
|
|
|
|
AnnotationJobs::GetAnnotationJob* annjob = static_cast<AnnotationJobs::GetAnnotationJob *>( job );
|
|
if ( annjob->error() ) {
|
|
if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
|
|
// that's when the imap server doesn't support annotations
|
|
if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML
|
|
&& (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() )
|
|
KMessageBox::error( 0, i18n( "The IMAP server %1 does not have support for IMAP annotations. The XML storage cannot be used on this server; please re-configure KMail differently." ).arg( mAccount->host() ) );
|
|
mAccount->setHasNoAnnotationSupport();
|
|
}
|
|
else
|
|
kdWarning(5006) << "slotGetAnnotationResult: " << job->errorString() << endl;
|
|
}
|
|
|
|
if (mAccount->slave()) mAccount->removeJob(job);
|
|
mProgress += 2;
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void KMFolderCachedImap::slotMultiUrlGetAnnotationResult( KIO::Job* job )
|
|
{
|
|
KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
|
|
Q_ASSERT( it != mAccount->jobsEnd() );
|
|
if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
|
|
Q_ASSERT( (*it).parent == folder() );
|
|
if ( (*it).parent != folder() ) return; // Shouldn't happen
|
|
|
|
TQValueVector<int> folders;
|
|
AnnotationJobs::MultiUrlGetAnnotationJob* annjob
|
|
= static_cast<AnnotationJobs::MultiUrlGetAnnotationJob *>( job );
|
|
if ( annjob->error() ) {
|
|
if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
|
|
// that's when the imap server doesn't support annotations
|
|
if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML
|
|
&& (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() )
|
|
KMessageBox::error( 0, i18n( "The IMAP server %1 doesn't have support for imap annotations. The XML storage cannot be used on this server, please re-configure KMail differently" ).arg( mAccount->host() ) );
|
|
mAccount->setHasNoAnnotationSupport();
|
|
}
|
|
else
|
|
kdWarning(5006) << "slotGetMultiUrlAnnotationResult: " << job->errorString() << endl;
|
|
} else {
|
|
// we got the annotation allright, let's filter out the ones with the wrong type
|
|
TQMap<TQString, TQString> annotations = annjob->annotations();
|
|
TQMap<TQString, TQString>::Iterator it = annotations.begin();
|
|
for ( ; it != annotations.end(); ++it ) {
|
|
const TQString folderPath = it.key();
|
|
const TQString annotation = it.data();
|
|
kdDebug(5006) << k_funcinfo << "Folder: " << folderPath << " has type: " << annotation << endl;
|
|
// we're only interested in the main type
|
|
TQString type(annotation);
|
|
int dot = annotation.find( '.' );
|
|
if ( dot != -1 ) type.truncate( dot );
|
|
type = type.simplifyWhiteSpace();
|
|
|
|
const int idx = mSubfolderPaths.findIndex( folderPath );
|
|
const bool isNoContent = mSubfolderMimeTypes[idx] == "inode/directory";
|
|
if ( ( isNoContent && type.isEmpty() )
|
|
|| ( !type.isEmpty() && type != KMailICalIfaceImpl::annotationForContentsType( ContentsTypeMail ) ) ) {
|
|
folders.append( idx );
|
|
kdDebug(5006) << k_funcinfo << " subscribing to: " << folderPath << endl;
|
|
} else {
|
|
kdDebug(5006) << k_funcinfo << " automatically unsubscribing from: " << folderPath << endl;
|
|
mAccount->changeLocalSubscription( folderPath, false );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mAccount->slave()) mAccount->removeJob(job);
|
|
createFoldersNewOnServerAndFinishListing( folders );
|
|
}
|
|
|
|
void KMFolderCachedImap::slotQuotaResult( KIO::Job* job )
|
|
{
|
|
KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
|
|
Q_ASSERT( it != mAccount->jobsEnd() );
|
|
if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
|
|
Q_ASSERT( (*it).parent == folder() );
|
|
if ( (*it).parent != folder() ) return; // Shouldn't happen
|
|
|
|
QuotaJobs::GetStorageQuotaJob* quotajob = static_cast<QuotaJobs::GetStorageQuotaJob *>( job );
|
|
QuotaInfo empty;
|
|
if ( quotajob->error() ) {
|
|
if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
|
|
// that's when the imap server doesn't support quota
|
|
mAccount->setHasNoQuotaSupport();
|
|
setQuotaInfo( empty );
|
|
}
|
|
else
|
|
kdWarning(5006) << "slotGetQuotaResult: " << job->errorString() << endl;
|
|
}
|
|
|
|
if (mAccount->slave()) mAccount->removeJob(job);
|
|
mProgress += 2;
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void
|
|
KMFolderCachedImap::slotAnnotationChanged( const TQString& entry, const TQString& attribute, const TQString& value )
|
|
{
|
|
Q_UNUSED( attribute );
|
|
Q_UNUSED( value );
|
|
//kdDebug(5006) << k_funcinfo << entry << " " << attribute << " " << value << endl;
|
|
if ( entry == KOLAB_FOLDERTYPE )
|
|
mAnnotationFolderTypeChanged = false;
|
|
else if ( entry == KOLAB_INCIDENCESFOR ) {
|
|
mIncidencesForChanged = false;
|
|
// The incidences-for changed, we must trigger the freebusy creation.
|
|
// HACK: in theory we would need a new enum value for this.
|
|
kmkernel->iCalIface().addFolderChange( folder(), KMailICalIfaceImpl::ACL );
|
|
} else if ( entry == KOLAB_SHAREDSEEN ) {
|
|
mSharedSeenFlagsChanged = false;
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::slotTestAnnotationResult(KIO::Job *job)
|
|
{
|
|
KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
|
|
Q_ASSERT( it != mAccount->jobsEnd() );
|
|
if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
|
|
Q_ASSERT( (*it).parent == folder() );
|
|
if ( (*it).parent != folder() ) return; // Shouldn't happen
|
|
|
|
mAccount->setAnnotationCheckPassed( true );
|
|
if ( job->error() ) {
|
|
kdDebug(5006) << "Test Annotation was not passed, disabling annotation support" << endl;
|
|
mAccount->setHasNoAnnotationSupport( );
|
|
} else {
|
|
kdDebug(5006) << "Test Annotation was passed OK" << endl;
|
|
}
|
|
if (mAccount->slave()) mAccount->removeJob(job);
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void
|
|
KMFolderCachedImap::slotSetAnnotationResult(KIO::Job *job)
|
|
{
|
|
KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
|
|
if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
|
|
if ( (*it).parent != folder() ) return; // Shouldn't happen
|
|
|
|
bool cont = true;
|
|
if ( job->error() ) {
|
|
// Don't show error if the server doesn't support ANNOTATEMORE and this folder only contains mail
|
|
if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION && contentsType() == ContentsTypeMail ) {
|
|
if (mAccount->slave()) mAccount->removeJob(job);
|
|
} else {
|
|
cont = mAccount->handleJobError( job, i18n( "Error while setting annotation: " ) + '\n' );
|
|
}
|
|
} else {
|
|
if (mAccount->slave()) mAccount->removeJob(job);
|
|
}
|
|
if ( cont )
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void KMFolderCachedImap::slotUpdateLastUid()
|
|
{
|
|
if( mTentativeHighestUid != 0 ) {
|
|
|
|
// Sanity checking:
|
|
// By now all new mails should be downloaded, which means
|
|
// that iteration over the folder should yield only UIDs
|
|
// lower or equal to what we think the highes ist, and the
|
|
// highest one as well. If not, our notion of the highest
|
|
// uid we've seen thus far is wrong, which is dangerous, so
|
|
// don't update the mLastUid, then.
|
|
// Not entirely true though, mails might have been moved out
|
|
// of the folder already by filters, thus giving us a higher tentative
|
|
// uid than we actually observe here.
|
|
bool sane = count() == 0;
|
|
|
|
for (int i=0;i<count(); i++ ) {
|
|
ulong uid = getMsgBase(i)->UID();
|
|
if ( uid > mTentativeHighestUid && uid > lastUid() ) {
|
|
kdWarning(5006) << "DANGER: Either the server listed a wrong highest uid, "
|
|
"or we parsed it wrong. Send email to adam@kde.org, please, and include this log." << endl;
|
|
kdWarning(5006) << "uid: " << uid << " mTentativeHighestUid: " << mTentativeHighestUid << endl;
|
|
assert( false );
|
|
break;
|
|
} else {
|
|
sane = true;
|
|
}
|
|
}
|
|
if (sane) {
|
|
#if MAIL_LOSS_DEBUGGING
|
|
kdDebug(5006) << "Tentative highest UID test was sane, writing out: " << mTentativeHighestUid << endl;
|
|
#endif
|
|
setLastUid( mTentativeHighestUid );
|
|
}
|
|
}
|
|
mTentativeHighestUid = 0;
|
|
}
|
|
|
|
bool KMFolderCachedImap::isMoveable() const
|
|
{
|
|
return ( hasChildren() == HasNoChildren &&
|
|
!folder()->isSystemFolder() ) ? true : false;
|
|
}
|
|
|
|
void KMFolderCachedImap::slotFolderDeletionOnServerFinished()
|
|
{
|
|
for ( TQStringList::const_iterator it = foldersForDeletionOnServer.constBegin();
|
|
it != foldersForDeletionOnServer.constEnd(); ++it ) {
|
|
KURL url( mAccount->getUrl() );
|
|
url.setPath( *it );
|
|
kmkernel->iCalIface().folderDeletedOnServer( url );
|
|
}
|
|
serverSyncInternal();
|
|
}
|
|
|
|
int KMFolderCachedImap::createIndexFromContentsRecursive()
|
|
{
|
|
if ( !folder() || !folder()->child() )
|
|
return 0;
|
|
|
|
KMFolderNode *node = 0;
|
|
for( TQPtrListIterator<KMFolderNode> it( *folder()->child() ); (node = it.current()); ++it ) {
|
|
if( !node->isDir() ) {
|
|
KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
|
|
kdDebug() << k_funcinfo << "Re-indexing: " << storage->folder()->label() << endl;
|
|
int rv = storage->createIndexFromContentsRecursive();
|
|
if ( rv > 0 )
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return createIndexFromContents();
|
|
}
|
|
|
|
void KMFolderCachedImap::setAlarmsBlocked( bool blocked )
|
|
{
|
|
mAlarmsBlocked = blocked;
|
|
}
|
|
|
|
bool KMFolderCachedImap::alarmsBlocked() const
|
|
{
|
|
return mAlarmsBlocked;
|
|
}
|
|
|
|
bool KMFolderCachedImap::isCloseToQuota() const
|
|
{
|
|
bool closeToQuota = false;
|
|
if ( mQuotaInfo.isValid() && mQuotaInfo.max().toInt() > 0 ) {
|
|
const int ratio = mQuotaInfo.current().toInt() * 100 / mQuotaInfo.max().toInt();
|
|
//kdDebug(5006) << "Quota ratio: " << ratio << "% " << mQuotaInfo.toString() << endl;
|
|
closeToQuota = ( ratio > 0 && ratio >= GlobalSettings::closeToQuotaThreshold() );
|
|
}
|
|
//kdDebug(5006) << "Folder: " << folder()->prettyURL() << " is over quota: " << closeToQuota << endl;
|
|
return closeToQuota;
|
|
}
|
|
|
|
KMCommand* KMFolderCachedImap::rescueUnsyncedMessages()
|
|
{
|
|
TQValueList<unsigned long> newMsgs = findNewMessages();
|
|
kdDebug() << k_funcinfo << newMsgs << " of " << count() << endl;
|
|
if ( newMsgs.isEmpty() )
|
|
return 0;
|
|
KMFolder *dest = 0;
|
|
bool manualMove = true;
|
|
while ( GlobalSettings::autoLostFoundMove() ) {
|
|
// find the inbox of this account
|
|
KMFolder *inboxFolder = kmkernel->findFolderById( TQString(".%1.directory/INBOX").arg( account()->id() ) );
|
|
if ( !inboxFolder ) {
|
|
kdWarning(5006) << k_funcinfo << "inbox not found!" << endl;
|
|
break;
|
|
}
|
|
KMFolderDir *inboxDir = inboxFolder->child();
|
|
if ( !inboxDir && !inboxFolder->storage() )
|
|
break;
|
|
assert( inboxFolder->storage()->folderType() == KMFolderTypeCachedImap );
|
|
|
|
// create lost+found folder if needed
|
|
KMFolderNode *node;
|
|
KMFolder *lfFolder = 0;
|
|
if ( !(node = inboxDir->hasNamedFolder( i18n("lost+found") )) ) {
|
|
kdDebug(5006) << k_funcinfo << "creating lost+found folder" << endl;
|
|
KMFolder* folder = kmkernel->dimapFolderMgr()->createFolder(
|
|
i18n("lost+found"), false, KMFolderTypeCachedImap, inboxDir );
|
|
if ( !folder || !folder->storage() )
|
|
break;
|
|
static_cast<KMFolderCachedImap*>( folder->storage() )->initializeFrom(
|
|
static_cast<KMFolderCachedImap*>( inboxFolder->storage() ) );
|
|
folder->storage()->setContentsType( KMail::ContentsTypeMail );
|
|
folder->storage()->writeConfig();
|
|
lfFolder = folder;
|
|
} else {
|
|
kdDebug(5006) << k_funcinfo << "found lost+found folder" << endl;
|
|
lfFolder = dynamic_cast<KMFolder*>( node );
|
|
}
|
|
if ( !lfFolder || !lfFolder->createChildFolder() || !lfFolder->storage() )
|
|
break;
|
|
|
|
// create subfolder for this incident
|
|
TQDate today = TQDate::currentDate();
|
|
TQString baseName = folder()->label() + "-" + TQString::number( today.year() )
|
|
+ (today.month() < 10 ? "0" : "" ) + TQString::number( today.month() )
|
|
+ (today.day() < 10 ? "0" : "" ) + TQString::number( today.day() );
|
|
TQString name = baseName;
|
|
int suffix = 0;
|
|
while ( (node = lfFolder->child()->hasNamedFolder( name )) ) {
|
|
++suffix;
|
|
name = baseName + '-' + TQString::number( suffix );
|
|
}
|
|
kdDebug(5006) << k_funcinfo << "creating lost+found folder " << name << endl;
|
|
dest = kmkernel->dimapFolderMgr()->createFolder( name, false, KMFolderTypeCachedImap, lfFolder->child() );
|
|
if ( !dest || !dest->storage() )
|
|
break;
|
|
static_cast<KMFolderCachedImap*>( dest->storage() )->initializeFrom(
|
|
static_cast<KMFolderCachedImap*>( lfFolder->storage() ) );
|
|
dest->storage()->setContentsType( contentsType() );
|
|
dest->storage()->writeConfig();
|
|
|
|
KMessageBox::sorry( 0, i18n("<p>There are new messages in folder <b>%1</b>, which "
|
|
"have not been uploaded to the server yet, but the folder has been deleted "
|
|
"on the server or you do not "
|
|
"have sufficient access rights on the folder to upload them.</p>"
|
|
"<p>All affected messages will therefore be moved to <b>%2</b> "
|
|
"to avoid data loss.</p>").arg( folder()->prettyURL() ).arg( dest->prettyURL() ),
|
|
i18n("Insufficient access rights") );
|
|
manualMove = false;
|
|
break;
|
|
}
|
|
|
|
if ( manualMove ) {
|
|
const TQString msg ( i18n( "<p>There are new messages in this folder (%1), which "
|
|
"have not been uploaded to the server yet, but the folder has been deleted "
|
|
"on the server or you do not "
|
|
"have sufficient access rights on the folder now to upload them. "
|
|
"Please contact your administrator to allow upload of new messages "
|
|
"to you, or move them out of this folder.</p> "
|
|
"<p>Do you want to move these messages to another folder now?</p>").arg( folder()->prettyURL() ) );
|
|
if ( KMessageBox::warningYesNo( 0, msg, TQString(), i18n("Move"), i18n("Do Not Move") ) == KMessageBox::Yes ) {
|
|
KMail::KMFolderSelDlg dlg( kmkernel->getKMMainWidget(),
|
|
i18n("Move Messages to Folder"), true );
|
|
if ( dlg.exec() ) {
|
|
dest = dlg.folder();
|
|
}
|
|
}
|
|
}
|
|
if ( dest ) {
|
|
TQPtrList<KMMsgBase> msgs;
|
|
for( int i = 0; i < count(); ++i ) {
|
|
KMMsgBase *msg = getMsgBase( i );
|
|
if( !msg ) continue; /* what goes on if getMsg() returns 0? */
|
|
if ( msg->UID() == 0 )
|
|
msgs.append( msg );
|
|
}
|
|
KMCommand *command = new KMMoveCommand( dest, msgs );
|
|
command->start();
|
|
return command;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder( KMFolder *folder, bool root )
|
|
{
|
|
kdDebug() << k_funcinfo << folder << " " << root << endl;
|
|
if ( root )
|
|
mToBeDeletedAfterRescue.append( folder );
|
|
folder->open("cachedimap");
|
|
KMFolderCachedImap* storage = dynamic_cast<KMFolderCachedImap*>( folder->storage() );
|
|
if ( storage ) {
|
|
KMCommand *command = storage->rescueUnsyncedMessages();
|
|
if ( command ) {
|
|
connect( command, TQT_SIGNAL(completed(KMCommand*)),
|
|
TQT_SLOT(slotRescueDone(KMCommand*)) );
|
|
++mRescueCommandCount;
|
|
} else {
|
|
// nothing to rescue, close folder
|
|
// (we don't need to close it in the other case, it will be deleted anyway)
|
|
folder->close("cachedimap");
|
|
}
|
|
}
|
|
if ( folder->child() ) {
|
|
KMFolderNode *node = folder->child()->first();
|
|
while (node) {
|
|
if (!node->isDir() ) {
|
|
KMFolder *subFolder = static_cast<KMFolder*>( node );
|
|
rescueUnsyncedMessagesAndDeleteFolder( subFolder, false );
|
|
}
|
|
node = folder->child()->next();
|
|
}
|
|
}
|
|
}
|
|
|
|
void KMFolderCachedImap::slotRescueDone(KMCommand * command)
|
|
{
|
|
// FIXME: check command result
|
|
if ( command )
|
|
--mRescueCommandCount;
|
|
if ( mRescueCommandCount > 0 )
|
|
return;
|
|
for ( TQValueList<KMFolder*>::ConstIterator it = mToBeDeletedAfterRescue.constBegin();
|
|
it != mToBeDeletedAfterRescue.constEnd(); ++it ) {
|
|
kmkernel->dimapFolderMgr()->remove( *it );
|
|
}
|
|
mToBeDeletedAfterRescue.clear();
|
|
serverSyncInternal();
|
|
}
|
|
|
|
void KMFolderCachedImap::slotRenameFolderFinished()
|
|
{
|
|
// The syncing code assumes the folder was opened by us, and later closes it. So better
|
|
// make sure the reference count is correct, since the folder was force-closed by the rename.
|
|
// Otherwise bad things can happen, see https://issues.kolab.org/issue3853.
|
|
open( "cachedimap" );
|
|
serverSyncInternal();
|
|
}
|
|
|
|
bool KMFolderCachedImap::canDeleteMessages() const
|
|
{
|
|
if ( isReadOnly() )
|
|
return false;
|
|
if ( mUserRightsState == KMail::ACLJobs::Ok && !(userRights() & ACLJobs::Delete) )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool KMFolderCachedImap::mailCheckInProgress() const
|
|
{
|
|
return mSyncState != SYNC_STATE_INITIAL;
|
|
}
|
|
|
|
#include "kmfoldercachedimap.moc"
|