public override void DeleteAppointments(ExchangeUser user, List<Appointment> appointments)
 {
     foreach (Appointment a in appointments)
     {
         _deletedAppt.Add(a);
     }
 }
 public override void WriteAppointments(ExchangeUser user, List<Appointment> appointments)
 {
     foreach (Appointment a in appointments)
     {
         _writtenAppt.Add(a);
     }
 }
 private ExchangeUser createFauxUser(string name, string email)
 {
     ExchangeUser result = new ExchangeUser();
     result.Email = email;
     result.MailNickname = name;
     result.LegacyExchangeDN = "/o=Microsoft/ou=APPS-ABC/cn=RECIPIENTS/cn=ASAMPLE";
     return result;
 }
 public List<Appointment> getResult(ExchangeUser user)
 {
     // This method runs from the main thread
     if (ConfigCache.EnableAppointmentLookup)
     {
         waitForCompletion();
         return result[user];
     }
     else
     {
         return new List<Appointment>();
     }
 }
Example #5
0
 public List <Appointment> getResult(ExchangeUser user)
 {
     // This method runs from the main thread
     if (ConfigCache.EnableAppointmentLookup)
     {
         waitForCompletion();
         return(result[user]);
     }
     else
     {
         return(new List <Appointment>());
     }
 }
Example #6
0
        private void GenerateResponseForTimeBlock(
            ExchangeUser user,
            FreeBusyTimeBlock timeBlock,
            BusyStatus busyStatus,
            bool firstAppointment,
            StringBuilder result)
        {
            if (timeBlock.Appointments == null || timeBlock.Appointments.Count == 0)
            {
                if (!firstAppointment)
                {
                    result.Append(",");
                }

                AppendPrivateFreeBusyEntry(timeBlock.StartDate,
                                           timeBlock.EndDate,
                                           user.CommonName,
                                           busyStatus,
                                           result);
                return;
            }

            foreach (Appointment appt in timeBlock.Appointments)
            {
                if (!firstAppointment)
                {
                    result.Append(",");
                }

                if (!appt.IsPrivate)
                {
                    AppendFreeBusyEntry(appt.StartDate,
                                        appt.EndDate,
                                        appt.Subject,
                                        appt.Location,
                                        appt.Organizer,
                                        appt.BusyStatus,
                                        result);
                }
                else
                {
                    AppendPrivateFreeBusyEntry(appt.StartDate,
                                               appt.EndDate,
                                               user.CommonName,
                                               appt.BusyStatus,
                                               result);
                }

                firstAppointment = false;
            }
        }
Example #7
0
 public void TestConvertEventsToFreeBusy()
 {
     ExchangeUser         user            = new ExchangeUser();
     EventEntry           googleAppsEvent = new EventEntry("title", "description", "location");
     DateTimeRange        coveredRange    = new DateTimeRange(DateTime.MaxValue, DateTime.MinValue);
     List <DateTimeRange> busyTimes       = new List <DateTimeRange>();
     List <DateTimeRange> tentativeTimes  = new List <DateTimeRange>();
     DateTime             startDate       = new DateTime(2007, 07, 1, 10, 0, 0, DateTimeKind.Utc);
     DateTime             endDate         = new DateTime(2007, 07, 1, 11, 0, 0, DateTimeKind.Utc);
     When                when             = new When(startDate, endDate);
     Uri                 uri            = new Uri("https://www.google.com/calendar/feeds/[email protected]/private/full");
     EventFeed           googleAppsFeed = new EventFeed(uri, null);
     AtomEntryCollection entries        = new AtomEntryCollection(googleAppsFeed);
 }
 public void TestConvertEventsToFreeBusy()
 {
     ExchangeUser user = new ExchangeUser();
     EventEntry googleAppsEvent = new EventEntry("title", "description", "location");
     DateTimeRange coveredRange = new DateTimeRange(DateTime.MaxValue, DateTime.MinValue);
     List<DateTimeRange> busyTimes = new List<DateTimeRange>();
     List<DateTimeRange> tentativeTimes = new List<DateTimeRange>();
     DateTime startDate = new DateTime(2007, 07, 1, 10, 0, 0, DateTimeKind.Utc);
     DateTime endDate = new DateTime(2007, 07, 1, 11, 0, 0, DateTimeKind.Utc);
     When when = new When(startDate, endDate);
     Uri uri = new Uri("https://www.google.com/calendar/feeds/[email protected]/private/full");
     EventFeed googleAppsFeed = new EventFeed(uri, null);
     AtomEntryCollection entries = new AtomEntryCollection(googleAppsFeed);
 }
Example #9
0
 /// <summary>
 /// Delete appointments from an exchange user mailbox
 /// </summary>
 /// <param name="user">The user mailbox to remove events from</param>
 /// <param name="appointments">The events to remove</param>
 public virtual void DeleteAppointments(ExchangeUser user, List <Appointment> appointments)
 {
     try
     {
         foreach (Appointment appt in appointments)
         {
             webDavQuery.Delete(appt.HRef);
         }
     }
     catch (Exception ex)
     {
         throw new GCalExchangeException(
                   GCalExchangeErrorCode.ExchangeUnreachable, "Error deleting appointment", ex);
     }
 }
        /// <summary>
        /// Sync a users free busy information between Google Calendar and the
        /// SchedulePlus Public Folder store
        /// </summary>
        /// <param name="user">The user to synchronize</param>
        /// <param name="googleAppsFeed">The Google Calendar events for the user</param>
        /// <param name="exchangeGateway">The Exchange Gateway to use</param>
        /// <param name="window">The DateTimeRange to synchronize for</param>
        public void SyncUser(
            ExchangeUser user,
            EventFeed googleAppsFeed,
            ExchangeService exchangeGateway,
            DateTimeRange window)
        {
            if (_log.IsInfoEnabled)
            {
                _log.InfoFormat("Creating F/B message.  [User={0}]", user.Email);
                _log.DebugFormat("The feed time zone is {0}", googleAppsFeed.TimeZone.Value);
            }

            string userFreeBusyUrl = FreeBusyUrl.GenerateUrlFromDN(_exchangeServerUrl,
                                                                   user.LegacyExchangeDN);

            List <string> busyMonthValues      = new List <string>();
            List <string> busyBase64Data       = new List <string>();
            List <string> tentativeMonthValues = new List <string>();
            List <string> tentativeBase64Data  = new List <string>();

            ConvertEventsToFreeBusy(user,
                                    googleAppsFeed.Entries,
                                    window,
                                    busyMonthValues,
                                    busyBase64Data,
                                    tentativeMonthValues,
                                    tentativeBase64Data);



            string startDate = FreeBusyConverter.ConvertToSysTime(window.Start).ToString();
            string endDate   = FreeBusyConverter.ConvertToSysTime(window.End).ToString();

            exchangeGateway.FreeBusy.CreateFreeBusyMessage(userFreeBusyUrl,
                                                           user.FreeBusyCommonName,
                                                           busyMonthValues,
                                                           busyBase64Data,
                                                           tentativeMonthValues,
                                                           tentativeBase64Data,
                                                           startDate,
                                                           endDate);

            if (_log.IsInfoEnabled)
            {
                _log.Info("Free/Busy message with the right properties created successfully.");
            }
        }
        /// <summary>
        /// Sync a users free busy information between Google Calendar and the
        /// SchedulePlus Public Folder store
        /// </summary>
        /// <param name="user">The user to synchronize</param>
        /// <param name="googleAppsFeed">The Google Calendar events for the user</param>
        /// <param name="exchangeGateway">The Exchange Gateway to use</param>
        /// <param name="window">The DateTimeRange to synchronize for</param>
        public void SyncUser(
            ExchangeUser user,
            EventFeed googleAppsFeed,
            ExchangeService exchangeGateway,
            DateTimeRange window)
        {
            if (_log.IsInfoEnabled)
            {
                _log.InfoFormat("Creating F/B message.  [User={0}]", user.Email);
                _log.DebugFormat("The feed time zone is {0}", googleAppsFeed.TimeZone.Value);
            }

            string userFreeBusyUrl = FreeBusyUrl.GenerateUrlFromDN(_exchangeServerUrl,
                                                                   user.LegacyExchangeDN);

            List<string> busyMonthValues = new List<string>();
            List<string> busyBase64Data = new List<string>();
            List<string> tentativeMonthValues = new List<string>();
            List<string> tentativeBase64Data = new List<string>();

            ConvertEventsToFreeBusy(user,
                                    googleAppsFeed.Entries,
                                    window,
                                    busyMonthValues,
                                    busyBase64Data,
                                    tentativeMonthValues,
                                    tentativeBase64Data);



            string startDate = FreeBusyConverter.ConvertToSysTime(window.Start).ToString();
            string endDate = FreeBusyConverter.ConvertToSysTime(window.End).ToString();

            exchangeGateway.FreeBusy.CreateFreeBusyMessage(userFreeBusyUrl,
                                                           user.FreeBusyCommonName,
                                                           busyMonthValues,
                                                           busyBase64Data,
                                                           tentativeMonthValues,
                                                           tentativeBase64Data,
                                                           startDate,
                                                           endDate);

            if ( _log.IsInfoEnabled )
            {
                _log.Info( "Free/Busy message with the right properties created successfully." );
            }
        }
