/* This file is part of libkpimexchange. Copyright (c) 2002 Jan-Pascal van Best This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dateset.h" #include "exchangeaccount.h" #include "exchangeclient.h" #include "exchangemonitor.h" #include "resourceexchange.h" #include "resourceexchangeconfig.h" using namespace KCal; using namespace KPIM; typedef KRES::PluginFactory ExchangeFactory; // FIXME: Use K_EXPORT_COMPONENT_FACTORY( resourcecalendarexchange, ExchangeFactory ); here // Problem: How to insert the catalogue! extern "C" { void* init_resourcecalendarexchange() { KGlobal::locale()->insertCatalogue( "kres_exchange" ); return new ExchangeFactory; } } class ResourceExchange::EventInfo { public: KCal::Event* event; KURL url; long updateWatch; }; ResourceExchange::ResourceExchange( const KConfig *config ) : ResourceCalendar( config ), mClient(0), mMonitor(0), mCache(0), mDates(0), mEventDates(0), mCacheDates(0) { mLock = new KABC::LockNull( true ); mTimeZoneId = TQString::tqfromLatin1( "UTC" ); kdDebug() << "Creating ResourceExchange" << endl; if (config ) { mAccount = new ExchangeAccount( config->readEntry( "ExchangeHost" ), config->readEntry( "ExchangePort" ), config->readEntry( "ExchangeAccount" ), KStringHandler::obscure( config->readEntry( "ExchangePassword" ) ), config->readEntry( "ExchangeMailbox" ) ); mCachedSeconds = config->readNumEntry( "ExchangeCacheTimeout", 600 ); mAutoMailbox = config->readBoolEntry( "ExchangeAutoMailbox", true ); } else { setResourceName( i18n( "Exchange Server" ) ); mAccount = new ExchangeAccount( "", "", "", "" ); mCachedSeconds = 600; } } ResourceExchange::~ResourceExchange() { kdDebug() << "Destructing ResourceExchange" << endl; close(); delete mAccount; mAccount = 0; } void ResourceExchange::writeConfig( KConfig* config ) { ResourceCalendar::writeConfig( config ); config->writeEntry( "ExchangeHost", mAccount->host() ); config->writeEntry( "ExchangePort", mAccount->port() ); config->writeEntry( "ExchangeAccount", mAccount->account() ); config->writeEntry( "ExchangeMailbox", mAccount->mailbox() ); config->writeEntry( "ExchangePassword", KStringHandler::obscure( mAccount->password() ) ); config->writeEntry( "ExchangeCacheTimeout", mCachedSeconds ); config->writeEntry( "ExchangeAutoMailbox", mAutoMailbox ); } bool ResourceExchange::doOpen() { kdDebug() << "ResourceExchange::doOpen()" << endl; mClient = new ExchangeClient( mAccount, mTimeZoneId ); connect( mClient, TQT_SIGNAL( downloadFinished( int, const TQString & ) ), TQT_SLOT( slotDownloadFinished( int, const TQString & ) ) ); connect( mClient, TQT_SIGNAL( event( KCal::Event *, const KURL & ) ), TQT_SLOT( downloadedEvent( KCal::Event *, const KURL & ) ) ); #if 0 kdDebug() << "Creating monitor" << endl; TQHostAddress ip; ip.setAddress( mAccount->host() ); mMonitor = new ExchangeMonitor( mAccount, ExchangeMonitor::CallBack, ip ); connect( mMonitor, TQT_SIGNAL(notify( const TQValueList& , const TQValueList& )), this, TQT_SLOT(slotMonitorNotify( const TQValueList& , const TQValueList& )) ); connect( mMonitor, TQT_SIGNAL(error(int , const TQString&)), this, TQT_SLOT(slotMonitorError(int , const TQString&)) ); mMonitor->addWatch( mAccount->calendarURL(), ExchangeMonitor::UpdateNewMember, 1 ); #endif TQWidgetList* widgets = TQApplication::tqtopLevelWidgets(); if ( !widgets->isEmpty() ) mClient->setWindow( widgets->first() ); delete widgets; mDates = new DateSet(); mEventDates = new TQMap(); mCacheDates = new TQMap(); mCache = new CalendarLocal( mTimeZoneId ); // mOldestDate = 0L; // mNewestDate = 0L; // FIXME: check if server exists, account is OK, etc. return true; } void ResourceExchange::doClose() { kdDebug() << "ResourceExchange::doClose()" << endl; // delete mNewestDate; // delete mOldestDate; delete mDates; mDates = 0; // delete mMonitor; mMonitor = 0; delete mClient; mClient = 0; delete mEventDates; mEventDates = 0; delete mCacheDates; mCacheDates = 0; if (mCache) { mCache->close(); delete mCache; mCache = 0; } // setModified( false ); } bool ResourceExchange::doLoad() { return true; } bool ResourceExchange::doSave() { kdDebug() << "ResourceExchange::save() " << mChangedIncidences.count() << endl; Incidence::List::Iterator it = mChangedIncidences.begin(); while( it != mChangedIncidences.end() ) { if ( (*it)->type() == "Event" ) { if ( uploadEvent( static_cast( *it ) ) ) { it = mChangedIncidences.remove( it ); } else { kdError() << "ResourceExchange::save(): upload failed." << endl; ++it; } } else { kdError() << "ResourceExchange::save() type not handled: " << (*it)->type() << endl; ++it; } } return true; } KABC::Lock *ResourceExchange::lock() { return mLock; } void ResourceExchange::slotMonitorNotify( const TQValueList& IDs, const TQValueList& urls ) { kdDebug() << "ResourceExchange::slotMonitorNotify()" << endl; TQString result; KPIM::ExchangeMonitor::IDList::ConstIterator it; for ( it = IDs.begin(); it != IDs.end(); ++it ) { if ( it == IDs.begin() ) result += TQString::number( (*it) ); else result += "," + TQString::number( (*it) ); } kdDebug() << "Got signals for " << result << endl; TQValueList::ConstIterator it2; for ( it2 = urls.begin(); it2 != urls.end(); ++it2 ) { kdDebug() << "URL: " << (*it2).prettyURL() << endl; } /* Now find out what happened: * One or more of the following: * 1. Event added in period that we think we have cached * 2. Event deleted that we have in cache * 3. Event modified that we have in cache * 4. Something else happened that isn't relevant to us * Update cache, then notify whoever's watching us * We may be able to find (1) and (3) by looking at the * DAV:getlastmodified property * (2) is trickier: we might have to resort to checking * all uids in the cache * Or: put monitors on every event in the cache, so that * we know when one gets deleted or modified * Only look for new events using the global monitor */ } void ResourceExchange::slotMonitorError( int errorCode, const TQString& moreInfo ) { kdError() << "Ignoring error from Exchange monitor, code=" << errorCode << "; more info: " << moreInfo << endl; } bool ResourceExchange::addEvent( Event *event ) { return addEvent( event, TQString() ); } bool ResourceExchange::addEvent( Event *event, const TQString &subresource ) { Q_UNUSED( subresource ); //subresources are not supported if( !mCache ) return false; kdDebug() << "ResourceExchange::addEvent" << endl; // FIXME: first check of upload finished successfully, only then // add to cache mCache->addEvent( event ); uploadEvent( event ); // insertEvent( event ); event->registerObserver( this ); // setModified( true ); return true; } bool ResourceExchange::uploadEvent( Event *event ) { mClient->uploadSynchronous( event ); return true; } bool ResourceExchange::deleteEvent(Event *event) { if ( !mCache ) return false; kdDebug(5800) << "ResourceExchange::deleteEvent" << endl; mClient->removeSynchronous( event ); // This also frees the event return mCache->deleteEvent( event ); // setModified( true ); } void ResourceExchange::changeIncidence( Incidence *incidence ) { kdDebug() << "ResourceExchange::changeIncidence(): " << incidence->summary() << endl; if ( mChangedIncidences.find( incidence ) == mChangedIncidences.end() ) { mChangedIncidences.append( incidence ); } } Event *ResourceExchange::event( const TQString &uid ) { kdDebug(5800) << "ResourceExchange::event(): " << uid << endl; // FIXME: Look in exchange server for uid! Event *event = 0; if ( mCache ) event = mCache->event( uid ); return event; } void ResourceExchange::subscribeEvents( const TQDate &start, const TQDate &end ) { kdDebug(5800) << "ResourceExchange::subscribeEvents()" << endl; // FIXME: possible race condition if several subscribe events are run close // to each other mClient->download( start, end, false ); } void ResourceExchange::downloadedEvent( KCal::Event *event, const KURL &url ) { kdDebug() << "Downloaded event: " << event->summary() << " from url " << url.prettyURL() << endl; // FIXME: add watches to the monitor for these events // KURL url = // mMonitor->addWatch( url, KPIM::ExchangeMonitor::Update, 0 ); // emit eventsAdded( events ); } void ResourceExchange::slotDownloadFinished( int result, const TQString &moreinfo ) { kdDebug() << "ResourceExchange::downloadFinished" << endl; if ( result != KPIM::ExchangeClient::ResultOK ) { // Do something useful with the error report kdError() << "ResourceExchange::slotDownloadFinished(): error " << result << ": " << moreinfo << endl; } } void ResourceExchange::unsubscribeEvents( const TQDate &/*start*/, const TQDate &/*end*/ ) { kdDebug() << "ResourceExchange::unsubscribeEvents()" << endl; } bool ResourceExchange::addTodo( Todo *todo ) { return addTodo( todo, TQString() ); } bool ResourceExchange::addTodo( Todo */*todo*/, const TQString &subresource ) { Q_UNUSED( subresource ); //subresources are not supported // This resource doesn't handle todos yet! return false; /* if( !mCache) return false; mCache->addTodo( todo ); todo->registerObserver( this ); // setModified( true ); return true;*/ } bool ResourceExchange::deleteTodo(Todo */*todo*/) { // We don't handle todos yet // if( !mCache ) return false; // mCache->deleteTodo( todo ); // setModified( true ); } Todo::List ResourceExchange::rawTodos( TodoSortField /*sortField*/, SortDirection /*sortDirection*/ ) { // We don't handle todos yet return Todo::List(); /* Todo::List list; if ( mCache ) list = mCache->rawTodos( sortField, sortDirection ); return list;*/ } Todo *ResourceExchange::todo( const TQString &/*uid*/ ) { // We don't handle todos yet return 0; /* if ( !mCache ) return 0; else return mCache->todo( uid );*/ } Todo::List ResourceExchange::rawTodosForDate( const TQDate &/*date*/ ) { Todo::List list; // We don't handle todos yet /* if ( mCache ) list = mCache->rawTodosForDate( date );*/ return list; } Alarm::List ResourceExchange::alarmsTo( const TQDateTime &to ) { Alarm::List list; if ( mCache ) list = mCache->alarmsTo( to ); return list; } /* Invoked by korgac when checking alarms. Always updates the cache. */ Alarm::List ResourceExchange::alarms( const TQDateTime &from, const TQDateTime &to ) { kdDebug(5800) << "ResourceExchange::alarms(" << from.toString() << " - " << to.toString() << ")\n"; Alarm::List list; TQDate start = from.date(); TQDate end = to.date(); if ( mCache ) { /* Clear the cache */ Event::List oldEvents = mCache->rawEvents( start, end, false ); Event::List::ConstIterator it; for( it = oldEvents.begin(); it != oldEvents.end(); ++it ) { mCache->deleteEvent( *it ); } /* Fetch events */ mClient->downloadSynchronous( mCache, start, end, false ); list = mCache->alarms( from, to ); } return list; } /****************************** PROTECTED METHODS ****************************/ // after changes are made to an event, this should be called. void ResourceExchange::incidenceUpdated( IncidenceBase *incidence ) { Event* event = dynamic_cast( incidence ); if ( event ) { kdDebug() << "Event updated, resubmit to server..." << endl; uploadEvent( event ); } // setModified( true ); } // this function will take a VEvent and insert it into the event // dictionary for the ResourceExchange. If there is no list of events for that // particular location in the dictionary, a new one will be created. /* void ResourceExchange::insertEvent(const Event *anEvent) { kdDebug() << "ResourceExchange::insertEvent" << endl; } */ // taking a TQDate, this function will look for an eventlist in the dict // with that date attached - Event::List ResourceExchange::rawEventsForDate( const TQDate &qd, EventSortField sortField, SortDirection sortDirection ) { if (!mCache) return Event::List(); // If the events for this date are not in the cache, or if they are old, // get them again TQDateTime now = TQDateTime::tqcurrentDateTime(); // kdDebug() << "Now is " << now.toString() << endl; // kdDebug() << "mDates: " << mDates << endl; TQDate start = TQDate( qd.year(), qd.month(), 1 ); // First day of month if ( mDates && ( !mDates->contains( start ) || (*mCacheDates)[start].secsTo( now ) > mCachedSeconds ) ) { TQDate end = start.addMonths( 1 ).addDays( -1 ); // Last day of month // Get events that occur in this period from the cache Event::List oldEvents = mCache->rawEvents( start, end, false ); // And remove them all Event::List::ConstIterator it; for( it = oldEvents.begin(); it != oldEvents.end(); ++it ) { mCache->deleteEvent( *it ); } // FIXME: This is needed for the hack below: Event::List eventsBefore = mCache->rawEvents(); kdDebug() << "Reading events for month of " << TQString(start.toString()) << endl; mClient->downloadSynchronous( mCache, start, end, true ); // Show progress dialog // FIXME: This is a terrible hack! We need to install the observer for // newly downloaded events.However, downloading is done by // mClient->downloadSynchronous, where we don't have the pointer to this // available... On the other hand, here we don't really know which events // are really new. Event::List eventsAfter = mCache->rawEvents(); for ( it = eventsAfter.begin(); it != eventsAfter.end(); ++it ) { if ( eventsBefore.find( *it ) == eventsBefore.end() ) { // it's a new event downloaded by downloadSynchronous -> install observer (*it)->registerObserver( this ); } } mDates->add( start ); mCacheDates->insert( start, now ); } // Events are safely in the cache now, return them from cache Event::List events; if ( mCache ) events = mCache->rawEventsForDate( qd, sortField, sortDirection ); // kdDebug() << "Found " << events.count() << " events." << endl; return events; } Event::List ResourceExchange::rawEvents( const TQDate &start, const TQDate &end, bool inclusive ) { kdDebug() << "ResourceExchange::rawEvents(start,end,inclusive)" << endl; if (!mCache) return Event::List(); return mCache->rawEvents( start, end, inclusive ); } Event::List ResourceExchange::rawEventsForDate(const TQDateTime &qdt) { kdDebug() << "ResourceExchange::rawEventsForDate(qdt)" << endl; return rawEventsForDate( qdt.date() ); } Event::List ResourceExchange::rawEvents( EventSortField sortField, SortDirection sortDirection ) { kdDebug() << "ResourceExchange::rawEvents()" << endl; if (!mCache) return Event::List(); return mCache->rawEvents( sortField, sortDirection ); } bool ResourceExchange::addJournal( Journal *journal ) { return addJournal( journal, TQString() ); } bool ResourceExchange::addJournal( Journal */*journal*/, const TQString &subresource ) { Q_UNUSED( subresource ); //subresources are not supported // This resource doesn't handle journals yet return false; /* kdDebug(5800) << "Adding Journal on " << journal->dtStart().toString() << endl; if (mCache) { mCache->addJournal( journal ); journal->registerObserver( this ); // setModified( true ); } return true;*/ } bool ResourceExchange::deleteJournal(Journal */*journal*/) { // Wedon't handle journals yet // if( !mCache ) return false; // mCache->deleteJournal( journal ); // setModified( true ); } Journal::List ResourceExchange::journals(const TQDate &/*date*/) { // We don't handle journals yet return Journal::List(); /* Journal::List list; if ( mCache ) list = mCache->journals( date ); return list;*/ } Journal *ResourceExchange::journal(const TQString &/*uid*/) { // We don't handle journals yet return 0; /* if( !mCache ) return 0; return mCache->journal( uid );*/ } Journal::List ResourceExchange::rawJournals( JournalSortField /*sortField*/, SortDirection /*sortDirection*/ ) { // We don't handle journals yet return Journal::List(); /* Journal::List list; if ( mCache ) list = mCache->rawJournals( sortField, sortDirection ); return list;*/ } Journal::List ResourceExchange::rawJournalsForDate( const TQDate &/*date*/ ) { // We don't handle journals yet return Journal::List(); /* Journal::List list; if ( mCache ) list = mCache->rawJournalsForDate( date ); return list;*/ } void ResourceExchange::setTimeZoneId( const TQString &tzid ) { mTimeZoneId = tzid; if ( mCache ) mCache->setTimeZoneId( tzid ); if ( mClient ) mClient->setTimeZoneId( tzid ); } #include "resourceexchange.moc"