From 7ef3b0e2a5e5bc9bf928e989e4f66932be096824 Mon Sep 17 00:00:00 2001 From: tpearson Date: Mon, 31 May 2010 07:49:08 +0000 Subject: Added new carddav resource for kaddressbook Lots of bugfixes for korganizer caldav resource git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1132701 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kresources/caldav/job.cpp | 5 + kresources/caldav/job.h | 31 +++++ kresources/caldav/preferences.cpp | 17 ++- kresources/caldav/resource.cpp | 243 +++++++++++++++++++++++++++++--------- kresources/caldav/resource.h | 20 +++- kresources/caldav/writer.cpp | 2 +- 6 files changed, 260 insertions(+), 58 deletions(-) (limited to 'kresources/caldav') diff --git a/kresources/caldav/job.cpp b/kresources/caldav/job.cpp index 9ba2ac265..9d317d131 100644 --- a/kresources/caldav/job.cpp +++ b/kresources/caldav/job.cpp @@ -101,6 +101,11 @@ void CalDavJob::run() { } caldav_free_runtime_info(&caldav_runtime); + + // Signal done + // 1000 is read, 1001 is write + if (type() == 0) QApplication::postEvent ( parent(), new QEvent( static_cast(1000) ) ); + if (type() == 1) QApplication::postEvent ( parent(), new QEvent( static_cast(1001) ) ); } // EOF ======================================================================== diff --git a/kresources/caldav/job.h b/kresources/caldav/job.h index 853ef5610..4f3430c90 100644 --- a/kresources/caldav/job.h +++ b/kresources/caldav/job.h @@ -20,6 +20,7 @@ #include #include #include +#include extern "C" { #include @@ -52,6 +53,20 @@ public: mUrl = s; } + /** + * Sets the parent qobject. + */ + virtual void setParent(QObject *s) { + mParent = s; + } + + /** + * Sets the type (0==read, 1==write) + */ + virtual void setType(int s) { + mType = s; + } + /** * @return URL to load. */ @@ -59,6 +74,20 @@ public: return mUrl; } + /** + * @return parent object + */ + virtual QObject *parent() { + return mParent; + } + + /** + * @return type + */ + virtual int type() { + return mType; + } + /** * @return true if downloading process failed. */ @@ -121,6 +150,8 @@ private: bool mError; QString mErrorString; long mErrorNumber; + QObject *mParent; + int mType; void enableCaldavDebug(runtime_info*); }; diff --git a/kresources/caldav/preferences.cpp b/kresources/caldav/preferences.cpp index 04ca22389..4be55e509 100644 --- a/kresources/caldav/preferences.cpp +++ b/kresources/caldav/preferences.cpp @@ -210,11 +210,26 @@ void CalDavPrefs::readConfig() { QString CalDavPrefs::getFullUrl() { QUrl t(url()); + QString safeURL; + int firstAt; t.setUser(username()); t.setPassword(password()); - return t.toString(); + safeURL = t.toString(); + + firstAt = safeURL.find("@") + 1; + while (safeURL.find("@", firstAt) != -1) { + safeURL.replace(safeURL.find("@", firstAt), 1, "%40"); + } + + // Unencode the username, as Zimbra stupidly rejects the %40 + safeURL.replace("%40", "@"); + + // Encode any spaces, as libcaldav stupidly fails otherwise + safeURL.replace(" ", "%20"); + + return safeURL; } // EOF ======================================================================== diff --git a/kresources/caldav/resource.cpp b/kresources/caldav/resource.cpp index 31e2ce0cd..3ab1d358f 100644 --- a/kresources/caldav/resource.cpp +++ b/kresources/caldav/resource.cpp @@ -69,10 +69,12 @@ const int ResourceCalDav::DEFAULT_SAVE_POLICY = ResourceCached::SaveDelaye ResourceCalDav::ResourceCalDav( const KConfig *config ) : ResourceCached(config) + , readLockout(false) , mLock(true) , mPrefs(NULL) , mLoader(NULL) , mWriter(NULL) + , mProgress(NULL) , mLoadingQueueReady(true) , mWritingQueueReady(true) { @@ -87,7 +89,12 @@ ResourceCalDav::ResourceCalDav( const KConfig *config ) : ResourceCalDav::~ResourceCalDav() { log("jobs termination"); - // TODO: do we need termination here? + // This must save the users data before termination below to prevent data loss... + doSave(); + while ((mWriter->running() == true) || (mWritingQueue.isEmpty() == false) || !mWritingQueueReady) { + sleep(1); + qApp->processEvents(QEventLoop::ExcludeUserInput); + } if (mLoader) { mLoader->terminate(); @@ -122,6 +129,10 @@ ResourceCalDav::~ResourceCalDav() { bool ResourceCalDav::doLoad() { bool syncCache = true; + if ((mLoadingQueueReady == false) || (mLoadingQueue.isEmpty() == false) || (mLoader->running() == true)) { + return true; // Silently fail; the user has obviously not responded to a dialog and we don't need to pop up more of them! + } + log(QString("doLoad(%1)").arg(syncCache)); clearCache(); @@ -130,6 +141,9 @@ bool ResourceCalDav::doLoad() { disableChangeNotification(); loadCache(); enableChangeNotification(); + clearChanges(); // TODO: Determine if this really needs to be here, as it might clear out the calendar prematurely causing user confusion while the download process is running + emit resourceChanged(this); + emit resourceLoaded(this); log("starting download job"); startLoading(mPrefs->getFullUrl()); @@ -150,15 +164,25 @@ bool ResourceCalDav::doSave() { log("saving cache"); saveCache(); - log("start writing job"); - startWriting(mPrefs->getFullUrl()); + // Delete any queued read jobs + mLoadingQueue.clear(); - log("clearing changes"); - // FIXME: Calling clearChanges() here is not the ideal way since the - // upload might fail, but there is no other place to call it... - clearChanges(); + // See if there is a running read thread and terminate it + if (mLoader->running() == true) { + mLoader->terminate(); + mLoader->wait(TERMINATION_WAITING_TIME); + mLoadingQueueReady = true; + } - return true; + log("start writing job"); + if (startWriting(mPrefs->getFullUrl()) == true) { + log("clearing changes"); + // FIXME: Calling clearChanges() here is not the ideal way since the + // upload might fail, but there is no other place to call it... + clearChanges(); + return true; + } + else return true; // We do not need to alert the user to this transient failure; a timer has been started to retry the save } @@ -201,19 +225,16 @@ void ResourceCalDav::init() { // creating preferences mPrefs = createPrefs(); + // creating reader/writer instances + mLoader = new CalDavReader; + mWriter = new CalDavWriter; + // creating jobs - // FIXME: Qt4 handles this quite differently, as shown below... -// mLoader = new CalDavReader; + // Qt4 handles this quite differently, as shown below, + // whereas Qt3 needs events (see ::event()) // connect(mLoader, SIGNAL(finished()), this, SLOT(loadFinished())); -// mWriter = new CalDavWriter; // connect(mWriter, SIGNAL(finished()), this, SLOT(writingFinished())); - // ...whereas Qt3 needs events like so: - mLoader = new CalDavReader; - //connect(mLoader, SIGNAL(finished()), this, SLOT(loadFinished())); - mWriter = new CalDavWriter; - //connect(mWriter, SIGNAL(finished()), this, SLOT(writingFinished())); - setType("ResourceCalDav"); } @@ -241,17 +262,46 @@ void ResourceCalDav::setReadOnly(bool v) { ensureReadOnlyFlagHonored(); } +void ResourceCalDav::updateProgressBar(int direction) { + int current_queued_events; + static int original_queued_events; + + // See if anything is in the queues + current_queued_events = mWritingQueue.count() + mLoadingQueue.count(); + if (current_queued_events > original_queued_events) { + original_queued_events = current_queued_events; + } + + if (current_queued_events == 0) { + if ( mProgress != NULL) { + mProgress->setComplete(); + mProgress = NULL; + original_queued_events = 0; + } + } + else { + if (mProgress == NULL) { + if (direction == 0) mProgress = KPIM::ProgressManager::createProgressItem(KPIM::ProgressManager::getUniqueID(), i18n("Downloading Calendar") ); + if (direction == 1) mProgress = KPIM::ProgressManager::createProgressItem(KPIM::ProgressManager::getUniqueID(), i18n("Uploading Calendar") ); + } + mProgress->setProgress( ((((float)original_queued_events-(float)current_queued_events)*100)/(float)original_queued_events) ); + } +} + /*========================================================================= | READING METHODS ========================================================================*/ void ResourceCalDav::loadingQueuePush(const LoadingTask *task) { - mLoadingQueue.enqueue(task); - loadingQueuePop(); + if ((mLoadingQueue.isEmpty() == true) && (mLoader->running() == false)) { + mLoadingQueue.enqueue(task); + loadingQueuePop(); + updateProgressBar(0); + } } void ResourceCalDav::loadingQueuePop() { - if (!mLoadingQueueReady || mLoadingQueue.isEmpty()) { + if (!mLoadingQueueReady || mLoadingQueue.isEmpty() || (mWritingQueue.isEmpty() == false) || (mWriter->running() == true) || !mWritingQueueReady || (readLockout == true)) { return; } @@ -265,6 +315,8 @@ void ResourceCalDav::loadingQueuePop() { LoadingTask *t = mLoadingQueue.head(); mLoader->setUrl(t->url); + mLoader->setParent(this); + mLoader->setType(0); QDateTime dt(QDate::currentDate()); mLoader->setRange(dt.addDays(-CACHE_DAYS), dt.addDays(CACHE_DAYS)); @@ -274,21 +326,12 @@ void ResourceCalDav::loadingQueuePop() { log("starting actual download job"); mLoader->start(QThread::LowestPriority); + updateProgressBar(0); // if all ok, removing the task from the queue mLoadingQueue.dequeue(); delete t; - - // FIXME - // Qt3 needs to wait here for the download to finish, as I am too - // lazy to set up the correct event mechanisms at this time - // The correct mechanism would be to have the thread call - // QApplication::postEvent(), have the GUI trap the event, - // and then call loadFinished() - while (mLoader->running() == true) - qApp->eventLoop()->processEvents(QEventLoop::ExcludeUserInput); - loadFinished(); } void ResourceCalDav::startLoading(const QString& url) { @@ -302,6 +345,8 @@ void ResourceCalDav::loadFinished() { log("load finished"); + updateProgressBar(0); + if (!loader) { log("loader is NULL"); return; @@ -343,9 +388,13 @@ void ResourceCalDav::loadFinished() { log("trying to parse..."); if (parseData(data)) { + // FIXME: The agenda view can crash when a change is + // made on a remote server and a reload is requested! log("... parsing is ok"); log("clearing changes"); + enableChangeNotification(); clearChanges(); + emit resourceChanged(this); emit resourceLoaded(this); } } @@ -384,7 +433,6 @@ bool ResourceCalDav::parseData(const QString& data) { log("clearing cache"); clearCache(); - emit resourceChanged(this); disableChangeNotification(); @@ -425,8 +473,6 @@ bool ResourceCalDav::parseData(const QString& data) { saveCache(); } - emit resourceChanged(this); - return ret; } @@ -461,11 +507,10 @@ void ResourceCalDav::writingQueuePush(const WritingTask *task) { // printf("task->changed: %s\n\r", task->changed.ascii()); mWritingQueue.enqueue(task); writingQueuePop(); + updateProgressBar(1); } void ResourceCalDav::writingQueuePop() { - // FIXME This crashes... - if (!mWritingQueueReady || mWritingQueue.isEmpty()) { return; } @@ -482,6 +527,8 @@ void ResourceCalDav::writingQueuePop() { log("writingQueuePop: url = " + t->url); mWriter->setUrl(t->url); + mWriter->setParent(this); + mWriter->setType(1); #ifdef KCALDAV_DEBUG const QString fout_path = "/tmp/kcaldav_upload_" + identifier() + ".tmp"; @@ -507,56 +554,144 @@ void ResourceCalDav::writingQueuePop() { log("starting actual write job"); mWriter->start(QThread::LowestPriority); + updateProgressBar(1); // if all ok, remove the task from the queue mWritingQueue.dequeue(); delete t; +} - // FIXME - // Qt3 needs to wait here for the download to finish, as I am too - // lazy to set up the correct event mechanisms at this time - // The correct mechanism would be to have the thread call - // QApplication::postEvent(), have the GUI trap the event, - // and then call writingFinished() - while (mWriter->running() == true) - qApp->eventLoop()->processEvents(QEventLoop::ExcludeUserInput); - writingFinished(); +bool ResourceCalDav::event ( QEvent * e ) { + if (e->type() == 1000) { + // Read done + loadFinished(); + return TRUE; + } + else if (e->type() == 1001) { + // Write done + writingFinished(); + return TRUE; + } + else return FALSE; } -void ResourceCalDav::startWriting(const QString& url) { +void ResourceCalDav::releaseReadLockout() { + readLockout = false; +} + +bool ResourceCalDav::startWriting(const QString& url) { log("startWriting: url = " + url); - WritingTask *t = new WritingTask; + // WARNING: This will segfault if a separate read or write thread + // modifies the calendar with clearChanges() or similar + // Before these calls are made any existing read (and maybe write) threads should be finished + if ((mLoader->running() == true) || (mLoadingQueue.isEmpty() == false) || (mWriter->running() == true) || (mWritingQueue.isEmpty() == false)) { + QTimer::singleShot( 100, this, SLOT(doSave()) ); + return false; + } + + // If we don't lock the read out for a few seconds, it would be possible for the old calendar to be + // downloaded before our changes are committed, presenting a very bad image to the user as his/her appointments + // revert to the state they were in before the write (albiet temporarily) + readLockout = true; + + // This needs to send each event separately; i.e. if two events were added they need + // to be extracted and pushed on the stack independently (using two calls to writingQueuePush()) Incidence::List added = addedIncidences(); Incidence::List changed = changedIncidences(); Incidence::List deleted = deletedIncidences(); - t->url = url; - t->added = getICalString(added); // This crashes when an event is added from the remote server and save() is subsequently called - t->changed = getICalString(changed); - t->deleted = getICalString(deleted); + Incidence::List::ConstIterator it; + Incidence::List currentIncidence; + + for( it = added.begin(); it != added.end(); ++it ) { + WritingTask *t = new WritingTask; + + currentIncidence.clear(); + currentIncidence.append(*it); + + t->url = url; + t->added = getICalString(currentIncidence); + t->changed = ""; + t->deleted = ""; + + writingQueuePush(t); + } + + for( it = changed.begin(); it != changed.end(); ++it ) { + WritingTask *t = new WritingTask; + + currentIncidence.clear(); + currentIncidence.append(*it); + + t->url = url; + t->added = ""; + t->changed = getICalString(currentIncidence); + t->deleted = ""; + + writingQueuePush(t); + } + + for( it = deleted.begin(); it != deleted.end(); ++it ) { + WritingTask *t = new WritingTask; + + currentIncidence.clear(); + currentIncidence.append(*it); + + t->url = url; + t->added = ""; + t->changed = ""; + t->deleted = getICalString(currentIncidence); - writingQueuePush(t); + writingQueuePush(t); + } + + return true; } void ResourceCalDav::writingFinished() { log("writing finished"); + updateProgressBar(1); + if (!mWriter) { log("mWriter is NULL"); return; } - if (mWriter->error()) { - log("error: " + mWriter->errorString()); - saveError(QString("[%1] ").arg(abs(mWriter->errorNumber())) + mWriter->errorString()); + if (mWriter->error() && (abs(mWriter->errorNumber()) != 207)) { + if (mWriter->errorNumber() == -401) { + if (NULL != mPrefs) { + QCString newpass; + if (KPasswordDialog::getPassword (newpass, QString("") + i18n("Remote authorization required") + QString("