Example #12
0
        /// <summary>
        /// Update appointments in an exchange user mailbox
        /// </summary>
        /// <param name="user">The user mailbox to update events in</param>
        /// <param name="appointments">The appointments to update</param>
        public virtual void UpdateAppointments(ExchangeUser user, List <Appointment> appointments)
        {
            string userMailbox = ExchangeUtil.GetDefaultCalendarUrl(exchangeServerUrl, user.AccountName);

            try
            {
                foreach (Appointment appt in appointments)
                {
                    webDavQuery.UpdateAppointment(userMailbox, appt);
                }
            }
            catch (Exception ex)
            {
                throw new GCalExchangeException(
                          GCalExchangeErrorCode.ExchangeUnreachable, "Error updating appointment", ex);
            }
        }
Example #13
0
        private static void CallConvertEventToFreeBusy(
            ExchangeUser user,
            EventEntry googleAppsEvent,
            DateTimeRange coveredRange,
            List <DateTimeRange> busyTimes,
            List <DateTimeRange> tentativeTimes)
        {
            object[] parameters = new object[5] {
                user, googleAppsEvent, coveredRange, busyTimes, tentativeTimes
            };
            BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic;

            Type       type       = typeof(SchedulePlusFreeBusyWriter);
            MethodInfo methodInfo = type.GetMethod("ConvertEventToFreeBusy", flags);

            methodInfo.Invoke(null, parameters);
        }
Example #14
0
        /// <summary>
        /// Returns appointments for the specified exchange user
        /// </summary>
        /// <param name="user">The user which appointments will be looked up for</param>
        /// <param name="window">The event window to return appointments for</param>
        /// <returns>The event appointments that were looked up</returns>
        public List <Appointment> Lookup(ExchangeUser user, DateTimeRange window)
        {
            /* Create a holder for appointments */
            List <Appointment> appointments = new List <Appointment>();

            if (!ConfigCache.EnableAppointmentLookup)
            {
                if (log.IsDebugEnabled)
                {
                    log.DebugFormat("Appointment lookup supressed for {0}", user.Email);
                }
                return(appointments);
            }

            try
            {
                /* Attempt to retrieve the Exchanger user's appointments
                 * This may fail if there is a permissions issue for this user's calendar */
                string calendarUrl = ExchangeUtil.GetDefaultCalendarUrl(exchangeServerUrl, user);

                appointments               = webDavQuery.LoadAppointments(calendarUrl, window.Start, window.End);
                user.AccessLevel           = GCalAccessLevel.ReadAccess;
                user.HaveAppointmentDetail = true;

                log.InfoFormat(
                    "Appointments read succesfully for '{0}', setting access level to ReadAccess.",
                    user.Email);
            }
            catch (WebException ex)
            {
                log.InfoFormat("Appointment access denied for {0} - {1}", user.Email, ex);
            }
            catch (Exception ex)
            {
                string errorMessage = string.Format(
                    "Error occured while retrieving appointments for user '{0}'", user.Email);

                throw new GCalExchangeException(
                          GCalExchangeErrorCode.ExchangeUnreachable,
                          errorMessage,
                          ex);
            }

            return(appointments);
        }
Example #15
0
        /// <summary>
        /// Write appointments to an exchange server mailbox
        /// </summary>
        /// <param name="user">The user mailbox to write appointments for</param>
        /// <param name="appointments">The Appointments to write</param>
        public virtual void WriteAppointments(ExchangeUser user, List <Appointment> appointments)
        {
            string calendarUrl = ExchangeUtil.GetDefaultCalendarUrl(
                exchangeServerUrl, user);

            try
            {
                foreach (Appointment appt in appointments)
                {
                    webDavQuery.CreateAppointment(calendarUrl, appt);
                }
            }
            catch (Exception ex)
            {
                throw new GCalExchangeException(
                          GCalExchangeErrorCode.ExchangeUnreachable, "Error writing appointment", ex);
            }
        }
