[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-pim
Subject:    [Kde-pim] [PATCH] Displaying VCALENDAR events
From:       Allen Winter <winterz () verizon ! net>
Date:       2004-03-23 21:30:25
Message-ID: 200403231630.25303.winterz () verizon ! net
[Download RAW message or body]

Howdy,

OK, attached is a patch for kogroupware.cpp that reworks the calendar 
meeting invitation/replies and task assignments/replies as we've been
discussing recently.  Please review.  I don't do groupware stuff in my
little two-person office so I can't test.

The new Request formats are:
  You have been invited to this meeting.<br>
  Start Time: 03/23/04  2:00pm<br>
  End Time:  03/23/04  4:00pm<br>
  Duration:    2 hours<br>

  You have been assigned this task:<br>
  Summary:    Fix all bugs in our software<br>
  Description: You will fix all the bugs before the end of the day or you're fired!<br>

The new Reply formats are:
  Sender <b>accepts/tentatively accepts/declines</b> this meeting invitation.
  Start Time: 03/23/04  2:00pm<br>
  End Time:  03/23/04  4:00pm<br>
  Duration:    2 hours<br>

  Sender <accepts/tentatively accepts/declines</b> this task:<br>
  Summary:    Fix all bugs in our software<br>
  Description: You will fix all the bugs before the end of the day or you're fired!<br>

Regards,
Allen

["kogroupware.cpp" (text/x-c++src)]

/*
  This file is part of the Groupware/KOrganizer integration.

  Requires the Qt and KDE widget libraries, available at no cost at
  http://www.trolltech.com and http://www.kde.org respectively

  Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
        <info@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; either version 2 of the License, or
  (at your option) any later version.

  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., 59 Temple Place - Suite 330, Boston,
  MA  02111-1307, USA.

  In addition, as a special exception, the copyright holders give
  permission to link the code of this program with any edition of
  the Qt library by Trolltech AS, Norway (or with modified versions
  of Qt that use the same license as Qt), 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
  Qt.  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.
*/

#include "kogroupware.h"

#include "freebusymanager.h"
#include "koprefs.h"
#include "calendarview.h"
#include "mailscheduler.h"
#include "kogroupwareincomingdialog.h"
#include "koviewmanager.h"

#include <ktnef/ktnefparser.h>
#include <ktnef/ktnefmessage.h>
#include <ktnef/ktnefdefs.h>

#include <libkcal/incidencebase.h>
#include <libkcal/attendee.h>
#include <libkcal/freebusy.h>
#include <libkcal/journal.h>
#include <libkcal/calendarlocal.h>
#include <libkcal/icalformat.h>

#include <kabc/phonenumber.h>
#include <kabc/vcardconverter.h>

#include <kdebug.h>
#include <kmessagebox.h>
#include <ktempfile.h>
#include <kio/netaccess.h>
#include <kapplication.h>
#include <kconfig.h>
#include <dcopclient.h>
#include <dcopref.h>

#include <qfile.h>
#include <qbuffer.h>
#include <qregexp.h>

#include <mimelib/enum.h>

#include <stdlib.h>
#include <time.h>

FreeBusyManager *KOGroupware::mFreeBusyManager = 0;

KOGroupware *KOGroupware::mInstance = 0;

KOGroupware *KOGroupware::create( CalendarView *view,
                                  KCal::Calendar *calendar )
{
  if( !mInstance )
    mInstance = new KOGroupware( view, calendar );
  return mInstance;
}

KOGroupware *KOGroupware::instance()
{
  // Doesn't create, that is the task of create()
  Q_ASSERT( mInstance );
  return mInstance;
}


KOGroupware::KOGroupware( CalendarView* view, KCal::Calendar* calendar )
  : QObject( 0, "kmgroupware_instance" )
{
  mView = view;
  mCalendar = calendar;
}

FreeBusyManager *KOGroupware::freeBusyManager()
{
  if ( !mFreeBusyManager ) {
    mFreeBusyManager = new FreeBusyManager( this, "freebusymanager" );
    mFreeBusyManager->setCalendar( mCalendar );
    connect( mCalendar, SIGNAL( calendarChanged() ),
             mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
  }

  return mFreeBusyManager;
}


static void vPartMicroParser( const QString& str, QString& s )
{
  QString line;
  uint len = str.length();

  for( uint i=0; i<len; ++i) {
    if( str[i] == '\r' || str[i] == '\n' ) {
      if( str[i] == '\r' )
        ++i;
      if( i+1 < len && str[i+1] == ' ' ) {
        // Found a continuation line, skip it's leading blanc
        ++i;
      } else {
        // Found a logical line end, process the line
        if( line.startsWith( s ) ) {
          s = line.mid( s.length() + 1 );
          return;
        }
        line = "";
      }
    } else {
      line += str[i];
    }
  }
  s.truncate(0);
}

static void string2HTML( QString& str ) {
  str.replace( QChar( '&' ), "&amp;" );
  str.replace( QChar( '<' ), "&lt;" );
  str.replace( QChar( '>' ), "&gt;" );
  str.replace( QChar( '\"' ), "&quot;" );
  str.replace( "\\n", "<br>" );
  str.replace( "\\,", "," );
}

static QString meetingDetails( Incidence* incidence, Event* event )
{
  QString html;

  QString sLocation = i18n( "Location unspecified" );
  if ( incidence ) {
    if ( ! incidence->location().isEmpty() ) {
      sLocation = incidence->location();
      string2HTML( sLocation );
    }
  }

  // Meeting Location
  html  =   i18n( "Where:      %1<br>" ).arg( sLocation );

  // Meeting Start Time
  html +=   i18n( "Start Time: %1 " ).arg( event->dtStartDateStr() );
  if ( ! event->doesFloat() ) {
    html += event->dtStartTimeStr();
  } else {
    html += i18n( "(no time specified)" );
  }
  html += "<br>";

  // Meeting End Time
  html +=   i18n( "End Time:   " );
  if ( event->hasEndDate() ) {
    html += event->dtEndDateStr();
    if ( ! event->doesFloat() ) {
      html += event->dtEndTimeStr();
    } else {
      html += i18n( "(no time specified)" );
    }
    html += "<br>";
  } else {
    html += i18n( "(no date/time specified)<br>" );
  }

  // Meeting Duration
  if ( ! event->doesFloat() && event->hasEndDate() ) {
    QTime sDuration, t;
    int secs = event->dtStart().secsTo( event->dtEnd() );
    t = sDuration.addSecs( secs );
    html += i18n( "Duration:   " );
    if ( t.hour() > 0 ) {
      html += t.toString( "h" );
      html += i18n( " hours " );
    }
    if ( t.minute() > 0 ) {
      html += t.toString( "m" );
      html += i18n( " mins " );
    }
    html += "<br>";
  }

  return html;
}

static QString taskDetails( Incidence* incidence )
{
  QString html;

  QString sSummary = i18n( "Summary unspecified" );
  QString sDescr = i18n( "Description unspecified" );
  if ( incidence ) {
    if ( ! incidence->summary().isEmpty() ) {
      sSummary = incidence->summary();
      string2HTML( sSummary );
    }
    if ( ! incidence->description().isEmpty() ) {
      sDescr = incidence->description();
      string2HTML( sDescr );
    }
  }

  html =  i18n( "Summary:     %1<br>" ).arg( sSummary );
  html += i18n( "Description: %1<br>" ).arg( sDescr );

  return html;
}

QString KOGroupware::formatICal( const QString& iCal )
{
  KCal::CalendarLocal cl;
  ICalFormat format;
  format.fromString( &cl, iCal );

  // Make a shallow copy of the event and task lists
  if( cl.events().count() == 0 && cl.todos().count() == 0 ) {
    kdDebug(5850) << "No iCal in this one\n";
    return QString();
  }

  // Parse the first event out of the vcal
  // TODO: Is it legal to have several events/todos per mail part?
  Incidence* incidence = 0;
  Event* event = 0;
  Todo* todo = 0;
  if( cl.events().count() > 0 )
    incidence = event = cl.events().first();
  else
    incidence = todo = cl.todos().first();

  // TODO: Actually the scheduler needs to do this:
  QString sMethod; // = incidence->method();
  // TODO: This is a temporary workaround to get the method
  sMethod = "METHOD";
  vPartMicroParser( iCal, sMethod );
  sMethod = sMethod.lower();

  // First make the text of the message
  QString html;
  if( sMethod == "request" ) {
    if( event ) {
      html = i18n( "You have been invited to this meeting.<br>" );
      html += meetingDetails( incidence, event );
    } else {
      html = i18n( "You have been assigned this task:<br>" );
      html += taskDetails( incidence );
    }
  } else if( sMethod == "reply" ) {
    Attendee::List attendees = incidence->attendees();
    if( attendees.count() == 0 ) {
      kdDebug(5850) << "No attendees in the iCal reply!\n";
      return QString();
    }
    if( attendees.count() != 1 )
      kdDebug(5850) << "Warning: attendeecount in the reply should be 1 "
                    << "but is " << attendees.count() << endl;
    Attendee* attendee = *attendees.begin();

    switch( attendee->status() ) {
    case Attendee::Accepted:
      if( event ) {
        html = i18n( "Sender <b>accepts</b> this meeting invitation.<br>" );
        html += meetingDetails( incidence, event );
      } else {
        html = i18n( "Sender <b>accepts</b> this task.<br>" );
        html += taskDetails( incidence );
      }
      break;

    case Attendee::Tentative:
      if( event ) {
        html = i18n( "Sender <b>tentatively accepts</b> this "
                     "meeting invitation.<br>" );
        html += meetingDetails( incidence, event );
      } else {
        html = i18n( "Sender <b>tentatively accepts</b> this task.<br>" );
        html += taskDetails( incidence );
      }
      break;

    case Attendee::Declined:
      if( event ) {
        html = i18n( "Sender <b>declines</b> this meeting invitation.<br>" );
        html += meetingDetails( incidence, event );
      } else {
        html = i18n( "Sender <b>declines</b> this task.<br>" );
        html += taskDetails( incidence );
      }
      break;

    default:
      if( event ) {
        html = i18n( "Unknown response to this meeting invitation.<br>" );
        html += meetingDetails( incidence, event );
      } else {
        html = i18n( "Unknown response to this task.<br>" );
        html += taskDetails( incidence );
      }
    }
  } else if( sMethod == "cancel" ) {
    if( event ) {
      html = i18n( "This meeting has been canceled.<br>" );
      html += meetingDetails( incidence, event );
    } else {
      html = i18n( "This task was canceled.<br>" );
      html += taskDetails( incidence );
    }
  }

  // Add the groupware URLs
  html += "<br>&nbsp;<br>&nbsp;<br>";
  html += "<table border=\"0\" cellspacing=\"0\"><tr><td>&nbsp;</td><td>";
  if( sMethod == "request" || sMethod == "update" ) {
    // Accept
    html += "<a href=\"kmail:groupware_request_accept\"><b>";
    html += i18n( "[Accept]" );
    html += "</b></a></td><td> &nbsp; </td><td>";
    // Accept conditionally
    html += "<a href=\"kmail:groupware_request_accept conditionally\"><b>";
    html += i18n( "Accept conditionally", "[Accept cond.]" );
    html += "</b></a></td><td> &nbsp; </td><td>";
    // Decline
    html += "<a href=\"kmail:groupware_request_decline\"><b>";
    html += i18n( "[Decline]" );
    if( event ) {
      // Check my calendar...
      html += "</b></a></td><td> &nbsp; </td><td>";
      html += "<a href=\"kmail:groupware_request_check\"><b>";
      html += i18n("[Check my calendar...]" );
    }
  } else if( sMethod == "reply" ) {
    // Enter this into my calendar
    html += "<a href=\"kmail:groupware_reply#%1\"><b>";
    if( event )
      html += i18n( "[Enter this into my calendar]" );
    else
      html += i18n( "[Enter this into my task list]" );
  } else if( sMethod == "cancel" ) {
    // Cancel event from my calendar
    html += "<a href=\"kmail:groupware_cancel\"><b>";
    html += i18n( "[Remove this from my calendar]" );
  }
  html += "</b></a></td></tr></table>";

  QString sDescr = incidence->description();
  if( ( sMethod == "request" || sMethod == "cancel" ) && !sDescr.isEmpty() ) {
    string2HTML( sDescr );
    html += "<br>&nbsp;<br>&nbsp;<br><u>" + i18n("Description:")
      + "</u><br><table border=\"0\"><tr><td>&nbsp;</td><td>";
    html += sDescr + "</td></tr></table>";
  }
  html += "&nbsp;<br>&nbsp;<br><u>" + i18n( "Original message:" ) + "</u>";

  return html;
}

QString KOGroupware::formatTNEF( const QByteArray& tnef )
{
  QString vPart = msTNEFToVPart( tnef );
  QString iCal = formatICal( vPart );
  if( !iCal.isEmpty() )
    return iCal;
  return vPart;
}

bool KOGroupware::incomingEventRequest( const QString& request,
                                        const QString& receiver,
                                        const QString& vCalIn )
{
  // This was the code to accept a choice from KMail. Needs porting
  EventState state;
  if( request == "accept" )
    state = Accepted;
  else if( request == "accept conditionally" )
    state = ConditionallyAccepted;
  else if( request == "decline" )
    state = Declined;
  else if( request == "check" )
    state = Request;
  else
    return false;

  // Parse the event request into a ScheduleMessage; this needs to
  // be done in any case.
  KCal::ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar,
                                                                 vCalIn );
  if( message ) {
    kdDebug(5850) << "KOGroupware::incomingEventRequest: got message '"
                  << vCalIn << "'" << endl;
  } else {
    QString errorMessage;
    if( mFormat.exception() ) {
      errorMessage = mFormat.exception()->message();
    }
    kdDebug(5850) << "KOGroupware::incomingEventRequest() Error parsing "
                  << "message: " << errorMessage << endl;
    // If the message was broken, there's nothing we can do.
    return false;
  }

  KCal::Incidence* event = dynamic_cast<KCal::Incidence*>( message->event() );
  Q_ASSERT( event );
  if( !event ) { // something bad happened, just to be safe
    kdDebug(5850) << "KOGroupware::incomingEventRequest(): Not an event???\n";
    return false;
  }

  // Now check if the event needs to be accepted or if this is
  // already done.
  if( state == Request ) {
    // Need to accept, present it to the user
    KOGroupwareIncomingDialog dlg( event );
    int ret = dlg.exec();
    if( ret == QDialog::Rejected ) {
      // User declined to make a choice, we can't send a vCal back
      kdDebug(5850) << "KOGroupware::incomingEventRequest(): User canceled\n";
      return false;
    }

    if( dlg.isDeclined() )
      state = Declined;
    else if( dlg.isConditionallyAccepted() )
      state = ConditionallyAccepted;
    else if( dlg.isAccepted() )
      state = Accepted;
    else
      kdDebug(5850) << "KOGroupware::incomingEventRequest(): unknown "
                                        << "event request state" << endl;
  }

  // If the event has an alarm, make sure it doesn't have a negative time.
  // This is yet another OL workaround
#if 0
  // PENDING(bo): Disabled for now, until I figure out how the old offset
  // matches the two new offsets
  Alarm::List alarms = event->alarms();
  Alarm::List::ConstIterator it;
  for ( it = alarms.begin(); it != alarms.end(); ++it) {
    if ( (*it)->hasTime() ) {
      QDateTime t = (*it)->time();
      int offset = event->dtStart().secsTo( t );
      if( offset > 0 )
       // PENDING(Bo): Not implemented yet
       kdDebug(5850) << "Warning: Alarm fires after the event\n";
    } else {
      int offset = (*it)->offset().asSeconds();
      if( offset > 0 ) {
       // This number should be negative so the alarm fires before the event
       Duration d( -offset );
       (*it)->setOffset( d );
      }
    }
  }
#endif

  // Enter the event into the calendar. We just create a
  // Scheduler, because all the code we need is already there. We
  // take an MailScheduler, because we need a concrete one, but we
  // really only want code from Scheduler.
  // PENDING(kalle) Handle tentative acceptance differently.
  KCal::MailScheduler scheduler( mCalendar );
  if( state == Accepted || state == ConditionallyAccepted ) {
    scheduler.acceptTransaction( event,
                                 (KCal::Scheduler::Method)message->method(),
                                 message->status() );
    mView->updateView();
  }

  KCal::Attendee::List attendees = event->attendees();
  KCal::Attendee::List::ConstIterator it;
  KCal::Attendee* myself = 0;
  // Find myself, there will always be all attendees listed, even if
  // only I need to answer it.
  for ( it = attendees.begin(); it != attendees.end(); ++it ) {
    if( (*it)->email() == receiver ) {
      // We are the current one, and even the receiver, note
      // this and quit searching.
      myself = (*it);
      break;
    }

    if( (*it)->email() == KOPrefs::instance()->email() ) {
      // If we are the current one, note that. Still continue to
      // search in case we find the receiver himself.
      myself = (*it);
    }
  }

  Q_ASSERT( myself );

  KCal::Attendee* newMyself = 0;
  if( myself ) {
    switch( state ) {
    case Accepted:
      myself->setStatus( KCal::Attendee::Accepted );
      break;
    case ConditionallyAccepted:
      myself->setStatus( KCal::Attendee::Tentative );
      break;
    case Declined:
      myself->setStatus( KCal::Attendee::Declined );
      break;
    default:
      ;
    };

    // No more request response
    myself->setRSVP(false);

    event->updated();

    newMyself = new KCal::Attendee( myself->name(),
                                    receiver.isEmpty() ?
                                    myself->email() :
                                    receiver,
                                    myself->RSVP(),
                                    myself->status(),
                                    myself->role(),
                                    myself->uid() );
  }

  event->updated();

  // Send back the answer; construct it on the base of state. We
  // make a clone of the event since we need to manipulate it here.
  // NOTE: This contains a workaround around a libkcal bug: REPLY
  // vCals may not have more than one ATTENDEE (as libical correctly
  // specifies), but libkcal always writes out all the ATTENDEEs,
  // thus producing invalid vCals. We make a clone of the vEvent
  // here and remove all attendees except ourselves.
  Incidence* newIncidence = event->clone();
  Event* newEvent = static_cast<KCal::Event*>( newIncidence );

#if 0
  // OL compatibility thing. To be ported.
  // The problem is that OL is braindead when it comes to receiving
  // events that mention alarms. So strip them before sending to OL
  // people
  bool stripAlarms = false;
  emit getStripAlarmsForSending( stripAlarms );
  if( stripAlarms )
    // Strip alarms from the send
    newEvent->clearAlarms();
#endif

  newEvent->clearAttendees();
  if( newMyself )
    newEvent->addAttendee( newMyself );

  // Create the outgoing vCal
  QString messageText =
    mFormat.createScheduleMessage( newEvent, KCal::Scheduler::Reply );
  scheduler.performTransaction( newEvent, KCal::Scheduler::Reply );

  // Fix broken OL appointments
  if( vCalIn.contains( "PRODID:-//Microsoft" ) ) {
    // OL doesn't send the organizer as an attendee as it should
    Attendee* organizer = new KCal::Attendee( i18n("Organizer"),
                                             event->organizer(), false,
                                             KCal::Attendee::Accepted );
    event->addAttendee( organizer );
  }

  kdDebug(5850) << "Done" << endl;
  return true;
}

/*!
  This method processes resource requests. KMail has at this point
  already decided whether the request should be accepted or declined.
*/
void KOGroupware::incomingResourceRequest( const QValueList<QPair<QDateTime,
                                           QDateTime> >& busy,
                                           const QCString& resource,
                                           const QString& vCalIn,
                                           bool& vCalInOK,
                                           QString& vCalOut,
                                           bool& vCalOutOK,
                                           bool& isFree,
                                           QDateTime& start,
                                           QDateTime& end )
{
  // Parse the event request into a ScheduleMessage; this needs to
  // be done in any case.
  KCal::ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar,
                                                                 vCalIn );
  if( message )
    vCalInOK = true;
  else {
    QString errorMessage;
    if( mFormat.exception() ) {
      errorMessage = mFormat.exception()->message();
    }
    kdDebug(5850) << "KOGroupware::incomingResourceRequest() Error parsing "
      "message: " << errorMessage << endl;
    vCalInOK = false;
    // If the message was broken, there's nothing we can do.
    return;
  }

  KCal::Event* event = dynamic_cast<KCal::Event*>( message->event() );
  Q_ASSERT( event );
  if( !event ) {
    // Something has gone badly wrong
    vCalInOK = false;
    return;
  }

  // Now find out whether the resource is free at the requested
  // time, take the opportunity to assign the reference parameters.
  start = event->dtStart();
  end = event->dtEnd();
  isFree = true;
  QValueList<QPair<QDateTime, QDateTime> >::ConstIterator it;
  for( it = busy.begin(); it != busy.end(); ++it ) {
    if( (*it).second <= start || (*it).first >= end )
      // Busy period ends before try period or starts after try period
      continue;
    else {
      isFree = false;
      break; // no need to search further
    }
  }

  // Send back the answer; construct it on the base of state
  KCal::Attendee::List attendees = event->attendees();
  KCal::Attendee* resourceAtt = 0;

  // Find the resource addresse, there will always be all attendees
  // listed, even if only one needs to answer it.
  KCal::Attendee::List::ConstIterator it2;
  for( it2 = attendees.begin(); it2 != attendees.end(); ++it2 ) {
    if( (*it2)->email().utf8() == resource ) {
      resourceAtt = *it2;
      break;
    }
  }
  Q_ASSERT( resourceAtt );
  if( resourceAtt ) {
    if( isFree )
      resourceAtt->setStatus( KCal::Attendee::Accepted );
    else
      resourceAtt->setStatus( KCal::Attendee::Declined );
  } else {
    vCalOutOK = false;
    return;
  }

  // Create the outgoing vCal
  QString messageText =
    mFormat.createScheduleMessage( event, KCal::Scheduler::Reply );
  // kdDebug(5850) << "Sending vCal back to KMail: " << messageText << endl;
  vCalOut = messageText;
  vCalOutOK = true;
  return;
}