") + i18n("Please input the password for") + QString(" ") + mPrefs->getusername(), NULL) != 1) { + log("write error: " + mWriter->errorString()); + saveError(QString("[%1] ").arg(abs(mWriter->errorNumber())) + mWriter->errorString()); + } + else { + // Set new password and try again + mPrefs->setPassword(QString(newpass)); + startWriting(mPrefs->getFullUrl()); + } + } + else { + log("write error: " + mWriter->errorString()); + saveError(QString("[%1] ").arg(abs(mWriter->errorNumber())) + mWriter->errorString()); + } + } + else { + log("write error: " + mWriter->errorString()); + saveError(QString("[%1] ").arg(abs(mWriter->errorNumber())) + mWriter->errorString()); + } } else { log("success"); // is there something to do here? } + // Give the remote system a few seconds to process the data before we allow any read operations + QTimer::singleShot( 3000, this, SLOT(releaseReadLockout()) ); + // Writing queue and mWritingQueueReady flag are not shared resources, i.e. only one thread has an access to them. // That's why no mutexes are required. mWritingQueueReady = true; diff --git a/kresources/caldav/resource.h b/kresources/caldav/resource.h index 6b28c3507..a93bf05b3 100644 --- a/kresources/caldav/resource.h +++ b/kresources/caldav/resource.h @@ -22,6 +22,7 @@ #include #include +#include #include @@ -72,8 +73,12 @@ protected slots: void loadFinished(); + virtual bool doSave(); + void writingFinished(); + void releaseReadLockout(); + protected: struct LoadingTask { @@ -92,7 +97,7 @@ protected: // virtual bool doSave( bool syncCache ); virtual bool doLoad(); - virtual bool doSave(); +// virtual bool doSave(); virtual bool doSave( bool syncCache, Incidence *incidence ); @@ -111,6 +116,11 @@ protected: */ void init(); + /** + * Updates the progress bar + */ + void updateProgressBar(int direction); + /** * Initiates calendar loading process. * @param url URL to load calendar data from. @@ -134,8 +144,9 @@ protected: /** * Initiates calendar writing process. * @param url URL to save calendar data to. + * @return true if write was queued successfully, false if not */ - void startWriting(const QString& url); + bool startWriting(const QString& url); /** * Returns a list of incidences as a valid iCalendar string. @@ -180,6 +191,8 @@ protected: */ void writingQueuePush(const WritingTask *task); + virtual bool event ( QEvent * e ); + private: // constants: ============================================================= @@ -197,12 +210,15 @@ private: static const int DEFAULT_RELOAD_POLICY; static const int DEFAULT_SAVE_POLICY; + bool readLockout; + // members: =============================================================== KABC::LockNull mLock; CalDavPrefs* mPrefs; CalDavReader* mLoader; CalDavWriter* mWriter; + KPIM::ProgressItem *mProgress; bool mLoadingQueueReady; QPtrQueue mLoadingQueue; diff --git a/kresources/caldav/writer.cpp b/kresources/caldav/writer.cpp index 8980c3d28..98008bcd8 100644 --- a/kresources/caldav/writer.cpp +++ b/kresources/caldav/writer.cpp @@ -27,7 +27,7 @@ // is used for modifying objects. // It's done, because, for some reason, SOGo server returns an error // on caldav_modify_object. DAViCAL works fine both ways. -//#define USE_CALDAV_MODIFY +#define USE_CALDAV_MODIFY /*========================================================================= | NAMESPACE -- cgit v1.2.1