Example #16
0
        public void TestAdjacentEvent()
        {
            DateTime      start  = DateUtil.ParseDateToUtc("2007-07-30T13:00:00.000Z");
            DateTimeRange window = new DateTimeRange(start.AddDays(-7), start.AddDays(7));

            // Test overlapping events
            // - 13:00 - 17:00 in Exchange from GCal
            // - 17:00 - 18:00 in GCal
            // Should create an exchange event [17:00-18:00]
            AddEventExchange(start, start.AddHours(4));
            AddEventGCal(start, start.AddHours(4));
            AddEventGCal(start.AddHours(4), start.AddHours(5));

            EventFeed          feed = createEventFeedFromEvents(_gcalEvents);
            FreeBusyCollection fb   = createFreeBusyFromSyncEvents(_exchEvents);

            ExchangeUser user = createFauxUser("*****@*****.**", "*****@*****.**");

            user.BusyTimes = fb;

            ExchangeGatewayMock gateway = new ExchangeGatewayMock();

            this.SyncUser(user, feed, gateway, window);

            Assert.AreEqual(0, gateway.AppointmentsMock.Deleted.Count);
            Assert.AreEqual(1, gateway.AppointmentsMock.Written.Count);
            Assert.AreEqual(0, gateway.AppointmentsMock.Updated.Count);

            Assert.AreEqual(gateway.AppointmentsMock.Written[0].StartDate, start.AddHours(4));
            Assert.AreEqual(gateway.AppointmentsMock.Written[0].EndDate, start.AddHours(5));

            AddEventExchange(start.AddHours(4), start.AddHours(5));
            feed           = createEventFeedFromEvents(_gcalEvents);
            user.BusyTimes = createFreeBusyFromSyncEvents(_exchEvents);

            gateway = new ExchangeGatewayMock();

            this.SyncUser(user, feed, gateway, window);
            Assert.AreEqual(0, gateway.AppointmentsMock.Deleted.Count);
            Assert.AreEqual(0, gateway.AppointmentsMock.Written.Count);
            Assert.AreEqual(0, gateway.AppointmentsMock.Updated.Count);
        }
        /// <summary>
        /// Returns appointments for the specified exchange user
        /// </summary>
        /// <param name="user">The user which appointments will be looked up for</param>
        /// <param name="window">The event window to return appointments for</param>
        /// <returns>The event appointments that were looked up</returns>
        public List<Appointment> Lookup(ExchangeUser user, DateTimeRange window)
        {
            /* Create a holder for appointments */
            List<Appointment> appointments = new List<Appointment>();
            if (!ConfigCache.EnableAppointmentLookup)
            {
                if (log.IsDebugEnabled)
                    log.DebugFormat("Appointment lookup supressed for {0}", user.Email );
                return appointments;
            }

            try
            {
                /* Attempt to retrieve the Exchanger user's appointments
                 * This may fail if there is a permissions issue for this user's calendar */
                string calendarUrl = ExchangeUtil.GetDefaultCalendarUrl(exchangeServerUrl, user);

                appointments = webDavQuery.LoadAppointments(calendarUrl, window.Start, window.End);
                user.AccessLevel = GCalAccessLevel.ReadAccess;
                user.HaveAppointmentDetail = true;

                log.InfoFormat(
                    "Appointments read succesfully for '{0}', setting access level to ReadAccess.",
                    user.Email );
            }
            catch (WebException ex)
            {
                log.InfoFormat("Appointment access denied for {0} - {1}", user.Email, ex);
            }
            catch (Exception ex)
            {
                string errorMessage = string.Format(
                    "Error occured while retrieving appointments for user '{0}'", user.Email );

                throw new GCalExchangeException(
                    GCalExchangeErrorCode.ExchangeUnreachable,
                    errorMessage,
                    ex);
            }

            return appointments;
        }
Example #18
0
        private void GenerateResponseForTimeBlocks(
            ExchangeUser user,
            StringBuilder result)
        {
            /* If a user has time no blocks associate with him / her */
            if (user.BusyTimes == null || user.BusyTimes.Count == 0)
            {
                return;
            }

            /* Flag for inserting commas */
            bool firstAppointment = true;

            foreach (FreeBusyTimeBlock timeBlock in user.BusyTimes.Values)
            {
                GenerateResponseForTimeBlock(user,
                                             timeBlock,
                                             BusyStatus.Busy,
                                             firstAppointment,
                                             result);
                firstAppointment = false;
            }
        }
Example #19
0
        public void TestMissingAppointmentDetail()
        {
            DateTime      start  = DateUtil.ParseDateToUtc("2007-07-30T13:00:00.000Z");
            DateTimeRange window = new DateTimeRange(start.AddDays(-7), start.AddDays(7));

            AddEventGCal(start, start.AddHours(1));

            EventFeed          feed = createEventFeedFromEvents(_gcalEvents);
            FreeBusyCollection fb   = createFreeBusyFromExistingEvents(_exchEvents);

            ExchangeUser user = createFauxUser("*****@*****.**", "*****@*****.**");

            user.BusyTimes             = fb;
            user.HaveAppointmentDetail = false;

            ExchangeGatewayMock gateway = new ExchangeGatewayMock();

            this.SyncUser(user, feed, gateway, window);

            Assert.AreEqual(0, gateway.AppointmentsMock.Deleted.Count);
            Assert.AreEqual(0, gateway.AppointmentsMock.Written.Count);
            Assert.AreEqual(0, gateway.AppointmentsMock.Updated.Count);
        }
        private ExchangeUserDict CreateExchangeUserCollection(SearchResultCollection searchResults)
        {
            ExchangeUserDict userCollection = new ExchangeUserDict();

            if (searchResults != null)
            {
                /* For each result set in the result set */
                foreach (System.DirectoryServices.SearchResult result in searchResults)
                {
                    /* Extract the property collection and create a new exchange user with it
                     * Add the new user to the result set and use the account name as the index for
                     * the dictionary collection */
                    ResultPropertyCollection property = result.Properties;
                    ExchangeUser             user     = new ExchangeUser(property);

                    if (!user.IsValid)
                    {
                        log.WarnFormat("User '{0}' is invalid and will not be synchronized.", user.CommonName);
                    }
                    else if (userCollection.ContainsKey(user.Email.ToLower()))
                    {
                        log.WarnFormat("User '{0}' was returned multiple times in the LDAP query. " +
                                       "Only the first instance was added.", user.Email);
                    }
                    else
                    {
                        userCollection.Add(user.Email.ToLower(), user);
                        log.InfoFormat("Found and added '{0}' as an ExchangeUser.", user.Email);
                    }

                    log.DebugFormat("LDAP object debug info: {0}", user);
                }
            }

            return(userCollection);
        }
        private static void ConvertEventToFreeBusy(
            ExchangeUser user,
            EventEntry googleAppsEvent,
            DateTimeRange coveredRange,
            List<DateTimeRange> busyTimes,
            List<DateTimeRange> tentativeTimes)
        {
            DateTime startDate = DateTime.MinValue;
            DateTime endDate = DateTime.MinValue;
            DateTime utcStartDate = DateTime.MinValue;
            DateTime utcEndDate = DateTime.MinValue;
            BusyStatus userStatus = BusyStatus.Free;

            userStatus = ConversionsUtil.GetUserStatusForEvent(user, googleAppsEvent);

            // If the user is free, do not put this meeting in the free busy
            if (userStatus == BusyStatus.Free)
            {
                return;
            }

            startDate = googleAppsEvent.Times[0].StartTime;
            utcStartDate = DateTime.SpecifyKind(startDate.ToUniversalTime(),
                                                DateTimeKind.Unspecified);
            endDate = googleAppsEvent.Times[0].EndTime;
            utcEndDate = DateTime.SpecifyKind(endDate.ToUniversalTime(),
                                              DateTimeKind.Unspecified);

            if (utcStartDate < coveredRange.Start)
            {
                coveredRange.Start = utcStartDate;
            }

            if (utcEndDate > coveredRange.End)
            {
                coveredRange.End = utcEndDate;
            }

            if (_log.IsDebugEnabled)
            {
                _log.DebugFormat("Read GData FB event {0} - {1}", startDate, endDate);
                _log.DebugFormat("Write FB event {0} - {1} in UTC", utcStartDate, utcEndDate);
                _log.DebugFormat("The FB status is {0}", userStatus.ToString());
            }

            if (userStatus == BusyStatus.Tentative)
            {
                tentativeTimes.Add(new DateTimeRange(utcStartDate, utcEndDate));
            }
            else
            {
                Debug.Assert(userStatus == BusyStatus.Busy);
                busyTimes.Add(new DateTimeRange(utcStartDate, utcEndDate));
            }
        }
        private static void ConvertEventsToFreeBusy(
            ExchangeUser user,
            AtomEntryCollection entries,
            DateTimeRange coveredRange,
            List<string> busyMonthValues,
            List<string> busyBase64Data,
            List<string> tentativeMonthValues,
            List<string> tentativeBase64Data)
        {
            List<DateTimeRange> busyTimes = new List<DateTimeRange>();
            List<DateTimeRange> tentativeTimes = new List<DateTimeRange>();

            foreach (EventEntry googleAppsEvent in entries)
            {
                ConvertEventToFreeBusy(user, googleAppsEvent, coveredRange, busyTimes, tentativeTimes);
            }

            FreeBusyConverter.CondenseFreeBusyTimes(busyTimes);
            FreeBusyConverter.CondenseFreeBusyTimes(tentativeTimes);

            FreeBusyConverter.ConvertDateTimeBlocksToBase64String(coveredRange.Start,
                                                                  coveredRange.End,
                                                                  busyTimes,
                                                                  busyMonthValues,
                                                                  busyBase64Data);

            FreeBusyConverter.ConvertDateTimeBlocksToBase64String(coveredRange.Start,
                                                                  coveredRange.End,
                                                                  tentativeTimes,
                                                                  tentativeMonthValues,
                                                                  tentativeBase64Data);
        }
 public override void GetCalendarInfoForUser(ExchangeUser user, DateTimeRange window)
 {
     return;
 }