/*!
  This method is called when the user has invited people and is now
  receiving the answers to the invitation.
*/
bool KOGroupware::incidenceAnswer( const QString& vCal )
{
  // Parse the event reply
  KCal::ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar,
                                                                 vCal );
  if( !message ) {
    // a parse error of some sort
    KMessageBox::
      error( mView,
             i18n( "<b>There was a problem parsing the iCal data:</b><br>%1" )
             .arg( mFormat.exception()->message() ) );
    return false;
  }

  KCal::IncidenceBase* incidence = message->event();

  // Enter the answer into the calendar.
  QString uid = incidence->uid();
  KCal::MailScheduler scheduler( mCalendar );
  if( !scheduler.acceptTransaction( incidence,
                                    (KCal::Scheduler::Method)message->method(),
                                    message->status() ) ) {
    KMessageBox::error( mView, i18n("Scheduling failed") );
    return false;
  }

  mView->updateView();
  return true;
}

bool KOGroupware::cancelIncidence( const QString& iCal )
{
  // Parse the event reply
  KCal::ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar,
                                                                 iCal );
  if( !message ) {
    // a parse error of some sort
    KMessageBox::
      error( mView,
             i18n( "<b>There was a problem parsing the iCal data:</b><br>%1" )
             .arg( mFormat.exception()->message() ) );
    return false;
  }

  KCal::IncidenceBase* incidence = message->event();

  // Enter the answer into the calendar.
  Event* event = mCalendar->event( incidence->uid() );
  if( !event )
    return false;

  mCalendar->deleteEvent( event );
  return true;
}

