summaryrefslogtreecommitdiffstats
path: root/kresources/caldav
diff options
context:
space:
mode:
Diffstat (limited to 'kresources/caldav')
-rw-r--r--kresources/caldav/job.cpp5
-rw-r--r--kresources/caldav/job.h31
-rw-r--r--kresources/caldav/preferences.cpp17
-rw-r--r--kresources/caldav/resource.cpp243
-rw-r--r--kresources/caldav/resource.h20
-rw-r--r--kresources/caldav/writer.cpp2
6 files changed, 260 insertions, 58 deletions
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<QEvent::Type>(1000) ) );
+ if (type() == 1) QApplication::postEvent ( parent(), new QEvent( static_cast<QEvent::Type>(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 <qthread.h>
#include <qstring.h>
#include <qdatetime.h>
+#include <qapplication.h>
extern "C" {
#include <libcaldav/caldav.h>
@@ -53,6 +54,20 @@ public:
}
/**
+ * 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.
*/
virtual QString url() const {
@@ -60,6 +75,20 @@ public:
}
/**
+ * @return parent object
+ */
+ virtual QObject *parent() {
+ return mParent;
+ }
+
+ /**
+ * @return type
+ */
+ virtual int type() {
+ return mType;
+ }
+
+ /**
* @return true if downloading process failed.
*/
virtual bool error() const {
@@ -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("<b>") + i18n("Remote authorization required") + QString("</b><p>") + 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 <qptrqueue.h>
#include <libkcal/resourcecached.h>
+#include <libkdepim/progressmanager.h>
#include <kabc/locknull.h>
@@ -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 );
@@ -112,6 +117,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<LoadingTask> 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