Example #24
0
        /// <summary>
        /// Merges a users appointment schedule from with appointments generated from a
        /// GoogleApps feed
        /// </summary>
        /// <param name="user">User to update with Google Apps information</param>
        /// <param name="googleAppsFeed">Source feed to generate appointment information</param>
        /// <param name="exchangeGateway">Gateway to sync Appointments with</param>
        /// <param name="window">DateRange to sync for</param>
        public void SyncUser(
            ExchangeUser user,
            EventFeed googleAppsFeed,
            ExchangeService exchangeGateway,
            DateTimeRange window)
        {
            exchangeGateway.GetCalendarInfoForUser(user, window);
            if (!user.HaveAppointmentDetail)
            {
                // Cannot sync if there is no appointment detail
                log.InfoFormat("Skipped Sync of {0} due to missing appointment lookup failure", user.Email);
                return;
            }

            List <Appointment> toUpdate = new List <Appointment>();
            List <Appointment> toDelete = new List <Appointment>();
            List <Appointment> toCreate = new List <Appointment>();

            OlsonTimeZone feedTimeZone = OlsonUtil.GetTimeZone(googleAppsFeed.TimeZone.Value);
            IntervalTree <Appointment> gcalApptTree =
                CreateAppointments(user, feedTimeZone, googleAppsFeed);

            /* Iterate through each Free/Busy time block for the user */
            foreach (FreeBusyTimeBlock fbtb in user.BusyTimes.Values)
            {
                /* Iterate through each appointment for the Free/Busy time block */
                foreach (Appointment appt in fbtb.Appointments)
                {
                    log.Debug(String.Format("Exchange @ '{0} {1}'",
                                            appt.Range,
                                            ValidateOwnership(appt)));
                    /* Validate that this is a GCalender appoint */
                    if (ValidateOwnership(appt))
                    {
                        /* If the GCalender appointments do not contain an
                         * appointment for this period, add it for deletion */
                        if (gcalApptTree.FindExact(appt.Range) == null)
                        {
                            toDelete.Add(appt);
                        }
                    }
                }
            }

            /* Iterate through each Google Apps appointment */
            AppointmentCollection appointments = user.BusyTimes.Appointments;
            List <Appointment>    gcalApptList = gcalApptTree.GetNodeList();

            foreach (Appointment newAppt in gcalApptList)
            {
                // If the meeting was cancelled
                log.DebugFormat("Looking @ {0} {1}", newAppt.Range, newAppt.Range.Start.Kind);

                if (newAppt.MeetingStatus == MeetingStatus.Cancelled)
                {
                    // Check if there is an existing appointment that matches
                    List <Appointment> matches = appointments.Get(newAppt.Range);
                    foreach (Appointment a in matches)
                    {
                        if (ValidateOwnership(a))
                        {
                            toDelete.Add(a);
                        }
                    }

                    // Work is done for this appointment, continue to next entry
                    continue;
                }

                bool updatedAppointment = false;

                List <Appointment> apptList = appointments.Get(newAppt.Range);
                log.DebugFormat("Looking up preexisting event: {0} {1}", newAppt.Range, newAppt.Range.Start.Kind);
                log.DebugFormat("Found {0} matching items", apptList.Count);

                // Check that there is a free busy block that correlates with this appointment
                foreach (Appointment existingAppt in apptList)
                {
                    if (ValidateOwnership(existingAppt) && !updatedAppointment)
                    {
                        UpdateAppointmentInfo(existingAppt, newAppt);
                        toUpdate.Add(existingAppt);
                        updatedAppointment = true;
                    }
                }

                if (!updatedAppointment)
                {
                    toCreate.Add(newAppt);
                    log.DebugFormat("ADDING '{0}' - Not an update",
                                    newAppt.Range);
                }
            }

            if (log.IsInfoEnabled)
            {
                log.InfoFormat(
                    "AppointmentWriter for '{0}'.  [{1} deleted, {2} updated, {3} new]",
                    user.Email,
                    toDelete.Count,
                    toUpdate.Count,
                    toCreate.Count);
            }

            exchangeGateway.Appointments.DeleteAppointments(user, toDelete);
            // TODO: Updates are not currently published
            // exchangeGateway.Appointments.UpdateAppointments( user, updateAppointments );
            exchangeGateway.Appointments.WriteAppointments(user, toCreate);
        }
        private static void CallConvertEventToFreeBusy(
            ExchangeUser user,
            EventEntry googleAppsEvent,
            DateTimeRange coveredRange,
            List<DateTimeRange> busyTimes,
            List<DateTimeRange> tentativeTimes)
        {
            object[] parameters = new object[5] { user, googleAppsEvent, coveredRange, busyTimes, tentativeTimes };
            BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic;

            Type type = typeof(SchedulePlusFreeBusyWriter);
            MethodInfo methodInfo = type.GetMethod("ConvertEventToFreeBusy", flags);

            methodInfo.Invoke(null, parameters);
        }
        private void GenerateResponseForTimeBlocks(
            ExchangeUser user,
            StringBuilder result)
        {
            /* If a user has time no blocks associate with him / her */
            if (user.BusyTimes == null || user.BusyTimes.Count == 0)
            {
                return;
            }

            /* Flag for inserting commas */
            bool firstAppointment = true;

            foreach (FreeBusyTimeBlock timeBlock in user.BusyTimes.Values)
            {
                GenerateResponseForTimeBlock(user,
                                             timeBlock,
                                             BusyStatus.Busy,
                                             firstAppointment,
                                             result);
                firstAppointment = false;
            }

        }