/* This function sends mails if necessary, and makes sure the user really
 * want to change his calendar.
 *
 * Return true means accept the changes
 * Return false means revert the changes
 */
bool KOGroupware::sendICalMessage( QWidget* parent,
                                   KCal::Scheduler::Method method,
                                   Incidence* incidence, bool isDeleting )
{
  bool isOrganizer = KOPrefs::instance()->email() == incidence->organizer();

  int rc = 0;
  if( isOrganizer ) {
    // Figure out if there are other people involved in this incidence
    bool otherPeople = false;
    Attendee::List attendees = incidence->attendees();
    Attendee::List::ConstIterator it;
    for ( it = attendees.begin(); it != attendees.end(); ++it ) {
      // Don't send email to ourselves
      if( (*it)->email() != KOPrefs::instance()->email() ) {
        otherPeople = true;
        break;
      }
    }
    if( !otherPeople )
      // You never send something out if no others are involved
      return true;

    QString type;
    if( incidence->type() == "Event") type = i18n("event");
    else if( incidence->type() == "Todo" ) type = i18n("task");
    else if( incidence->type() == "Journal" ) type = i18n("journal entry");
    else type = incidence->type();
    QString txt = i18n( "This %1 includes other people. "
                        "Should email be sent out to the attendees?" )
      .arg( type );
    rc = KMessageBox::questionYesNoCancel( parent, txt,
                                           i18n("Group scheduling email") );
  } else if( incidence->type() == "Todo" ) {
    if( method == Scheduler::Request )
      // This is an update to be sent to the organizer
      method = Scheduler::Reply;

    // Ask if the user wants to tell the organizer about the current status
    QString txt = i18n( "Do you want to send a status update to the "
                        "organizer of this task?");
    rc = KMessageBox::questionYesNo( parent, txt );
  } else if( incidence->type() == "Event" ) {
    // When you're not the organizer of an event, an update mail can
    // never be sent out
    // Pending(Bo): So how will an attendee cancel his participation?
    QString txt;
    if( isDeleting )
      txt = i18n( "You are not the organizer of this event. "
                  "Deleting it will bring your calendar out of sync "
                  "with the organizers calendar. Do you really want "
                  "to delete it?" );
    else
      txt = i18n( "You are not the organizer of this event. "
                  "Editing it will bring your calendar out of sync "
                  "with the organizers calendar. Do you really want "
                  "to edit it?" );
    rc = KMessageBox::questionYesNo( parent, txt );
    return ( rc == KMessageBox::Yes );
  } else {
    qFatal( "Some unimplemented thing happened" );
  }

  if( rc == KMessageBox::Yes ) {
    // We will be sending out a message here. Now make sure there is
    // some summary
    if( incidence->summary().isEmpty() )
      incidence->setSummary( i18n("<No summary given>") );

    // Send the mail
    KCal::MailScheduler scheduler( mCalendar );
    scheduler.performTransaction( incidence, method );

    return true;
  } else if( rc == KMessageBox::No )
    return true;
  else
    return false;
}


