diff options
author | Timothy Pearson <kb9vqf@pearsoncomputing.net> | 2013-01-26 13:17:50 -0600 |
---|---|---|
committer | Timothy Pearson <kb9vqf@pearsoncomputing.net> | 2013-01-26 13:17:50 -0600 |
commit | b363d2579af0a11b77e698aed2e1021c2233b644 (patch) | |
tree | f4a47b87354b7a6a3b266c8121bd8ddaeb7accaa /tderesources/caldav/resource.cpp | |
parent | 61bddfe3a7226b18c68a76124b727c736f431688 (diff) | |
download | tdepim-b363d2579af0a11b77e698aed2e1021c2233b644.tar.gz tdepim-b363d2579af0a11b77e698aed2e1021c2233b644.zip |
Rename a number of libraries and executables to avoid conflicts with KDE4
Diffstat (limited to 'tderesources/caldav/resource.cpp')
-rw-r--r-- | tderesources/caldav/resource.cpp | 1011 |
1 files changed, 1011 insertions, 0 deletions
diff --git a/tderesources/caldav/resource.cpp b/tderesources/caldav/resource.cpp new file mode 100644 index 000000000..b9285e611 --- /dev/null +++ b/tderesources/caldav/resource.cpp @@ -0,0 +1,1011 @@ +/*========================================================================= +| KCalDAV +|-------------------------------------------------------------------------- +| (c) 2010 Timothy Pearson +| (c) 2009 Kumaran Santhanam (initial KDE4 version) +| +| This project is released under the GNU General Public License. +| Please see the file COPYING for more details. +|-------------------------------------------------------------------------- +| Main interface to the KResource system. + ========================================================================*/ + +/*========================================================================= +| INCLUDES + ========================================================================*/ + +#include <string.h> +#include <unistd.h> + +#include <tqurl.h> +#include <tqmessagebox.h> +#include <tqapplication.h> +#include <tqeventloop.h> + +#include <libkcal/calendarlocal.h> +#include <libkcal/icalformat.h> + +#include <klocale.h> +#include <kpassdlg.h> + +#include <tqdatetime.h> +#include <tqmutex.h> +#include <tqthread.h> + +#ifdef KCALDAV_DEBUG + #include <tqfile.h> +#endif + +#include "resource.h" +#include "reader.h" +#include "writer.h" + +/*========================================================================= +| NAMESPACE + ========================================================================*/ + +using namespace KCal; + +/*========================================================================= +| CONSTANTS + ========================================================================*/ + +const unsigned long ResourceCalDav::TERMINATION_WAITING_TIME = 3 * 1000; // 3 seconds +const int ResourceCalDav::CACHE_DAYS = 90; + +const int ResourceCalDav::DEFAULT_RELOAD_INTERVAL = 10; +const int ResourceCalDav::DEFAULT_SAVE_INTERVAL = 10; +const int ResourceCalDav::DEFAULT_RELOAD_POLICY = ResourceCached::ReloadInterval; +const int ResourceCalDav::DEFAULT_SAVE_POLICY = ResourceCached::SaveDelayed; + +/*========================================================================= +| UTILITY + ========================================================================*/ + +#define log(s) kdDebug() << identifier() << ": " << (s) << '\n'; + +/*========================================================================= +| CONSTRUCTOR / DESTRUCTOR + ========================================================================*/ + +ResourceCalDav::ResourceCalDav( const TDEConfig *config ) : + ResourceCached(config) + , readLockout(false) + , mAllWritesComplete(false) + , mLock(true) + , mPrefs(NULL) + , mLoader(NULL) + , mWriter(NULL) + , mProgress(NULL) + , mLoadingQueueReady(true) + , mWritingQueueReady(true) + , mWriteRetryTimer(NULL) +{ + log("ResourceCalDav(config)"); + init(); + + if ( config ) { + readConfig( config ); + } +} + +ResourceCalDav::~ResourceCalDav() { + log("jobs termination"); + + if (mWriteRetryTimer != NULL) { + mWriteRetryTimer->stop(); // Unfortunately we cannot do anything at this point; if this timer is still running something is seriously wrong + } + + if (mLoader) { + readLockout = true; + mLoader->terminate(); + mLoader->wait(TERMINATION_WAITING_TIME); + mLoadingQueueReady = true; + } + + while ((mWriter->running() == true) || (mWritingQueue.isEmpty() == false) || !mWritingQueueReady) { + readLockout = true; + sleep(1); + tqApp->processEvents(TQEventLoop::ExcludeUserInput); + } + + if (mWriter) { + mWriter->terminate(); + } + + log("waiting for jobs terminated"); + + if (mLoader) { + mLoader->wait(TERMINATION_WAITING_TIME); + } + if (mWriter) { + mWriter->wait(TERMINATION_WAITING_TIME); + } + + log("deleting jobs"); + + delete mLoader; + delete mWriter; + + log("deleting preferences"); + + delete mPrefs; +} + +bool ResourceCalDav::isSaving() { + doSave(); + return (((mWriteRetryTimer != NULL) ? 1 : 0) || (mWriter->running() == true) || (mWritingQueue.isEmpty() == false) || !mWritingQueueReady || readLockout); +} + +/*========================================================================= +| GENERAL METHODS + ========================================================================*/ + +bool ResourceCalDav::doLoad() { + bool syncCache = true; + + if ((mLoadingQueueReady == false) || (mLoadingQueue.isEmpty() == false) || (mLoader->running() == true) || (isSaving() == 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(TQString("doLoad(%1)").arg(syncCache)); + + clearCache(); + + log("loading from cache"); + 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(), mPrefs->getFullTasksUrl(), mPrefs->getFullJournalsUrl()); + + return true; +} + +bool ResourceCalDav::doSave() { + bool syncCache = true; + + log(TQString("doSave(%1)").arg(syncCache)); + + if (!hasChanges()) { + log("no changes"); + return true; + } + + log("saving cache"); + saveCache(); + + // Delete any queued read jobs + mLoadingQueue.clear(); + + // See if there is a running read thread and terminate it + if (mLoader->running() == true) { + mLoader->terminate(); + mLoader->wait(TERMINATION_WAITING_TIME); + mLoadingQueueReady = true; + } + + log("start writing job"); + if (startWriting(mPrefs->getFullUrl(), mPrefs->getFullTasksUrl(), mPrefs->getFullJournalsUrl()) == 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(); + if (mWriteRetryTimer != NULL) { + if (mWriteRetryTimer->isActive() == false) { + disconnect( mWriteRetryTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(doSave()) ); + delete mWriteRetryTimer; + mWriteRetryTimer = NULL; + } + } + 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 +} + + +KABC::Lock* ResourceCalDav::lock() { + log("lock()"); + return &mLock; +} + +void ResourceCalDav::readConfig( const TDEConfig *config ) { + log("readConfig"); + mPrefs->readConfig(); + ResourceCached::readConfig(config); +} + +void ResourceCalDav::writeConfig( TDEConfig *config ) { + log("writeConfig()"); + ResourceCalendar::writeConfig(config); + mPrefs->writeConfig(); + ResourceCached::writeConfig(config); +} + +CalDavPrefs* ResourceCalDav::createPrefs() const { + log("createPrefs()"); + CalDavPrefs* p = new CalDavPrefs(identifier()); + return p; +} + +void ResourceCalDav::init() { + // default settings + setReloadInterval(DEFAULT_RELOAD_INTERVAL); + setReloadPolicy(DEFAULT_RELOAD_POLICY); + setSaveInterval(DEFAULT_SAVE_INTERVAL); + setSavePolicy(DEFAULT_SAVE_POLICY); + + // creating preferences + mPrefs = createPrefs(); + + // creating reader/writer instances + mLoader = new CalDavReader; + mWriter = new CalDavWriter; + + // creating jobs + // TQt4 handles this quite differently, as shown below, + // whereas TQt3 needs events (see ::event()) +// connect(mLoader, TQT_SIGNAL(finished()), this, TQT_SLOT(loadFinished())); +// connect(mWriter, TQT_SIGNAL(finished()), this, TQT_SLOT(writingFinished())); + + setType("ResourceCalDav"); +} + +void ResourceCalDav::setIncidencesReadOnly(Incidence::List& inc, bool readOnly) { + Incidence::List::Iterator it; + for ( it = inc.begin(); it != inc.end(); ++it ) { + (*it)->setReadOnly( readOnly ); + } +} + +void ResourceCalDav::ensureReadOnlyFlagHonored() { + //disableChangeNotification(); + + Incidence::List inc( rawIncidences() ); + setIncidencesReadOnly(inc, readOnly()); + + //enableChangeNotification(); + + emit resourceChanged(this); +} + +void ResourceCalDav::setReadOnly(bool v) { + KRES::Resource::setReadOnly(v); + log("ensuring read only flag honored"); + 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 ((direction == 0) && (mLoader->running() == true)) current_queued_events++; + if ((direction == 1) && (mWriter->running() == true)) current_queued_events++; + 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) { + if ((mLoadingQueue.isEmpty() == true) && (mLoader->running() == false)) { + mLoadingQueue.enqueue(task); + updateProgressBar(0); + loadingQueuePop(); + } +} + +void ResourceCalDav::loadingQueuePop() { + if (!mLoadingQueueReady || mLoadingQueue.isEmpty() || (isSaving() == true)) { + return; + } + + if (!mLoader) { + log("loader == NULL"); + return; + } + + // Loading queue and mLoadingQueueReady flag are not shared resources, i.e. only one thread has an access to them. + // That's why no mutexes are required. + LoadingTask *t = mLoadingQueue.head(); + + mLoader->setUrl(t->url); + mLoader->setTasksUrl(t->tasksUrl); + mLoader->setJournalsUrl(t->journalsUrl); + mLoader->setParent(this); + mLoader->setType(0); + + TQDateTime dt(TQDate::currentDate()); + mLoader->setRange(dt.addDays(-CACHE_DAYS), dt.addDays(CACHE_DAYS)); + //mLoader->setGetAll(); + + mLoadingQueueReady = false; + + log("starting actual download job"); + mLoader->start(TQThread::LowestPriority); + + // if all ok, removing the task from the queue + mLoadingQueue.dequeue(); + updateProgressBar(0); + + delete t; +} + +void ResourceCalDav::startLoading(const TQString& url, const TQString& tasksUrl, const TQString& journalsUrl) { + LoadingTask *t = new LoadingTask; + t->url = url; + t->tasksUrl = tasksUrl; + t->journalsUrl = journalsUrl; + loadingQueuePush(t); +} + +void ResourceCalDav::loadFinished() { + CalDavReader* loader = mLoader; + + log("load finished"); + + if (!loader) { + log("loader is NULL"); + return; + } + + if (loader->error()) { + if (loader->errorNumber() == -401) { + if (NULL != mPrefs) { + TQCString newpass; + if (KPasswordDialog::getPassword (newpass, TQString("<b>") + i18n("Remote authorization required") + TQString("</b><p>") + i18n("Please input the password for") + TQString(" ") + mPrefs->getusername(), NULL) != 1) { + log("load error: " + loader->errorString() ); + loadError(TQString("[%1] ").arg(abs(loader->errorNumber())) + loader->errorString()); + } + else { + // Set new password and try again + mPrefs->setPassword(TQString(newpass)); + startLoading(mPrefs->getFullUrl(), mPrefs->getFullTasksUrl(), mPrefs->getFullJournalsUrl()); + } + } + else { + log("load error: " + loader->errorString() ); + loadError(TQString("[%1] ").arg(abs(loader->errorNumber())) + loader->errorString()); + } + } + else { + log("load error: " + loader->errorString() ); + loadError(TQString("[%1] ").arg(abs(loader->errorNumber())) + loader->errorString()); + } + } else { + log("successful event load"); + TQString data = loader->data(); + + if (!data.isNull() && !data.isEmpty()) { + // TODO: I don't know why, but some schedules on http://caldav-test.ioda.net/ (I used it for testing) + // have some lines separated by single \r rather than \n or \r\n. + // ICalFormat fails to parse that. + data.replace("\r\n", "\n"); // to avoid \r\n becomes \n\n after the next line + data.replace('\r', '\n'); + + 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); + } + } + } + + if (loader->tasksError()) { + if (loader->tasksErrorNumber() == -401) { + if (NULL != mPrefs) { +// TQCString newpass; +// if (KPasswordDialog::getPassword (newpass, TQString("<b>") + i18n("Remote authorization required") + TQString("</b><p>") + i18n("Please input the password for") + TQString(" ") + mPrefs->getusername(), NULL) != 1) { +// log("load error: " + loader->tasksErrorString() ); +// loadError(TQString("[%1] ").arg(abs(loader->tasksErrorNumber())) + loader->tasksErrorString()); +// } +// else { +// // Set new password and try again +// mPrefs->setPassword(TQString(newpass)); +// startLoading(mPrefs->getFullUrl(), mPrefs->getFullTasksUrl(), mPrefs->getFullJournalsUrl()); +// } + } + else { + log("load error: " + loader->tasksErrorString() ); + loadError(TQString("[%1] ").arg(abs(loader->tasksErrorNumber())) + loader->tasksErrorString()); + } + } + else { + log("load error: " + loader->tasksErrorString() ); + loadError(TQString("[%1] ").arg(abs(loader->tasksErrorNumber())) + loader->tasksErrorString()); + } + } else { + log("successful tasks load"); + TQString tasksData = loader->tasksData(); + + if (!tasksData.isNull() && !tasksData.isEmpty()) { + // TODO: I don't know why, but some schedules on http://caldav-test.ioda.net/ (I used it for testing) + // have some lines separated by single \r rather than \n or \r\n. + // ICalFormat fails to parse that. + tasksData.replace("\r\n", "\n"); // to avoid \r\n becomes \n\n after the next line + tasksData.replace('\r', '\n'); + + log("trying to parse..."); + if (parseTasksData(tasksData)) { + // 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); + } + } + } + + if (loader->journalsError()) { + if (loader->journalsErrorNumber() == -401) { + if (NULL != mPrefs) { +// TQCString newpass; +// if (KPasswordDialog::getPassword (newpass, TQString("<b>") + i18n("Remote authorization required") + TQString("</b><p>") + i18n("Please input the password for") + TQString(" ") + mPrefs->getusername(), NULL) != 1) { +// log("load error: " + loader->journalsErrorString() ); +// loadError(TQString("[%1] ").arg(abs(loader->journalsErrorNumber())) + loader->journalsErrorString()); +// } +// else { +// // Set new password and try again +// mPrefs->setPassword(TQString(newpass)); +// startLoading(mPrefs->getFullUrl(), mPrefs->getFullTasksUrl(), mPrefs->getFullJournalsUrl()); +// } + } + else { + log("load error: " + loader->journalsErrorString() ); + loadError(TQString("[%1] ").arg(abs(loader->journalsErrorNumber())) + loader->journalsErrorString()); + } + } + else { + log("load error: " + loader->journalsErrorString() ); + loadError(TQString("[%1] ").arg(abs(loader->journalsErrorNumber())) + loader->journalsErrorString()); + } + } else { + log("successful journals load"); + TQString journalsData = loader->journalsData(); + + if (!journalsData.isNull() && !journalsData.isEmpty()) { + // TODO: I don't know why, but some schedules on http://caldav-test.ioda.net/ (I used it for testing) + // have some lines separated by single \r rather than \n or \r\n. + // ICalFormat fails to parse that. + journalsData.replace("\r\n", "\n"); // to avoid \r\n becomes \n\n after the next line + journalsData.replace('\r', '\n'); + + log("trying to parse..."); + if (parseJournalsData(journalsData)) { + // 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); + } + } + } + + // Loading queue and mLoadingQueueReady flag are not shared resources, i.e. only one thread has an access to them. + // That's why no mutexes are required. + mLoader->terminate(); + mLoader->wait(TERMINATION_WAITING_TIME); + mLoadingQueueReady = true; + updateProgressBar(0); + loadingQueuePop(); +} + +bool ResourceCalDav::checkData(const TQString& data) { + log("checking the data"); + + ICalFormat ical; + bool ret = true; + if (!ical.fromString(&mCalendar, data)) { + log("invalid ical string"); + ret = false; + } + + return ret; +} + +bool ResourceCalDav::parseData(const TQString& data) { + log("parseData()"); + + bool ret = true; + + // check if the data is OK + // May be it's not efficient (parsing is done twice), but it should be safe + if (!checkData(data)) { + loadError(i18n("Parsing calendar data failed.")); + return false; + } + + log("clearing cache"); + clearEventsCache(); + + disableChangeNotification(); + + log("actually parsing the data"); + + ICalFormat ical; + if ( !ical.fromString( &mCalendar, data ) ) { + // this should never happen, but... + ret = false; + } + + // debug code here ------------------------------------------------------- +#ifdef KCALDAV_DEBUG + const TQString fout_path = "/tmp/kcaldav_download_" + identifier() + ".tmp"; + + TQFile fout(fout_path); + if (fout.open(IO_WriteOnly | IO_Append)) { + TQTextStream sout(&fout); + sout << "---------- " << resourceName() << ": --------------------------------\n"; + sout << data << "\n"; + fout.close(); + } else { + loadError(i18n("can't open file")); + } +#endif // KCALDAV_DEBUG + // end of debug code ---------------------------------------------------- + + enableChangeNotification(); + + if (ret) { + log("parsing is ok"); + //if ( !noReadOnlyOnLoad() && readOnly() ) { + if ( readOnly() ) { + log("ensuring read only flag honored"); + ensureReadOnlyFlagHonored(); + } + log("saving to cache"); + saveCache(); + } + + return ret; +} + +bool ResourceCalDav::parseTasksData(const TQString& data) { + log("parseTasksData()"); + + bool ret = true; + + // check if the data is OK + // May be it's not efficient (parsing is done twice), but it should be safe + if (!checkData(data)) { + loadError(i18n("Parsing calendar data failed.")); + return false; + } + + log("clearing cache"); + clearTodosCache(); + + disableChangeNotification(); + + log("actually parsing the data"); + + ICalFormat ical; + if ( !ical.fromString( &mCalendar, data ) ) { + // this should never happen, but... + ret = false; + } + + // debug code here ------------------------------------------------------- +#ifdef KCALDAV_DEBUG + const TQString fout_path = "/tmp/kcaldav_download_" + identifier() + ".tmp"; + + TQFile fout(fout_path); + if (fout.open(IO_WriteOnly | IO_Append)) { + TQTextStream sout(&fout); + sout << "---------- " << resourceName() << ": --------------------------------\n"; + sout << data << "\n"; + fout.close(); + } else { + loadError(i18n("can't open file")); + } +#endif // KCALDAV_DEBUG + // end of debug code ---------------------------------------------------- + + enableChangeNotification(); + + if (ret) { + log("parsing is ok"); + //if ( !noReadOnlyOnLoad() && readOnly() ) { + if ( readOnly() ) { + log("ensuring read only flag honored"); + ensureReadOnlyFlagHonored(); + } + log("saving to cache"); + saveCache(); + } + + return ret; +} + +bool ResourceCalDav::parseJournalsData(const TQString& data) { + log("parseJournalsData()"); + + bool ret = true; + + // check if the data is OK + // May be it's not efficient (parsing is done twice), but it should be safe + if (!checkData(data)) { + loadError(i18n("Parsing calendar data failed.")); + return false; + } + + log("clearing cache"); + clearJournalsCache(); + + disableChangeNotification(); + + log("actually parsing the data"); + + ICalFormat ical; + if ( !ical.fromString( &mCalendar, data ) ) { + // this should never happen, but... + ret = false; + } + + // debug code here ------------------------------------------------------- +#ifdef KCALDAV_DEBUG + const TQString fout_path = "/tmp/kcaldav_download_" + identifier() + ".tmp"; + + TQFile fout(fout_path); + if (fout.open(IO_WriteOnly | IO_Append)) { + TQTextStream sout(&fout); + sout << "---------- " << resourceName() << ": --------------------------------\n"; + sout << data << "\n"; + fout.close(); + } else { + loadError(i18n("can't open file")); + } +#endif // KCALDAV_DEBUG + // end of debug code ---------------------------------------------------- + + enableChangeNotification(); + + if (ret) { + log("parsing is ok"); + //if ( !noReadOnlyOnLoad() && readOnly() ) { + if ( readOnly() ) { + log("ensuring read only flag honored"); + ensureReadOnlyFlagHonored(); + } + log("saving to cache"); + saveCache(); + } + + return ret; +} + +/*========================================================================= +| WRITING METHODS + ========================================================================*/ + +TQString ResourceCalDav::getICalString(const Incidence::List& inc) { + if (inc.isEmpty()) { + return ""; + } + + CalendarLocal loc(timeZoneId()); + TQString data = ""; + TQString header = ""; + TQString footer = ""; + ICalFormat ical; + + // Get the iCal header and footer + header = ical.toString(&loc); + int location = header.find("END:VCALENDAR", 0, true); + footer = header.mid(location, 0xffffffff); + header.remove("END:VCALENDAR"); + + data = data + header; + // NOTE: This is very susceptible to invalid entries in added/changed/deletedIncidences + // Be very careful with clearChange/clearChanges, and be sure to clear after load and save... + for(Incidence::List::ConstIterator it = inc.constBegin(); it != inc.constEnd(); ++it) { + Incidence *i = (*it)->clone(); + data = data + ical.toString(i, &mCalendar); + } + data = data + footer; + + // Remove any line feeds/carriage returns/white spaces from the UID field as they WILL break libcaldav + int uidPos = data.find("UID:", 0) + 4; + int nextPos = data.findRev("\n", data.find(":", uidPos)); + TQString uidField = data.mid(uidPos, nextPos-uidPos); + data.remove(uidPos, nextPos-uidPos); + uidField.replace("\n", ""); + uidField.replace("\r", ""); + uidField.replace(" ", ""); + data.insert(uidPos, uidField); + return data; +} + +void ResourceCalDav::writingQueuePush(const WritingTask *task) { +// printf("task->added: %s\n\r", task->added.ascii()); +// printf("task->deleted: %s\n\r", task->deleted.ascii()); +// printf("task->changed: %s\n\r", task->changed.ascii()); + mWritingQueue.enqueue(task); + updateProgressBar(1); + writingQueuePop(); +} + +void ResourceCalDav::writingQueuePop() { + if (!mWritingQueueReady || mWritingQueue.isEmpty()) { + return; + } + + if (!mWriter) { + log("writer == NULL"); + return; + } + + // 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. + WritingTask *t = mWritingQueue.head(); + + log("writingQueuePop: url = " + t->url); + + mWriter->setUrl(t->url); + mWriter->setTasksUrl(t->tasksUrl); + mWriter->setJournalsUrl(t->journalsUrl); + mWriter->setParent(this); + mWriter->setType(1); + +#ifdef KCALDAV_DEBUG + const TQString fout_path = "/tmp/kcaldav_upload_" + identifier() + ".tmp"; + + TQFile fout(fout_path); + if (fout.open(IO_WriteOnly | IO_Append)) { + TQTextStream sout(&fout); + sout << "---------- " << resourceName() << ": --------------------------------\n"; + sout << "================== Added:\n" << t->added << "\n"; + sout << "================== Changed:\n" << t->changed << "\n"; + sout << "================== Deleted:\n" << t->deleted << "\n"; + fout.close(); + } else { + loadError(i18n("can't open file")); + } +#endif // debug + + mWriter->setAddedObjects(t->added); + mWriter->setChangedObjects(t->changed); + mWriter->setDeletedObjects(t->deleted); + + mWriter->setAddedTasksObjects(t->tasksAdded); + mWriter->setChangedTasksObjects(t->tasksChanged); + mWriter->setDeletedTasksObjects(t->tasksDeleted); + + mWriter->setAddedJournalsObjects(t->journalsAdded); + mWriter->setChangedJournalsObjects(t->journalsChanged); + mWriter->setDeletedJournalsObjects(t->journalsDeleted); + + mWritingQueueReady = false; + + log("starting actual write job"); + mWriter->start(TQThread::LowestPriority); + + // if all ok, remove the task from the queue + mWritingQueue.dequeue(); + updateProgressBar(1); + + delete t; +} + +bool ResourceCalDav::event ( TQEvent * 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::releaseReadLockout() { + readLockout = false; +} + +bool ResourceCalDav::startWriting(const TQString& url, const TQString& tasksUrl, const TQString& journalsUrl) { + log("startWriting: url = " + url); + + // 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)) { + if (mWriteRetryTimer == NULL) { + mWriteRetryTimer = new TQTimer(this); + connect( mWriteRetryTimer, TQT_SIGNAL(timeout()), TQT_SLOT(doSave()) ); + } + mWriteRetryTimer->start(1000, TRUE); + 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(); + + 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->tasksUrl = tasksUrl; + t->journalsUrl = journalsUrl; + t->added = ""; + t->changed = ""; + t->deleted = ""; + t->tasksAdded = ""; + t->tasksChanged = ""; + t->tasksDeleted = ""; + t->journalsAdded = ""; + t->journalsChanged = ""; + t->journalsDeleted = ""; + if (getICalString(currentIncidence).contains("BEGIN:VEVENT") > 0) + t->added = getICalString(currentIncidence); + else if (getICalString(currentIncidence).contains("BEGIN:VTODO") > 0) + t->tasksAdded = getICalString(currentIncidence); + else if (getICalString(currentIncidence).contains("BEGIN:VJOURNAL") > 0) + t->journalsAdded = getICalString(currentIncidence); + + writingQueuePush(t); + } + + for( it = changed.begin(); it != changed.end(); ++it ) { + WritingTask *t = new WritingTask; + + currentIncidence.clear(); + currentIncidence.append(*it); + + t->url = url; + t->tasksUrl = tasksUrl; + t->added = ""; + t->changed = ""; + t->deleted = ""; + t->tasksAdded = ""; + t->tasksChanged = ""; + t->tasksDeleted = ""; + t->journalsAdded = ""; + t->journalsChanged = ""; + t->journalsDeleted = ""; + + if (getICalString(currentIncidence).contains("BEGIN:VEVENT") > 0) + t->changed = getICalString(currentIncidence); + else if (getICalString(currentIncidence).contains("BEGIN:VTODO") > 0) + t->tasksChanged = getICalString(currentIncidence); + else if (getICalString(currentIncidence).contains("BEGIN:VJOURNAL") > 0) + t->journalsChanged = getICalString(currentIncidence); + + writingQueuePush(t); + } + + for( it = deleted.begin(); it != deleted.end(); ++it ) { + WritingTask *t = new WritingTask; + + currentIncidence.clear(); + currentIncidence.append(*it); + + t->url = url; + t->tasksUrl = tasksUrl; + t->added = ""; + t->changed = ""; + t->deleted = ""; + t->tasksAdded = ""; + t->tasksChanged = ""; + t->tasksDeleted = ""; + t->journalsAdded = ""; + t->journalsChanged = ""; + t->journalsDeleted = ""; + + if (getICalString(currentIncidence).contains("BEGIN:VEVENT") > 0) + t->deleted = getICalString(currentIncidence); + else if (getICalString(currentIncidence).contains("BEGIN:VTODO") > 0) + t->tasksDeleted = getICalString(currentIncidence); + else if (getICalString(currentIncidence).contains("BEGIN:VJOURNALS") > 0) + t->journalsDeleted = getICalString(currentIncidence); + + writingQueuePush(t); + } + + return true; +} + +void ResourceCalDav::writingFinished() { + log("writing finished"); + + if (!mWriter) { + log("mWriter is NULL"); + return; + } + + if (mWriter->error() && (abs(mWriter->errorNumber()) != 207)) { + if (mWriter->errorNumber() == -401) { + if (NULL != mPrefs) { + TQCString newpass; + if (KPasswordDialog::getPassword (newpass, TQString("<b>") + i18n("Remote authorization required") + TQString("</b><p>") + i18n("Please input the password for") + TQString(" ") + mPrefs->getusername(), NULL) != 1) { + log("write error: " + mWriter->errorString()); + saveError(TQString("[%1] ").arg(abs(mWriter->errorNumber())) + mWriter->errorString()); + } + else { + // Set new password and try again + mPrefs->setPassword(TQString(newpass)); + startWriting(mPrefs->getFullUrl(), mPrefs->getFullTasksUrl(), mPrefs->getFullJournalsUrl()); + } + } + else { + log("write error: " + mWriter->errorString()); + saveError(TQString("[%1] ").arg(abs(mWriter->errorNumber())) + mWriter->errorString()); + } + } + else { + log("write error: " + mWriter->errorString()); + saveError(TQString("[%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 + TQTimer::singleShot( 3000, this, TQT_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. + mWriter->terminate(); + mWriter->wait(TERMINATION_WAITING_TIME); + mWritingQueueReady = true; + updateProgressBar(1); + writingQueuePop(); +} + +#include "resource.moc" + +// EOF ======================================================================== |