Example #27
0
        public void TestConvertEventToFreeBusy()
        {
            ExchangeUser         user            = new ExchangeUser();
            EventEntry           googleAppsEvent = new EventEntry("title", "description", "location");
            DateTimeRange        coveredRange    = new DateTimeRange(DateTime.MaxValue, DateTime.MinValue);
            List <DateTimeRange> busyTimes       = new List <DateTimeRange>();
            List <DateTimeRange> tentativeTimes  = new List <DateTimeRange>();
            DateTime             startDate       = new DateTime(2007, 07, 1, 10, 0, 0, DateTimeKind.Utc);
            DateTime             endDate         = new DateTime(2007, 07, 1, 11, 0, 0, DateTimeKind.Utc);
            When when = new When(startDate, endDate);

            user.Email = "*****@*****.**";

            // Event w/o valid times set should be ignored.
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, DateTime.MaxValue);
            Assert.AreEqual(coveredRange.End, DateTime.MinValue);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 0);

            googleAppsEvent.Times.Add(when);

            // Event w/o explicit status should be treated as busy, since this is how the data
            // comes from the free busy projection
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 1);
            Assert.AreEqual(busyTimes[0].Start, startDate);
            Assert.AreEqual(busyTimes[0].End, endDate);
            busyTimes.Clear();

            // Confirmed event w/o attendees should be treated as busy.
            googleAppsEvent.Status = EventEntry.EventStatus.CONFIRMED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 1);
            Assert.AreEqual(busyTimes[0].Start, startDate);
            Assert.AreEqual(busyTimes[0].End, endDate);
            busyTimes.Clear();

            // Cancelled event should be treated as free.
            googleAppsEvent.Status = EventEntry.EventStatus.CANCELED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);

            // Tentative event w/o attendees should be treated as tentative.
            googleAppsEvent.Status = EventEntry.EventStatus.TENTATIVE;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();

            Who john = new Who();

            googleAppsEvent.Participants.Add(john);

            john.Attendee_Status   = new Who.AttendeeStatus();
            john.Email             = user.Email;
            googleAppsEvent.Status = EventEntry.EventStatus.CONFIRMED;

            // Busy event with attendee tentative should be treated as tentative.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_TENTATIVE;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();


            // Busy event with attendee invited should be treated as tentative.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_INVITED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();

            // Busy event with attendee accepted should be treated as busy.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_ACCEPTED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 1);
            Assert.AreEqual(busyTimes[0].Start, startDate);
            Assert.AreEqual(busyTimes[0].End, endDate);
            busyTimes.Clear();

            // Busy event with attendee declined should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_DECLINED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);

            googleAppsEvent.Status = EventEntry.EventStatus.TENTATIVE;

            // Tentative event with attendee tentative should be treated as tentative.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_TENTATIVE;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();


            // Tentative event with attendee invited should be treated as tentative.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_INVITED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();

            // Tentative event with attendee accepted should be treated as tentative.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_ACCEPTED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();

            // Tentative event with attendee declined should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_DECLINED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);

            googleAppsEvent.Status = EventEntry.EventStatus.CANCELED;

            // Cancelled event with attendee tentative should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_TENTATIVE;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);


            // Cancelled event with attendee invited should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_INVITED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);

            // Cancelled event with attendee accepted should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_ACCEPTED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);

            // Cancelled event with attendee declined should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_DECLINED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);
        }
 public void Init()
 {
     _user = createFauxUser("test", "*****@*****.**");
 }
        /// <summary>
        /// Assigns free busy times to the exchange users that are passed into the method
        /// </summary>
        public virtual void GetCalendarInfoForUser(ExchangeUser user, DateTimeRange window)
        {
            ExchangeUserDict users = new ExchangeUserDict();
            users.AddExchangeUser(user.Email, user);

            GetCalendarInfoForUsers(users, window);
        }
 /// <summary>
 /// Assigns free busy times to the exchange users that are passed into the method
 /// </summary>
 public virtual void GetCalendarInfoForUser(ExchangeUser exchangeUser)
 {
     GetCalendarInfoForUser(exchangeUser, DateTimeRange.Full);
 }