//-----------------------------------------------------------------------------

static QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key,
                           const QString& fallback = QString::null)
{
  return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16,
                            fallback );
}

static QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name,
                           const QString& fallback = QString::null )
{
  return tnefMsg->findNamedProp( name, fallback );
}

struct save_tz { char* old_tz; char* tz_env_str; };

/* temporarily go to a different timezone */
static struct save_tz set_tz( const char* _tc )
{
  const char *tc = _tc?_tc:"UTC";

  struct save_tz rv;

  rv.old_tz = 0;
  rv.tz_env_str = 0;

  //kdDebug(5006) << "set_tz(), timezone before = " << timezone << endl;

  char* tz_env = 0;
  if( getenv( "TZ" ) ) {
    tz_env = strdup( getenv( "TZ" ) );
    rv.old_tz = tz_env;
  }
  char* tmp_env = (char*)malloc( strlen( tc ) + 4 );
  strcpy( tmp_env, "TZ=" );
  strcpy( tmp_env+3, tc );
  putenv( tmp_env );

  rv.tz_env_str = tmp_env;

  /* tmp_env is not free'ed -- it is part of the environment */

  tzset();
  //kdDebug(5006) << "set_tz(), timezone after = " << timezone << endl;

  return rv;
}

/* restore previous timezone */
static void unset_tz( struct save_tz old_tz )
{
  if( old_tz.old_tz ) {
    char* tmp_env = (char*)malloc( strlen( old_tz.old_tz ) + 4 );
    strcpy( tmp_env, "TZ=" );
    strcpy( tmp_env+3, old_tz.old_tz );
    putenv( tmp_env );
    /* tmp_env is not free'ed -- it is part of the environment */
    free( old_tz.old_tz );
  } else {
    /* clear TZ from env */
    putenv( strdup("TZ") );
  }
  tzset();

  /* is this OK? */
  if( old_tz.tz_env_str ) free( old_tz.tz_env_str );
}

static QDateTime utc2Local( const QDateTime& utcdt )
{
  struct tm tmL;

  save_tz tmp_tz = set_tz("UTC");
  time_t utc = utcdt.toTime_t();
  unset_tz( tmp_tz );

  localtime_r( &utc, &tmL );
  return QDateTime( QDate( tmL.tm_year+1900, tmL.tm_mon+1, tmL.tm_mday ),
                    QTime( tmL.tm_hour, tmL.tm_min, tmL.tm_sec ) );
}

static QDateTime pureISOToLocalQDateTime( const QString& dtStr,
                                          bool bDateOnly = false )
{
  QDate tmpDate;
  QTime tmpTime;
  int year, month, day, hour, minute, second;

  if( bDateOnly ) {
    year = dtStr.left( 4 ).toInt();
    month = dtStr.mid( 4, 2 ).toInt();
    day = dtStr.mid( 6, 2 ).toInt();
    hour = 0;
    minute = 0;
    second = 0;
  } else {
    year = dtStr.left( 4 ).toInt();
    month = dtStr.mid( 4, 2 ).toInt();
    day = dtStr.mid( 6, 2 ).toInt();
    hour = dtStr.mid( 9, 2 ).toInt();
    minute = dtStr.mid( 11, 2 ).toInt();
    second = dtStr.mid( 13, 2 ).toInt();
  }
  tmpDate.setYMD( year, month, day );
  tmpTime.setHMS( hour, minute, second );

  if( tmpDate.isValid() && tmpTime.isValid() ) {
    QDateTime dT = QDateTime( tmpDate, tmpTime );

    if( !bDateOnly ) {
      // correct for GMT ( == Zulu time == UTC )
      if (dtStr.at(dtStr.length()-1) == 'Z') {
        //dT = dT.addSecs( 60 * KRFCDate::localUTCOffset() );
        //localUTCOffset( dT ) );
        dT = utc2Local( dT );
      }
    }
    return dT;
  } else
    return QDateTime();
}