Example #31
0
        public void TestSyncUser()
        {
            DateTime      start  = _base;
            DateTimeRange window = new DateTimeRange(start, start.AddDays(30));

            // Add Event to both
            AddEventBoth(start, start.AddHours(2));

            // Add only to Google Calendar - i.e. Create in exchange
            start = start.AddDays(2).AddHours(3);
            AddEventGCal(start, start.AddHours(1));

            // Add only to Exchange - i.e. Delete from exchange
            start = start.AddDays(3).AddHours(3);
            AddEventExchange(start, start.AddHours(1));

            start = start.AddDays(1).AddHours(-5);
            AddEventExchange(start, start.AddMinutes(20));

            // Add to Google Calendar
            start = start.AddDays(2).AddHours(3);
            AddEventGCal(start, start.AddHours(1));

            // Add Event to both
            start = start.AddDays(2).AddHours(3);
            AddEventBoth(start, start.AddHours(2));

            // Add only to Exchange
            start = start.AddDays(2).AddHours(3);
            AddEventExchange(start, start.AddHours(1));

            // Add Event to both
            start = start.AddDays(2).AddHours(14);
            AddEventBoth(start, start.AddHours(1));

            // Add to Google Calendar
            start = start.AddDays(2).AddHours(3);
            AddEventGCal(start, start.AddHours(1));

            // Add Event to both
            start = start.AddDays(5).AddHours(12);
            AddEventBoth(start, start.AddHours(6));

            EventFeed          feed = createEventFeedFromEvents(_gcalEvents);
            FreeBusyCollection fb   = createFreeBusyFromSyncEvents(_exchEvents);

            ExchangeUser user = createFauxUser("*****@*****.**", "*****@*****.**");

            user.BusyTimes = fb;

            ExchangeGatewayMock gateway = new ExchangeGatewayMock();

            this.SyncUser(user, feed, gateway, window);

            Assert.AreEqual(_deleteEvents.Count, gateway.AppointmentsMock.Deleted.Count);
            Assert.AreEqual(_createEvents.Count, gateway.AppointmentsMock.Written.Count);
            Assert.AreEqual(0, gateway.AppointmentsMock.Updated.Count);

            int idx = 0;

            foreach (DateTimeRange e in _deleteEvents)
            {
                Assert.AreEqual(e.Start, gateway.AppointmentsMock.Deleted[idx].StartDate);
                Assert.AreEqual(e.End, gateway.AppointmentsMock.Deleted[idx].EndDate);
                idx++;
            }

            idx = 0;
            foreach (DateTimeRange e in _createEvents)
            {
                Assert.AreEqual(e.Start, gateway.AppointmentsMock.Written[idx].StartDate);
                Assert.AreEqual(e.End, gateway.AppointmentsMock.Written[idx].EndDate);
                idx++;
            }
        }
 public override void GetCalendarInfoForUser(ExchangeUser user, DateTimeRange window)
 {
     return;
 }
        /// <summary>
        /// Combines the free busy and appointment blocks supplied to the exchange user object
        /// If no appointments are supplied the user will still have free busy time blocks assigned
        /// to them, with a null appointment assigned to the free busy time.
        /// </summary>
        /// <param name="exchangeUser">Exchange users to apply freeBusy and appointments</param>
        /// <param name="freeBusy">The collection of FreeBusy blocks to assign to exchangeUser</param>
        /// <param name="appointments">The collection of appointment blocks to assign to exchangeUser</param>
        /// <param name="window">Window to merge for</param>
        protected void MergeFreeBusyWithAppointments(
            ExchangeUser exchangeUser,
            FreeBusy freeBusy,
            List<Appointment> appointments,
            DateTimeRange window)
        {
            using (BlockTimer bt = new BlockTimer("MergeFreeBusyWithAppointments"))
            {
                IntervalTree<FreeBusyTimeBlock> busyIntervals =
                    new IntervalTree<FreeBusyTimeBlock>();
                FreeBusyCollection busyTimes = new FreeBusyCollection();
                int appointmentsCount = 0;
                List<DateTimeRange> combinedTimes =
                    FreeBusyConverter.MergeFreeBusyLists(freeBusy.All, freeBusy.Tentative);

                /* Add the date ranges from each collection in the FreeBusy object */
                ConvertFreeBusyToBlocks(window,
                                        combinedTimes,
                                        busyTimes,
                                        busyIntervals);

                if (appointments != null && appointments.Count > 0)
                {
                    appointmentsCount = appointments.Count;
                    foreach (Appointment appt in appointments)
                    {
                        log.DebugFormat("Appt \"{0}\" {1} {2} response = {3} status = {4} busy = {5}",
                                        appt.Subject,
                                        appt.Range,
                                        appt.StartDate.Kind,
                                        appt.ResponseStatus,
                                        appt.MeetingStatus,
                                        appt.BusyStatus);

                        if (appt.BusyStatus == BusyStatus.Free)
                        {
                            continue;
                        }

                        DateTimeRange range = new DateTimeRange(appt.StartDate, appt.EndDate);
                        List<FreeBusyTimeBlock> result =
                            busyIntervals.FindAll(range, IntervalTreeMatch.Overlap);

                        log.DebugFormat("Found {0} ranges overlap {1}", result.Count, range);

                        foreach (FreeBusyTimeBlock block in result)
                        {
                            log.DebugFormat("Adding \"{0}\" to FB {1} {2}",
                                            appt.Subject,
                                            block.Range,
                                            block.StartDate.Kind);
                            block.Appointments.Add(appt);
                        }

                        busyTimes.Appointments.Add(appt);
                    }
                }

                foreach (FreeBusyTimeBlock block in busyTimes.Values)
                {
                    block.Appointments.Sort(CompareAppointmentsByRanges);
                }

                log.InfoFormat("Merge Result of {0} + Appointment {1} -> {2}",
                               combinedTimes.Count,
                               appointmentsCount,
                               busyTimes.Count);

                /* Assign the data structure to the exchange user */
                exchangeUser.BusyTimes = busyTimes;
            }
        }
 /// <summary>
 /// Assigns free busy times to the exchange users that are passed into the method
 /// </summary>
 public virtual void GetCalendarInfoForUser( ExchangeUser exchangeUser )
 {
     GetCalendarInfoForUser( exchangeUser, DateTimeRange.Full );
 }
        public void TestConvertEventToFreeBusy()
        {
            ExchangeUser user = new ExchangeUser();
            EventEntry googleAppsEvent = new EventEntry("title", "description", "location");
            DateTimeRange coveredRange = new DateTimeRange(DateTime.MaxValue, DateTime.MinValue);
            List<DateTimeRange> busyTimes = new List<DateTimeRange>();
            List<DateTimeRange> tentativeTimes = new List<DateTimeRange>();
            DateTime startDate = new DateTime(2007, 07, 1, 10, 0, 0, DateTimeKind.Utc);
            DateTime endDate = new DateTime(2007, 07, 1, 11, 0, 0, DateTimeKind.Utc);
            When when = new When(startDate, endDate);

            user.Email = "*****@*****.**";

            // Event w/o valid times set should be ignored.
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, DateTime.MaxValue);
            Assert.AreEqual(coveredRange.End, DateTime.MinValue);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 0);

            googleAppsEvent.Times.Add(when);

            // Event w/o explicit status should be treated as busy, since this is how the data
            // comes from the free busy projection
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 1);
            Assert.AreEqual(busyTimes[0].Start, startDate);
            Assert.AreEqual(busyTimes[0].End, endDate);
            busyTimes.Clear();

            // Confirmed event w/o attendees should be treated as busy.
            googleAppsEvent.Status = EventEntry.EventStatus.CONFIRMED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 1);
            Assert.AreEqual(busyTimes[0].Start, startDate);
            Assert.AreEqual(busyTimes[0].End, endDate);
            busyTimes.Clear();

            // Cancelled event should be treated as free.
            googleAppsEvent.Status = EventEntry.EventStatus.CANCELED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);

            // Tentative event w/o attendees should be treated as tentative.
            googleAppsEvent.Status = EventEntry.EventStatus.TENTATIVE;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();

            Who john = new Who();
            googleAppsEvent.Participants.Add(john);

            john.Attendee_Status = new Who.AttendeeStatus();
            john.Email = user.Email;
            googleAppsEvent.Status = EventEntry.EventStatus.CONFIRMED;

            // Busy event with attendee tentative should be treated as tentative.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_TENTATIVE;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();


            // Busy event with attendee invited should be treated as tentative.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_INVITED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();

            // Busy event with attendee accepted should be treated as busy.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_ACCEPTED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 1);
            Assert.AreEqual(busyTimes[0].Start, startDate);
            Assert.AreEqual(busyTimes[0].End, endDate);
            busyTimes.Clear();

            // Busy event with attendee declined should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_DECLINED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);

            googleAppsEvent.Status = EventEntry.EventStatus.TENTATIVE;

            // Tentative event with attendee tentative should be treated as tentative.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_TENTATIVE;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();


            // Tentative event with attendee invited should be treated as tentative.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_INVITED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();

            // Tentative event with attendee accepted should be treated as tentative.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_ACCEPTED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(busyTimes.Count, 0);
            Assert.AreEqual(tentativeTimes.Count, 1);
            Assert.AreEqual(tentativeTimes[0].Start, startDate);
            Assert.AreEqual(tentativeTimes[0].End, endDate);
            tentativeTimes.Clear();

            // Tentative event with attendee declined should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_DECLINED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);

            googleAppsEvent.Status = EventEntry.EventStatus.CANCELED;

            // Cancelled event with attendee tentative should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_TENTATIVE;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);


            // Cancelled event with attendee invited should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_INVITED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);

            // Cancelled event with attendee accepted should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_ACCEPTED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);

            // Cancelled event with attendee declined should be treated as free.
            john.Attendee_Status.Value = Who.AttendeeStatus.EVENT_DECLINED;
            CallConvertEventToFreeBusy(user,
                                       googleAppsEvent,
                                       coveredRange,
                                       busyTimes,
                                       tentativeTimes);
            Assert.AreEqual(coveredRange.Start, startDate);
            Assert.AreEqual(coveredRange.End, endDate);
            Assert.AreEqual(tentativeTimes.Count, 0);
            Assert.AreEqual(busyTimes.Count, 0);
        }
        private ExchangeUserDict CreateExchangeUserCollection( SearchResultCollection searchResults )
        {
            ExchangeUserDict userCollection = new ExchangeUserDict();

            if ( searchResults != null )
            {
                /* For each result set in the result set */
                foreach ( System.DirectoryServices.SearchResult result in searchResults )
                {
                    /* Extract the property collection and create a new exchange user with it
                     * Add the new user to the result set and use the account name as the index for
                     * the dictionary collection */
                    ResultPropertyCollection property = result.Properties;
                    ExchangeUser user = new ExchangeUser( property );

                    if ( !user.IsValid )
                    {
                        log.WarnFormat( "User '{0}' is invalid and will not be synchronized.", user.CommonName );
                    }
                    else if ( userCollection.ContainsKey( user.Email.ToLower() ) )
                    {
                        log.WarnFormat( "User '{0}' was returned multiple times in the LDAP query. " +
                            "Only the first instance was added.", user.Email );
                    }
                    else
                    {
                        userCollection.Add( user.Email.ToLower(), user );
                        log.InfoFormat("Found and added '{0}' as an ExchangeUser.", user.Email);
                    }

                    log.DebugFormat("LDAP object debug info: {0}", user);
                }
            }

            return userCollection;
        }
        /// <summary>
        /// Returns the free busy times for the specified exchange users
        /// </summary>
        /// <param name="user">The user which free/busy blocks will be looked up for</param>
        /// <param name="window">The date range to do the lookup</param>
        /// <returns>FreeBusy data for user in the daterange</returns>
        public FreeBusy LookupFreeBusyTimes( ExchangeUser user, DateTimeRange window )
        {
            ExchangeUserDict users = new ExchangeUserDict();
            users.Add( user.Email, user );

            Dictionary<ExchangeUser, FreeBusy> result = LookupFreeBusyTimes ( users, window );

            return result[user];
        }
        /// <summary>
        /// Update appointments in an exchange user mailbox
        /// </summary>
        /// <param name="user">The user mailbox to update events in</param>
        /// <param name="appointments">The appointments to update</param>
        public virtual void UpdateAppointments(ExchangeUser user, List<Appointment> appointments)
        {
            string userMailbox = ExchangeUtil.GetDefaultCalendarUrl(exchangeServerUrl, user.AccountName);

            try
            {
                foreach (Appointment appt in appointments)
                {
                    webDavQuery.UpdateAppointment( userMailbox, appt );
                }
            }
            catch (Exception ex)
            {
                throw new GCalExchangeException(
                    GCalExchangeErrorCode.ExchangeUnreachable, "Error updating appointment", ex);
            }

        }
        /// <summary>
        /// Returns a set of appointments from a GoogleApps Feed
        /// </summary>
        /// <param name="user">The exchange user to get apointments for</param>
        /// <param name="srcTimeZone">The time zone to use</param>
        /// <param name="googleAppsFeed">Source feed to create array from</param>
        /// <returns></returns>
        private IntervalTree<Appointment> CreateAppointments(
            ExchangeUser user,
            OlsonTimeZone srcTimeZone,
            EventFeed googleAppsFeed)
        {
            IntervalTree<Appointment> result = new IntervalTree<Appointment>();

            foreach ( EventEntry eventEntry in googleAppsFeed.Entries )
            {
                try
                {
                    CreateAppointment(result, user, srcTimeZone, eventEntry);
                }
                catch ( GCalExchangeException ex )
                {
                    if ( ex.ErrorCode == GCalExchangeErrorCode.OlsonTZError )
                    {
                        log.Error( "Error creating appointment (TimeZone issue)", ex );
                    }
                    else
                    {
                        throw;
                    }
                }
            }
            return result;
        }
 /// <summary>
 /// Delete appointments from an exchange user mailbox
 /// </summary>
 /// <param name="user">The user mailbox to remove events from</param>
 /// <param name="appointments">The events to remove</param>
 public virtual void DeleteAppointments(ExchangeUser user, List<Appointment> appointments)
 {
     try
     {
         foreach (Appointment appt in appointments)
         {
             webDavQuery.Delete(appt.HRef);
         }
     }
     catch (Exception ex)
     {
         throw new GCalExchangeException(
             GCalExchangeErrorCode.ExchangeUnreachable, "Error deleting appointment", ex);
     }
 }
        private void CreateAppointment(
            IntervalTree<Appointment> result,
            ExchangeUser user,
            OlsonTimeZone srcTimeZone,
            EventEntry googleAppsEvent)
        {
            Appointment appt = new Appointment();

            if ( googleAppsEvent.Times != null && googleAppsEvent.Times.Count > 0 )
            {
                When eventTime = googleAppsEvent.Times[0];
                appt.StartDate = DateTime.SpecifyKind(eventTime.StartTime.ToUniversalTime(), DateTimeKind.Unspecified);
                appt.EndDate = DateTime.SpecifyKind(eventTime.EndTime.ToUniversalTime(), DateTimeKind.Unspecified);

                log.DebugFormat("Create - [{0}] {1}", appt.Range, appt.Range.Start.Kind);

                appt.AllDayEvent = googleAppsEvent.Times[0].AllDay;
            }

            if (ConfigCache.SyncAppointmentDetails &&
                googleAppsEvent.Locations != null &&
                googleAppsEvent.Locations.Count > 0 )
            {
                appt.Location = googleAppsEvent.Locations[0].ValueString ?? string.Empty;
            }
            else
            {
                appt.Location = String.Empty;
            }

            string subject = ConfigCache.PlaceHolderMessage;

            if (ConfigCache.SyncAppointmentDetails)
            {
                subject += ": " + ConversionsUtil.SafeGetText(googleAppsEvent.Title);
                if (subject.Length > 255)
                {
                    subject = subject.Remove(255);
                }
            }

            appt.Subject = subject;

            if (ConfigCache.SyncAppointmentDetails)
            {
                appt.Body = ConversionsUtil.SafeGetContent(googleAppsEvent.Content);
            }

            appt.MeetingStatus = ConversionsUtil.ConvertGoogleEventStatus(googleAppsEvent.Status);

            appt.BusyStatus = ConversionsUtil.GetUserStatusForEvent(user, googleAppsEvent);

            appt.Created = DateUtil.NowUtc;
            appt.InstanceType = InstanceType.Single;
            AssignOwnership( appt );
            result.Insert(appt.Range, appt);
        }
        private void GenerateResponseForTimeBlock(
            ExchangeUser user,
            FreeBusyTimeBlock timeBlock,
            BusyStatus busyStatus,
            bool firstAppointment,
            StringBuilder result)
        {
            if (timeBlock.Appointments == null || timeBlock.Appointments.Count == 0)
            {
                if (!firstAppointment)
                {
                    result.Append(",");
                }

                AppendPrivateFreeBusyEntry(timeBlock.StartDate,
                                           timeBlock.EndDate,
                                           user.CommonName,
                                           busyStatus,
                                           result);
                return;
            }

            foreach (Appointment appt in timeBlock.Appointments)
            {
                if (!firstAppointment)
                {
                    result.Append(",");
                }

                if (!appt.IsPrivate)
                {
                    AppendFreeBusyEntry(appt.StartDate,
                                        appt.EndDate,
                                        appt.Subject,
                                        appt.Location,
                                        appt.Organizer,
                                        appt.BusyStatus,
                                        result);
                }
                else
                {
                    AppendPrivateFreeBusyEntry(appt.StartDate,
                                               appt.EndDate,
                                               user.CommonName,
                                               appt.BusyStatus,
                                               result);
                }

                firstAppointment = false;
            }
        }
        /// <summary>
        /// Merges a users appointment schedule from with appointments generated from a
        /// GoogleApps feed
        /// </summary>
        /// <param name="user">User to update with Google Apps information</param>
        /// <param name="googleAppsFeed">Source feed to generate appointment information</param>
        /// <param name="exchangeGateway">Gateway to sync Appointments with</param>
        /// <param name="window">DateRange to sync for</param>
        public void SyncUser(
            ExchangeUser user,
            EventFeed googleAppsFeed,
            ExchangeService exchangeGateway,
            DateTimeRange window)
        {
            exchangeGateway.GetCalendarInfoForUser(user, window);
            if (!user.HaveAppointmentDetail)
            {
                // Cannot sync if there is no appointment detail
                log.InfoFormat("Skipped Sync of {0} due to missing appointment lookup failure", user.Email);
                return;
            }

            List<Appointment> toUpdate = new List<Appointment>();
            List<Appointment> toDelete = new List<Appointment>();
            List<Appointment> toCreate = new List<Appointment>();

            OlsonTimeZone feedTimeZone = OlsonUtil.GetTimeZone(googleAppsFeed.TimeZone.Value);
            IntervalTree<Appointment> gcalApptTree =
                CreateAppointments(user, feedTimeZone, googleAppsFeed);

            /* Iterate through each Free/Busy time block for the user */
            foreach (FreeBusyTimeBlock fbtb in user.BusyTimes.Values)
            {
                /* Iterate through each appointment for the Free/Busy time block */
                foreach (Appointment appt in fbtb.Appointments)
                {
                    log.Debug(String.Format("Exchange @ '{0} {1}'",
                               appt.Range,
                               ValidateOwnership(appt)));
                    /* Validate that this is a GCalender appoint */
                    if ( ValidateOwnership( appt ) )
                    {
                        /* If the GCalender appointments do not contain an
                         * appointment for this period, add it for deletion */
                        if (gcalApptTree.FindExact(appt.Range) == null)
                        {
                            toDelete.Add( appt );
                        }
                    }
                }
            }

            /* Iterate through each Google Apps appointment */
            AppointmentCollection appointments = user.BusyTimes.Appointments;
            List<Appointment> gcalApptList = gcalApptTree.GetNodeList();

            foreach (Appointment newAppt in gcalApptList)
            {
                // If the meeting was cancelled
                log.DebugFormat("Looking @ {0} {1}", newAppt.Range, newAppt.Range.Start.Kind);

                if ( newAppt.MeetingStatus == MeetingStatus.Cancelled )
                {
                    // Check if there is an existing appointment that matches
                    List<Appointment> matches = appointments.Get(newAppt.Range);
                    foreach (Appointment a in matches)
                    {
                        if (ValidateOwnership(a))
                        {
                            toDelete.Add(a);
                        }
                    }

                    // Work is done for this appointment, continue to next entry
                    continue;
                }

                bool updatedAppointment = false;

                List<Appointment> apptList = appointments.Get(newAppt.Range);
                log.DebugFormat("Looking up preexisting event: {0} {1}", newAppt.Range, newAppt.Range.Start.Kind);
                log.DebugFormat("Found {0} matching items", apptList.Count);

                // Check that there is a free busy block that correlates with this appointment
                foreach ( Appointment existingAppt in apptList )
                {
                    if (ValidateOwnership(existingAppt) && !updatedAppointment)
                    {
                        UpdateAppointmentInfo(existingAppt, newAppt);
                        toUpdate.Add( existingAppt );
                        updatedAppointment = true;
                    }
                }

                if (!updatedAppointment)
                {
                    toCreate.Add( newAppt );
                    log.DebugFormat("ADDING '{0}' - Not an update",
                            newAppt.Range);
                }
            }

            if (log.IsInfoEnabled)
            {
                log.InfoFormat(
                    "AppointmentWriter for '{0}'.  [{1} deleted, {2} updated, {3} new]",
                    user.Email,
                    toDelete.Count,
                    toUpdate.Count,
                    toCreate.Count);
            }

            exchangeGateway.Appointments.DeleteAppointments(user, toDelete);
            // TODO: Updates are not currently published
            // exchangeGateway.Appointments.UpdateAppointments( user, updateAppointments );
            exchangeGateway.Appointments.WriteAppointments(user, toCreate);
        }
 public void Init()
 {
     _user = createFauxUser("test", "*****@*****.**");
 }
        /// <summary>
        /// Combines the free busy and appointment blocks supplied to the exchange user object
        /// If no appointments are supplied the user will still have free busy time blocks assigned
        /// to them, with a null appointment assigned to the free busy time.
        /// </summary>
        /// <param name="exchangeUser">Exchange users to apply freeBusy and appointments</param>
        /// <param name="freeBusy">The collection of FreeBusy blocks to assign to exchangeUser</param>
        /// <param name="appointments">The collection of appointment blocks to assign to exchangeUser</param>
        /// <param name="window">Window to merge for</param>
        protected void MergeFreeBusyWithAppointments(
            ExchangeUser exchangeUser,
            FreeBusy freeBusy,
            List <Appointment> appointments,
            DateTimeRange window)
        {
            using (BlockTimer bt = new BlockTimer("MergeFreeBusyWithAppointments"))
            {
                IntervalTree <FreeBusyTimeBlock> busyIntervals =
                    new IntervalTree <FreeBusyTimeBlock>();
                FreeBusyCollection busyTimes       = new FreeBusyCollection();
                int appointmentsCount              = 0;
                List <DateTimeRange> combinedTimes =
                    FreeBusyConverter.MergeFreeBusyLists(freeBusy.All, freeBusy.Tentative);

                /* Add the date ranges from each collection in the FreeBusy object */
                ConvertFreeBusyToBlocks(window,
                                        combinedTimes,
                                        busyTimes,
                                        busyIntervals);

                if (appointments != null && appointments.Count > 0)
                {
                    appointmentsCount = appointments.Count;
                    foreach (Appointment appt in appointments)
                    {
                        log.DebugFormat("Appt \"{0}\" {1} {2} response = {3} status = {4} busy = {5}",
                                        appt.Subject,
                                        appt.Range,
                                        appt.StartDate.Kind,
                                        appt.ResponseStatus,
                                        appt.MeetingStatus,
                                        appt.BusyStatus);

                        if (appt.BusyStatus == BusyStatus.Free)
                        {
                            continue;
                        }

                        DateTimeRange            range  = new DateTimeRange(appt.StartDate, appt.EndDate);
                        List <FreeBusyTimeBlock> result =
                            busyIntervals.FindAll(range, IntervalTreeMatch.Overlap);

                        log.DebugFormat("Found {0} ranges overlap {1}", result.Count, range);

                        foreach (FreeBusyTimeBlock block in result)
                        {
                            log.DebugFormat("Adding \"{0}\" to FB {1} {2}",
                                            appt.Subject,
                                            block.Range,
                                            block.StartDate.Kind);
                            block.Appointments.Add(appt);
                        }

                        busyTimes.Appointments.Add(appt);
                    }
                }

                foreach (FreeBusyTimeBlock block in busyTimes.Values)
                {
                    block.Appointments.Sort(CompareAppointmentsByRanges);
                }

                log.InfoFormat("Merge Result of {0} + Appointment {1} -> {2}",
                               combinedTimes.Count,
                               appointmentsCount,
                               busyTimes.Count);

                /* Assign the data structure to the exchange user */
                exchangeUser.BusyTimes = busyTimes;
            }
        }
Example #46
0
        /// <summary>
        /// Equality test based on Active Directory CN
        /// </summary>
        /// <param name="obj">instance to compare</param>
        /// <returns>true if the two instances are equal</returns>
        public override bool Equals(Object obj)
        {
            ExchangeUser user = obj as ExchangeUser;

            return(this.CommonName.Equals(user.CommonName));
        }
        private FreeBusy createFreeBusy(ExchangeUser user)
        {
            FreeBusy result = new FreeBusy();
            result.User = user;

            result.All = _freeBusy.GetRange(0, _freeBusy.Count);
            result.Busy = _freeBusy.GetRange(0, _freeBusy.Count);
            return result;
        }
 public void Init()
 {
     _requestor = new XmlRequestMock();
     _webdav = new WebDavQuery(CredentialCache.DefaultCredentials, _requestor);
     _user = createFauxUser("test", "*****@*****.**");
 }
        /// <summary>
        /// Write appointments to an exchange server mailbox
        /// </summary>
        /// <param name="user">The user mailbox to write appointments for</param>
        /// <param name="appointments">The Appointments to write</param>
        public virtual void WriteAppointments(ExchangeUser user, List<Appointment> appointments)
        {
            string calendarUrl = ExchangeUtil.GetDefaultCalendarUrl(
                exchangeServerUrl, user );

            try
            {
                foreach (Appointment appt in appointments)
                {
                    webDavQuery.CreateAppointment(calendarUrl, appt);
                }
            }
            catch (Exception ex)
            {
                throw new GCalExchangeException(
                    GCalExchangeErrorCode.ExchangeUnreachable, "Error writing appointment", ex);
            }
        }