QString KOGroupware::msTNEFToVPart( const QByteArray& tnef )
{
  bool bOk = false;

  KTNEFParser parser;
  QBuffer buf( tnef );
  CalendarLocal cal;
  KABC::Addressee addressee;
  KABC::VCardConverter cardConv;
  ICalFormat calFormat;
  Event* event = new Event();

  if( parser.openDevice( &buf ) ) {
    KTNEFMessage* tnefMsg = parser.message();
    //QMap<int,KTNEFProperty*> props = parser.message()->properties();

    // Everything depends from property PR_MESSAGE_CLASS
    // (this is added by KTNEFParser):
    QString msgClass = tnefMsg->findProp( 0x001A, QString::null, true )
      .upper();
    if( !msgClass.isEmpty() ) {
      // Match the old class names that might be used by Outlook for
      // compatibility with Microsoft Mail for Windows for Workgroups 3.1.
      bool bCompatClassAppointment = false;
      bool bCompatMethodRequest = false;
      bool bCompatMethodCancled = false;
      bool bCompatMethodAccepted = false;
      bool bCompatMethodAcceptedCond = false;
      bool bCompatMethodDeclined = false;
      if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ) {
        bCompatClassAppointment = true;
        if( msgClass.endsWith( ".MTGREQ" ) )
          bCompatMethodRequest = true;
        if( msgClass.endsWith( ".MTGCNCL" ) )
          bCompatMethodCancled = true;
        if( msgClass.endsWith( ".MTGRESPP" ) )
          bCompatMethodAccepted = true;
        if( msgClass.endsWith( ".MTGRESPA" ) )
          bCompatMethodAcceptedCond = true;
        if( msgClass.endsWith( ".MTGRESPN" ) )
          bCompatMethodDeclined = true;
      }
      bool bCompatClassNote = ( msgClass == "IPM.MICROSOFT MAIL.NOTE" );

      if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ) {
        // Compose a vCal
        bool bIsReply = false;
        QString prodID = "-//Microsoft Corporation//Outlook ";
        prodID += tnefMsg->findNamedProp( "0x8554", "9.0" );
        prodID += "MIMEDIR/EN\n";
        prodID += "VERSION:2.0\n";
        calFormat.setApplication( "Outlook", prodID );

        Scheduler::Method method;
        if( bCompatMethodRequest )
          method = Scheduler::Request;
        else if( bCompatMethodCancled )
          method = Scheduler::Cancel;
        else if( bCompatMethodAccepted || bCompatMethodAcceptedCond ||
                 bCompatMethodDeclined ) {
          method = Scheduler::Reply;
          bIsReply = true;
        } else {
          // pending(khz): verify whether "0x0c17" is the right tag ???
          //
          // at the moment we think there are REQUESTS and UPDATES
          //
          // but WHAT ABOUT REPLIES ???
          //
          //

          if( tnefMsg->findProp(0x0c17) == "1" )
            bIsReply = true;
          method = Scheduler::Request;
        }

        /// ###  FIXME Need to get this attribute written
        ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown );

        QString sSenderSearchKeyEmail( tnefMsg->findProp( 0x0C1D ) );

        if( !sSenderSearchKeyEmail.isEmpty() ) {
          int colon = sSenderSearchKeyEmail.find( ':' );
          // May be e.g. "SMTP:KHZ@KDE.ORG"
          if( sSenderSearchKeyEmail.find( ':' ) == -1 )
            sSenderSearchKeyEmail.remove( 0, colon+1 );
        }

        QString s( tnefMsg->findProp( 0x0e04 ) );
        QStringList attendees = QStringList::split( ';', s );
        if( attendees.count() ) {
          for( QStringList::Iterator it = attendees.begin();
               it != attendees.end(); ++it ) {
            // Skip all entries that have no '@' since these are
            // no mail addresses
            if( (*it).find('@') == -1 ) {
              s = (*it).stripWhiteSpace();

              Attendee *attendee = new Attendee( s, s, true );
              if( bIsReply ) {
                if( bCompatMethodAccepted )
                  attendee->setStatus( Attendee::Accepted );
                if( bCompatMethodDeclined )
                  attendee->setStatus( Attendee::Declined );
                if( bCompatMethodAcceptedCond )
                  attendee->setStatus(Attendee::Tentative);
              } else {
                attendee->setStatus( Attendee::NeedsAction );
                attendee->setRole( Attendee::ReqParticipant );
              }
              event->addAttendee(attendee);
            }
          }
        } else {
          // Oops, no attendees?
          // This must be old style, let us use the PR_SENDER_SEARCH_KEY.
          s = sSenderSearchKeyEmail;
          if( !s.isEmpty() ) {
            Attendee *attendee = new Attendee( QString::null, QString::null,
                                               true );
            if( bIsReply ) {
              if( bCompatMethodAccepted )
                attendee->setStatus( Attendee::Accepted );
              if( bCompatMethodAcceptedCond )
                attendee->setStatus( Attendee::Declined );
              if( bCompatMethodDeclined )
                attendee->setStatus( Attendee::Tentative );
            } else {
              attendee->setStatus(Attendee::NeedsAction);
              attendee->setRole(Attendee::ReqParticipant);
            }
            event->addAttendee(attendee);
          }
        }
        s = tnefMsg->findProp( 0x0c1f ); // look for organizer property
        if( s.isEmpty() && !bIsReply )
          s = sSenderSearchKeyEmail;
        if( !s.isEmpty() )
          event->setOrganizer( s );

        s = tnefMsg->findProp( 0x8516 ).replace( QChar( '-' ), QString::null )
          .replace( QChar( ':' ), QString::null );
        event->setDtStart( QDateTime::fromString( s ) ); // ## Format??

        s = tnefMsg->findProp( 0x8517 ).replace( QChar( '-' ), QString::null )
          .replace( QChar( ':' ), QString::null );
        event->setDtEnd( QDateTime::fromString( s ) );

        s = tnefMsg->findProp( 0x8208 );
        event->setLocation( s );

        // is it OK to set this to OPAQUE always ??
        //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme!
        //vPart += "SEQUENCE:0\n";

        // is "0x0023" OK  -  or should we look for "0x0003" ??
        s = tnefMsg->findProp( 0x0023 );
        event->setUid( s );

        // PENDING(khz): is this value in local timezone? Must it be
        // adjusted? Most likely this is a bug in the server or in
        // Outlook - we ignore it for now.
        s = tnefMsg->findProp( 0x8202 ).replace( QChar( '-' ), QString::null )
          .replace( QChar( ':' ), QString::null );
        // ### libkcal always uses currentDateTime()
        // event->setDtStamp(QDateTime::fromString(s));

        s = tnefMsg->findNamedProp( "Keywords" );
        event->setCategories( s );

        s = tnefMsg->findProp( 0x1000 );
        event->setDescription( s );

        s = tnefMsg->findProp( 0x0070 );
        event->setSummary( s );

        s = tnefMsg->findProp( 0x0026 );
        event->setPriority( s.toInt() );

        // is reminder flag set ?
        if(!tnefMsg->findProp(0x8503).isEmpty()) {
          Alarm *alarm = new Alarm(event);
          QDateTime highNoonTime =
            pureISOToLocalQDateTime( tnefMsg->findProp( 0x8502 )
                                     .replace( QChar( '-' ), "" )
                                     .replace( QChar( ':' ), "" ) );
          QDateTime wakeMeUpTime =
            pureISOToLocalQDateTime( tnefMsg->findProp( 0x8560, "" )
                                     .replace( QChar( '-' ), "" )
                                     .replace( QChar( ':' ), "" ) );
          alarm->setTime(wakeMeUpTime);

          if( highNoonTime.isValid() && wakeMeUpTime.isValid() )
            alarm->setStartOffset( Duration( highNoonTime, wakeMeUpTime ) );
          else
            // default: wake them up 15 minutes before the appointment
            alarm->setStartOffset( Duration( 15*60 ) );
          alarm->setDisplayAlarm( i18n( "Reminder" ) );

          // Sorry: the different action types are not known (yet)
          //        so we always set 'DISPLAY' (no sounds, no images...)
          event->addAlarm( alarm );
        }
        cal.addEvent( event );
        bOk = true;
        // we finished composing a vCal
      } else if( bCompatClassNote || "IPM.CONTACT" == msgClass ) {
        addressee.setUid( stringProp( tnefMsg, attMSGID ) );
        addressee.setFormattedName( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME ) \
                );
        addressee.insertEmail( sNamedProp( tnefMsg, \
                MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS ), true );
        addressee.insertEmail( sNamedProp( tnefMsg, \
                MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS ), false );
        addressee.insertEmail( sNamedProp( tnefMsg, \
                MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS ), false );
        addressee.insertCustom( "KADDRESSBOOK", "X-IMAddress", sNamedProp( tnefMsg, \
                MAPI_TAG_CONTACT_IMADDRESS ) );
        addressee.insertCustom( "KADDRESSBOOK", "X-SpousesName", stringProp( tnefMsg, \
                MAPI_TAG_PR_SPOUSE_NAME ) );
        addressee.insertCustom( "KADDRESSBOOK", "X-ManagersName", stringProp( \
                tnefMsg, MAPI_TAG_PR_MANAGER_NAME ) );
        addressee.insertCustom( "KADDRESSBOOK", "X-AssistantsName", stringProp( \
                tnefMsg, MAPI_TAG_PR_ASSISTANT ) );
        addressee.insertCustom( "KADDRESSBOOK", "X-Department", stringProp( tnefMsg, \
                MAPI_TAG_PR_DEPARTMENT_NAME ) );
        addressee.insertCustom( "KADDRESSBOOK", "X-Office", stringProp( tnefMsg, \
                MAPI_TAG_PR_OFFICE_LOCATION ) );
        addressee.insertCustom( "KADDRESSBOOK", "X-Profession", stringProp( tnefMsg, \
MAPI_TAG_PR_PROFESSION ) );

        QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY )
          .replace( QChar( '-' ), QString::null )
          .replace( QChar( ':' ), QString::null );
        if( !s.isEmpty() )
          addressee.insertCustom( "KADDRESSBOOK", "X-Anniversary", s );

        addressee.setUrl( KURL( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_WEBPAGE )  ) );

        // collect parts of Name entry
        addressee.setFamilyName( stringProp( tnefMsg, MAPI_TAG_PR_SURNAME ) );
        addressee.setGivenName( stringProp( tnefMsg, MAPI_TAG_PR_GIVEN_NAME ) );
        addressee.setAdditionalName( stringProp( tnefMsg, MAPI_TAG_PR_MIDDLE_NAME ) \
                );
        addressee.setPrefix( stringProp( tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX ) \
                );
        addressee.setSuffix( stringProp( tnefMsg, MAPI_TAG_PR_GENERATION ) );

        addressee.setNickName( stringProp( tnefMsg, MAPI_TAG_PR_NICKNAME ) );
        addressee.setRole( stringProp( tnefMsg, MAPI_TAG_PR_TITLE ) );
        addressee.setOrganization( stringProp( tnefMsg, MAPI_TAG_PR_COMPANY_NAME ) );
        /*
        the MAPI property ID of this (multiline) )field is unknown:
        vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" );
        */

        KABC::Address adr;
        adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX ) \
                );
        adr.setStreet( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET ) );
        adr.setLocality( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY ) );
        adr.setRegion( stringProp( tnefMsg, \
                MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE ) );
        adr.setPostalCode( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE \
                ) );
        adr.setCountry( stringProp( tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY ) );
        adr.setType(KABC::Address::Home);
        addressee.insertAddress(adr);

        adr.setPostOfficeBox( sNamedProp( tnefMsg, \
                MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX ) );
        adr.setStreet( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET ) \
                );
        adr.setLocality( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY ) \
                );
        adr.setRegion( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE ) \
                );
        adr.setPostalCode( sNamedProp( tnefMsg, \
                MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE ) );
        adr.setCountry( sNamedProp( tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY \
) );  adr.setType( KABC::Address::Work );
        addressee.insertAddress( adr );

        adr.setPostOfficeBox( stringProp( tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX ) \
                );
        adr.setStreet( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET ) );
        adr.setLocality( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY ) );
        adr.setRegion( stringProp(tnefMsg, \
                MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE ) );
        adr.setPostalCode( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE \
                ) );
        adr.setCountry( stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY ) );
        adr.setType( KABC::Address::Dom );
        addressee.insertAddress(adr);

        // problem: the 'other' address was stored by KOrganizer in
        //          a line looking like the following one:
        // vPart += "\nADR;TYPE=dom;TYPE=intl;TYPE=parcel;TYPE=postal;TYPE=work;TYPE=h \
ome:other_pobox;;other_str1\nother_str2;other_loc;other_region;other_pocode;other_country


        QString nr;
        nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER );
        addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Home ) \
);  nr = stringProp( tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER );
        addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Work ) \
);  nr = stringProp( tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER );
        addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Cell ) \
);  nr = stringProp( tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER );
        addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | \
KABC::PhoneNumber::Home ) );  nr = stringProp( tnefMsg, \
                MAPI_TAG_PR_BUSINESS_FAX_NUMBER );
        addressee.insertPhoneNumber( KABC::PhoneNumber( nr, KABC::PhoneNumber::Fax | \
KABC::PhoneNumber::Work ) );

        s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY )
          .replace( QChar( '-' ), QString::null )
          .replace( QChar( ':' ), QString::null );
        if( !s.isEmpty() )
          addressee.setBirthday( QDateTime::fromString( s ) );

        bOk = ( !addressee.isEmpty() );
      } else if( "IPM.NOTE" == msgClass ) {

      } // else if ... and so on ...
    }
  }

  // Compose return string
  QString iCal = calFormat.toString( &cal );
  if( !iCal.isEmpty() )
    // This was an iCal
    return iCal;

  // Not an iCal - try a vCard
  KABC::VCardConverter converter;
  return converter.createVCard( addressee );
}


#include "kogroupware.moc"



_______________________________________________
kde-pim mailing list
kde-pim@mail.kde.org
https://mail.kde.org/mailman/listinfo/kde-pim
kde-pim home page at http://pim.kde.org/

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic