public async Task ExportEventsAsync(TripSketch.Core.Models.Calendar calendar, IList<Event> events) { var apiCalendar = new Calendars.Plugin.Abstractions.Calendar { Name = calendar.Name, ExternalID = calendar.ExternalID }; var calendarEvents = events.Select(e => new CalendarEvent { Name = e.Name, AllDay = e.AllDay, ExternalID = e.ExternalID, Start = e.Start, End = e.End }); if (string.IsNullOrEmpty(apiCalendar.ExternalID)) { await CrossCalendars.Current.AddOrUpdateCalendarAsync(apiCalendar).ConfigureAwait(false); calendar.ExternalID = apiCalendar.ExternalID; } foreach (var ev in events) { var calendarEvent = new CalendarEvent { Name = ev.Name, AllDay = ev.AllDay, ExternalID = ev.ExternalID, Start = ev.Start, End = ev.End }; await CrossCalendars.Current.AddOrUpdateEventAsync(apiCalendar, calendarEvent).ConfigureAwait(false); ev.ExternalID = calendarEvent.ExternalID; } }
/// <summary> /// Gets a single calendar by platform-specific ID. /// </summary> /// <param name="externalId">Platform-specific calendar identifier</param> /// <returns>The corresponding calendar, or null if not found</returns> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public Task <Calendar> GetCalendarByIdAsync(string externalId) { long calendarId = -1; if (!long.TryParse(externalId, out calendarId)) { return(null); } return(Task.Run <Calendar>(() => { Calendar calendar = null; var cursor = Query( ContentUris.WithAppendedId(_calendarsUri, calendarId), _calendarsProjection); try { if (cursor.MoveToFirst()) { calendar = GetCalendar(cursor); } } catch (Java.Lang.Exception ex) { throw new PlatformException(ex.Message, ex); } finally { cursor.Close(); } return calendar; })); }
public async Task<ObservableCollection<Event>> GetEventsAsync(TripSketch.Core.Models.Calendar calendar, DateTime start, DateTime end) { var apiCalendar = new Calendars.Plugin.Abstractions.Calendar { Name = calendar.Name, ExternalID = calendar.ExternalID }; var events = await CrossCalendars.Current.GetEventsAsync(apiCalendar, start, end).ConfigureAwait(false); return new ObservableCollection<Event>(events.Select(e => new Event { Name = e.Name, AllDay = e.AllDay, ExternalID = e.ExternalID, Start = e.Start, End = e.End })); }
/// <summary> /// Creates a new calendar with the specified name and optional color. /// (just a convenience wrapper around AddOrUpdateCalendarAsync) /// </summary> /// <param name="api">ICalendars instance to extend</param> /// <param name="calendarName">Calendar name</param> /// <param name="color">Preferred color, or null to accept default</param> /// <returns>The created calendar</returns> public static async Task<Calendar> CreateCalendarAsync(this ICalendars api, string calendarName, string color = null) { var calendar = new Calendar { Name = calendarName, Color = color, CanEditCalendar = true, CanEditEvents = true }; await api.AddOrUpdateCalendarAsync(calendar).ConfigureAwait(false); return calendar; }
/// <summary> /// Removes a calendar and all its events from the system. /// </summary> /// <param name="calendar">Calendar to delete</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task <bool> DeleteCalendarAsync(Calendar calendar) { var existing = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existing == null) { return(false); } else if (!existing.CanEditCalendar) { throw new ArgumentException("Cannot delete calendar (probably because it's non-local)", "calendar"); } return(await Task.Run(() => Delete(_calendarsUri, long.Parse(calendar.ExternalID))).ConfigureAwait(false)); }
/// <summary> /// Removes an event from the specified calendar. /// </summary> /// <param name="calendar">Calendar to remove event from</param> /// <param name="calendarEvent">Event to remove</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task <bool> DeleteEventAsync(Calendar calendar, CalendarEvent calendarEvent) { long existingId = -1; // Even though a Calendar was passed-in, we get this to both verify // that the calendar exists and to make sure we have accurate permissions // (rather than trusting the permissions that were passed to us...) // var existingCal = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existingCal == null) { return(false); } else if (!existingCal.CanEditEvents) { throw new ArgumentException("Cannot delete event from readonly calendar", "calendar"); } if (long.TryParse(calendarEvent.ExternalID, out existingId)) { return(await Task.Run <bool>(() => { var calendarId = GetCalendarIdForEventId(calendarEvent.ExternalID); if (calendarId.HasValue && calendarId.Value.ToString() == calendar.ExternalID) { var eventsUri = CalendarContract.Events.ContentUri; return Delete(eventsUri, existingId); } return false; })); } return(false); }
public async void Calendars_AddOrUpdateEvents_CopiesEventsBetweenCalendars() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today.AddDays(5), End = DateTime.Today.AddDays(5).AddHours(2), AllDay = false }; var calendarSource = new Calendar { Name = _calendarName }; var calendarTarget = new Calendar { Name = _calendarName + " copy destination" }; await _service.AddOrUpdateCalendarAsync(calendarSource); await _service.AddOrUpdateCalendarAsync(calendarTarget); await _service.AddOrUpdateEventAsync(calendarSource, calendarEvent); var sourceEvents = await _service.GetEventsAsync(calendarSource, DateTime.Today, DateTime.Today.AddDays(30)); //await _service.AddOrUpdateEventsAsync(calendarTarget, sourceEvents); foreach (var cev in sourceEvents) { await _service.AddOrUpdateEventAsync(calendarTarget, cev); } var targetEvents = await _service.GetEventsAsync(calendarTarget, DateTime.Today, DateTime.Today.AddDays(30)); // Requery source events, just to be extra sure sourceEvents = await _service.GetEventsAsync(calendarSource, DateTime.Today, DateTime.Today.AddDays(30)); // Make sure the events are the same... Assert.That(targetEvents, Is.EqualTo(sourceEvents).Using<CalendarEvent>(_eventComparer)); // ...except for their IDs! (i.e., they are actually unique copies) Assert.That(targetEvents.Select(e => e.ExternalID).ToList(), Is.Not.EqualTo(sourceEvents.Select(e => e.ExternalID).ToList())); }
public async void Calendars_AddOrUpdateEvent_NonexistentCalendarThrows() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today, End = DateTime.Today.AddHours(1) }; var calendar = new Calendar { Name = _calendarName }; // Create/delete calendar so we have a valid ID for a nonexistent calendar // await _service.AddOrUpdateCalendarAsync(calendar); await _service.DeleteCalendarAsync(calendar); Assert.IsTrue(await _service.AddOrUpdateEventAsync(calendar, calendarEvent).ThrowsAsync<ArgumentException>(), "Exception wasn't thrown"); }
public async void Calendars_AddOrUpdateEvents_StartAfterEndThrows() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today, End = DateTime.Today.AddDays(-1) }; var calendar = new Calendar { Name = _calendarName }; await _service.AddOrUpdateCalendarAsync(calendar); Assert.IsTrue(await _service.AddOrUpdateEventAsync(calendar, calendarEvent).ThrowsAsync<ArgumentException>(), "Exception wasn't thrown"); // Ensure that calendar still has no events Assert.That(await _service.GetEventsAsync(calendar, DateTime.Today.AddMonths(-1), DateTime.Today.AddMonths(1)), Is.Empty, "Calendar has event, even after throwing"); }
/// <summary> /// Removes an event from the specified calendar. /// </summary> /// <param name="calendar">Calendar to remove event from</param> /// <param name="calendarEvent">Event to remove</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteEventAsync(Calendar calendar, CalendarEvent calendarEvent) { if (string.IsNullOrEmpty(calendar.ExternalID) || string.IsNullOrEmpty(calendarEvent.ExternalID)) { return false; } await RequestCalendarAccess().ConfigureAwait(false); var deviceCalendar = _eventStore.GetCalendar(calendar.ExternalID); if (deviceCalendar == null) { return false; } var iosEvent = _eventStore.EventFromIdentifier(calendarEvent.ExternalID); if (iosEvent == null || iosEvent.Calendar.CalendarIdentifier != deviceCalendar.CalendarIdentifier) { return false; } NSError error = null; if (!_eventStore.RemoveEvent(iosEvent, EKSpan.ThisEvent, true, out error)) { // Without this, the eventStore may act like the remove succeeded. // (this obviously also resets any other changes, but since we own the eventStore // we can be pretty confident that won't be an issue) // _eventStore.Reset(); if (error.Domain == _ekErrorDomain && error.Code == (int)EKErrorCode.CalendarReadOnly) { throw new ArgumentException(error.LocalizedDescription, "calendar", new NSErrorException(error)); } else { throw new PlatformException(error.LocalizedDescription, new NSErrorException(error)); } } return true; }
public async void Calendars_AddOrUpdateEvents_StartAfterEndThrows() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today, End = DateTime.Today.AddDays(-1) }; var calendar = new Calendar { Name = _calendarName }; await _service.AddOrUpdateCalendarAsync(calendar); Assert.IsTrue(await _service.AddOrUpdateEventAsync(calendar, calendarEvent).ThrowsAsync<ArgumentException>(), "Exception wasn't thrown"); }
/// <summary> /// Removes a calendar and all its events from the system. /// </summary> /// <param name="calendar">Calendar to delete</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteCalendarAsync(Calendar calendar) { if (string.IsNullOrEmpty(calendar.ExternalID)) { return false; } await EnsureInitializedAsync().ConfigureAwait(false); bool deleted = false; //var appCalendar = await _localApptStore.GetAppointmentCalendarAsync(calendar.ExternalID).ConfigureAwait(false); var appCalendar = await GetLocalCalendarAsync(calendar.ExternalID).ConfigureAwait(false); if (appCalendar != null) { // Perform the delete and return true await appCalendar.DeleteAsync().ConfigureAwait(false); deleted = true; } else { // Check for calendar from non-local appt store // If we get it from there, then error that it's not writeable // else, it just doesn't exist, so return false appCalendar = await _apptStore.GetAppointmentCalendarAsync(calendar.ExternalID).ConfigureAwait(false); if (appCalendar != null) { throw new ArgumentException("Cannot delete read-only calendar", "calendar"); } } return deleted; }
/// <summary> /// Add new event to a calendar or update an existing event. /// Throws if Calendar ID is empty, calendar does not exist, or calendar is read-only. /// </summary> /// <param name="calendar">Destination calendar</param> /// <param name="calendarEvent">Event to add or update</param> /// <exception cref="System.ArgumentException">Calendar is not specified, does not exist on device, or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent) { await EnsureInitializedAsync().ConfigureAwait(false); AppointmentCalendar appCalendar = null; if (string.IsNullOrEmpty(calendar.ExternalID)) { throw new ArgumentException("Missing calendar identifier", "calendar"); } else { appCalendar = await GetAndValidateLocalCalendarAsync(calendar.ExternalID).ConfigureAwait(false); } Appointment appt = null; // If Event already corresponds to an existing Appointment in the target // Calendar, then edit that instead of creating a new one. // if (!string.IsNullOrEmpty(calendarEvent.ExternalID)) { var existingAppt = await _localApptStore.GetAppointmentAsync(calendarEvent.ExternalID); if (existingAppt != null && existingAppt.CalendarId == appCalendar.LocalId) { appt = existingAppt; } } if (appt == null) { appt = new Appointment(); } appt.Subject = calendarEvent.Name; appt.Details = calendarEvent.Description ?? string.Empty; appt.StartTime = calendarEvent.Start; appt.Duration = calendarEvent.End - calendarEvent.Start; appt.AllDay = calendarEvent.AllDay; await appCalendar.SaveAppointmentAsync(appt); calendarEvent.ExternalID = appt.LocalId; }
/// <summary> /// Creates a new calendar or updates the name and color of an existing one. /// </summary> /// <param name="calendar">The calendar to create/update</param> /// <exception cref="System.ArgumentException">Calendar does not exist on device or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateCalendarAsync(Calendar calendar) { await EnsureInitializedAsync().ConfigureAwait(false); AppointmentCalendar existingCalendar = null; if (!string.IsNullOrEmpty(calendar.ExternalID)) { existingCalendar = await GetAndValidateLocalCalendarAsync(calendar.ExternalID).ConfigureAwait(false); } // Note: DisplayColor is read-only, we cannot set/update it. if (existingCalendar == null) { // Create new calendar // var appCalendar = await CreateAppCalendarAsync(calendar.Name).ConfigureAwait(false); calendar.ExternalID = appCalendar.LocalId; calendar.Color = appCalendar.DisplayColor.ToString(); } else { // Edit existing calendar // existingCalendar.DisplayName = calendar.Name; await existingCalendar.SaveAsync().ConfigureAwait(false); } }
/// <summary> /// Removes an event from the specified calendar. /// </summary> /// <param name="calendar">Calendar to remove event from</param> /// <param name="calendarEvent">Event to remove</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteEventAsync(Calendar calendar, CalendarEvent calendarEvent) { long existingId = -1; // Even though a Calendar was passed-in, we get this to both verify // that the calendar exists and to make sure we have accurate permissions // (rather than trusting the permissions that were passed to us...) // var existingCal = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existingCal == null) { return false; } else if (!existingCal.CanEditEvents) { throw new ArgumentException("Cannot delete event from readonly calendar", "calendar"); } if (long.TryParse(calendarEvent.ExternalID, out existingId)) { return await Task.Run<bool>(() => { var calendarId = GetCalendarIdForEventId(calendarEvent.ExternalID); if (calendarId.HasValue && calendarId.Value.ToString() == calendar.ExternalID) { var eventsUri = CalendarContract.Events.ContentUri; return Delete(eventsUri, existingId); } return false; }); } return false; }
/// <summary> /// Removes a calendar and all its events from the system. /// </summary> /// <param name="calendar">Calendar to delete</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteCalendarAsync(Calendar calendar) { var existing = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existing == null) { return false; } else if (!existing.CanEditCalendar) { throw new ArgumentException("Cannot delete calendar (probably because it's non-local)", "calendar"); } return await Task.Run(() => Delete(_calendarsUri, long.Parse(calendar.ExternalID))).ConfigureAwait(false); }
/// <summary> /// Gets all events for a calendar within the specified time range. /// </summary> /// <param name="calendar">Calendar containing events</param> /// <param name="start">Start of event range</param> /// <param name="end">End of event range</param> /// <returns>Calendar events</returns> /// <exception cref="System.ArgumentException">Calendar does not exist on device</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task <IList <CalendarEvent> > GetEventsAsync(Calendar calendar, DateTime start, DateTime end) { var deviceCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } var eventsUriBuilder = CalendarContract.Instances.ContentUri.BuildUpon(); // Note that this is slightly different from the GetEventById projection // due to the Instances API vs. Event API (specifically, IDs) // string[] eventsProjection = { // CalendarContract.Events.InterfaceConsts.Id, CalendarContract.Events.InterfaceConsts.Title, CalendarContract.Events.InterfaceConsts.Description, CalendarContract.Events.InterfaceConsts.Dtstart, CalendarContract.Events.InterfaceConsts.Dtend, CalendarContract.Events.InterfaceConsts.AllDay, CalendarContract.Instances.EventId }; ContentUris.AppendId(eventsUriBuilder, DateConversions.GetDateAsAndroidMS(start)); ContentUris.AppendId(eventsUriBuilder, DateConversions.GetDateAsAndroidMS(end)); var eventsUri = eventsUriBuilder.Build(); var events = new List <CalendarEvent>(); await Task.Run(() => { var cursor = Query(eventsUri, eventsProjection, string.Format("{0} = {1}", CalendarContract.Events.InterfaceConsts.CalendarId, calendar.ExternalID), null, CalendarContract.Events.InterfaceConsts.Dtstart + " ASC"); try { if (cursor.MoveToFirst()) { do { events.Add(new CalendarEvent { Name = cursor.GetString(CalendarContract.Events.InterfaceConsts.Title), ExternalID = cursor.GetString(CalendarContract.Instances.EventId), Description = cursor.GetString(CalendarContract.Events.InterfaceConsts.Description), Start = cursor.GetDateTime(CalendarContract.Events.InterfaceConsts.Dtstart), End = cursor.GetDateTime(CalendarContract.Events.InterfaceConsts.Dtend), AllDay = cursor.GetBoolean(CalendarContract.Events.InterfaceConsts.AllDay) }); } while (cursor.MoveToNext()); } } catch (Java.Lang.Exception ex) { throw new PlatformException(ex.Message, ex); } finally { cursor.Close(); } }); return(events); }
/// <summary> /// Creates a new calendar or updates the name and color of an existing one. /// </summary> /// <param name="calendar">The calendar to create/update</param> /// <exception cref="System.ArgumentException">Calendar does not exist on device or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateCalendarAsync(Calendar calendar) { bool updateExisting = false; long existingId = -1; if (long.TryParse(calendar.ExternalID, out existingId)) { var existingCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (existingCalendar != null) { if (!existingCalendar.CanEditCalendar) { throw new ArgumentException("Destination calendar is not writeable"); } updateExisting = true; } else { throw new ArgumentException("Specified calendar does not exist on device", "calendar"); } } var values = new ContentValues(); values.Put(CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName, calendar.Name); values.Put(CalendarContract.Calendars.Name, calendar.Name); // Unlike iOS/WinPhone, Android does not automatically assign a color for us, // so we use our own default of blue. // int colorInt = unchecked ((int)0xFF0000FF); if (!string.IsNullOrEmpty(calendar.Color)) { int.TryParse(calendar.Color.Trim('#'), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out colorInt); } values.Put(CalendarContract.Calendars.InterfaceConsts.CalendarColor, colorInt); if (!updateExisting) { values.Put(CalendarContract.Calendars.InterfaceConsts.CalendarAccessLevel, (int)CalendarAccess.AccessOwner); values.Put(CalendarContract.Calendars.InterfaceConsts.AccountName, AccountName); values.Put(CalendarContract.Calendars.InterfaceConsts.OwnerAccount, OwnerAccount); values.Put(CalendarContract.Calendars.InterfaceConsts.Visible, true); values.Put(CalendarContract.Calendars.InterfaceConsts.AccountType, CalendarContract.AccountTypeLocal); } await Task.Run(() => { if (updateExisting) { Update(_calendarsUri, existingId, values); } else { var uri = _calendarsUri.BuildUpon() .AppendQueryParameter(CalendarContract.CallerIsSyncadapter, "true") .AppendQueryParameter(CalendarContract.Calendars.InterfaceConsts.AccountName, AccountName) .AppendQueryParameter(CalendarContract.Calendars.InterfaceConsts.AccountType, CalendarContract.AccountTypeLocal) .Build(); calendar.ExternalID = Insert(uri, values); calendar.CanEditCalendar = true; calendar.CanEditEvents = true; calendar.Color = "#" + colorInt.ToString("x8"); } }).ConfigureAwait(false); }
public async Task Calendars_AddOrUpdateEvents_CopiesEventsBetweenCalendars() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today.AddDays(5), End = DateTime.Today.AddDays(5).AddHours(2), AllDay = false }; var calendarSource = new Calendar { Name = _calendarName }; var calendarTarget = new Calendar { Name = _calendarName + " copy destination" }; await _service.AddOrUpdateCalendarAsync(calendarSource); await _service.AddOrUpdateCalendarAsync(calendarTarget); await _service.AddOrUpdateEventAsync(calendarSource, calendarEvent); var sourceEvents = await _service.GetEventsAsync(calendarSource, DateTime.Today, DateTime.Today.AddDays(30)); await _service.AddOrUpdateEventAsync(calendarTarget, calendarEvent); var targetEvents = await _service.GetEventsAsync(calendarTarget, DateTime.Today, DateTime.Today.AddDays(30)); // Requery source events, just to be extra sure sourceEvents = await _service.GetEventsAsync(calendarSource, DateTime.Today, DateTime.Today.AddDays(30)); // Make sure the events are the same... CollectionAssert.AreEqual((ICollection)sourceEvents, (ICollection)targetEvents, _eventComparer); // ...except for their IDs! (i.e., they are actually unique copies) CollectionAssert.AreNotEqual(sourceEvents.Select(e => e.ExternalID).ToList(), targetEvents.Select(e => e.ExternalID).ToList()); }
/// <summary> /// Add new event to a calendar or update an existing event. /// Throws if Calendar ID is empty, calendar does not exist, or calendar is read-only. /// </summary> /// <param name="calendar">Destination calendar</param> /// <param name="calendarEvent">Event to add or update</param> /// <exception cref="System.ArgumentException">Calendar is not specified, does not exist on device, or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent) { await RequestCalendarAccess().ConfigureAwait(false); EKCalendar deviceCalendar = null; if (string.IsNullOrEmpty(calendar.ExternalID)) { throw new ArgumentException("Missing calendar identifier", "calendar"); } else { deviceCalendar = _eventStore.GetCalendar(calendar.ExternalID); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } } EKEvent iosEvent = null; // If Event already corresponds to an existing EKEvent in the target // Calendar, then edit that instead of creating a new one. // if (!string.IsNullOrEmpty(calendarEvent.ExternalID)) { var existingEvent = _eventStore.EventFromIdentifier(calendarEvent.ExternalID); if (existingEvent.Calendar.CalendarIdentifier == deviceCalendar.CalendarIdentifier) { iosEvent = existingEvent; } } if (iosEvent == null) { iosEvent = EKEvent.FromStore(_eventStore); } iosEvent.Title = calendarEvent.Name; iosEvent.Notes = calendarEvent.Description; iosEvent.AllDay = calendarEvent.AllDay; iosEvent.StartDate = calendarEvent.Start.ToNSDate(); // If set to AllDay and given an EndDate of 12am the next day, EventKit // assumes that the event consumes two full days. // (whereas WinPhone/Android consider that one day, and thus so do we) // iosEvent.EndDate = calendarEvent.AllDay ? calendarEvent.End.AddMilliseconds(-1).ToNSDate() : calendarEvent.End.ToNSDate(); iosEvent.Calendar = deviceCalendar; NSError error = null; if (!_eventStore.SaveEvent(iosEvent, EKSpan.ThisEvent, out error)) { // Without this, the eventStore will continue to return the "updated" // event even though the save failed! // (this obviously also resets any other changes, but since we own the eventStore // we can be pretty confident that won't be an issue) // _eventStore.Reset(); // Technically, probably any ekerrordomain error would be an ArgumentException? // - but we don't necessarily know *which* argument (at least not without the code) // - for now, just focusing on the start > end scenario and translating the rest to PlatformException. Can always add more later. if (error.Domain == _ekErrorDomain && error.Code == (int)EKErrorCode.DatesInverted) { throw new ArgumentException(error.LocalizedDescription, new NSErrorException(error)); } else { throw new PlatformException(error.LocalizedDescription, new NSErrorException(error)); } } calendarEvent.ExternalID = iosEvent.EventIdentifier; }
/// <summary> /// Removes an event from the specified calendar. /// </summary> /// <param name="calendar">Calendar to remove event from</param> /// <param name="calendarEvent">Event to remove</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteEventAsync(Calendar calendar, CalendarEvent calendarEvent) { if (string.IsNullOrEmpty(calendar.ExternalID) || string.IsNullOrEmpty(calendarEvent.ExternalID)) { return false; } await EnsureInitializedAsync().ConfigureAwait(false); bool deleted = false; //var appCalendar = await _localApptStore.GetAppointmentCalendarAsync(calendar.ExternalID).ConfigureAwait(false); var appCalendar = await GetLocalCalendarAsync(calendar.ExternalID).ConfigureAwait(false); if (appCalendar != null) { // Verify that event actually exists on calendar // var appt = await appCalendar.GetAppointmentAsync(calendarEvent.ExternalID).ConfigureAwait(false); // The second check is because AppointmentCalendar.GetAppointmentAsync will apparently still // return events if they are associated with a different calendar... // if (appt != null && appt.CalendarId == appCalendar.LocalId) { // Sometimes DeleteAppointmentAsync throws UnauthorizedException if the event doesn't exist? // And sometimes it just fails silently? // Well, hopefully the above check will help avoid either case... // await appCalendar.DeleteAppointmentAsync(calendarEvent.ExternalID).ConfigureAwait(false); deleted = true; } } else { // Check for calendar from non-local appt store // If we get it from there, then error that it's not writeable // else, it just doesn't exist, so return false appCalendar = await _apptStore.GetAppointmentCalendarAsync(calendar.ExternalID).ConfigureAwait(false); if (appCalendar != null) { throw new ArgumentException("Cannot delete event from readonly calendar", "calendar"); } } return deleted; }
/// <summary> /// Add new event to a calendar or update an existing event. /// </summary> /// <param name="calendar">Destination calendar</param> /// <param name="calendarEvent">Event to add or update</param> /// <exception cref="System.ArgumentException">Calendar is not specified, does not exist on device, or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent) { if (string.IsNullOrEmpty(calendar.ExternalID)) { throw new ArgumentException("Missing calendar identifier", "calendar"); } else { // Verify calendar exists (Android actually allows using a nonexistent calendar ID...) // var deviceCalendar = await GetCalendarByIdAsync(calendar.ExternalID).ConfigureAwait(false); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } } // Validate times if (calendarEvent.End < calendarEvent.Start) { throw new ArgumentException("End time may not precede start time", "calendarEvent"); } bool updateExisting = false; long existingId = -1; await Task.Run(() => { if (long.TryParse(calendarEvent.ExternalID, out existingId)) { var calendarId = GetCalendarIdForEventId(calendarEvent.ExternalID); if (calendarId.HasValue && calendarId.Value.ToString() == calendar.ExternalID) { updateExisting = true; } } var eventValues = new ContentValues(); eventValues.Put(CalendarContract.Events.InterfaceConsts.CalendarId, calendar.ExternalID); eventValues.Put(CalendarContract.Events.InterfaceConsts.Title, calendarEvent.Name); eventValues.Put(CalendarContract.Events.InterfaceConsts.Description, calendarEvent.Description); eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtstart, DateConversions.GetDateAsAndroidMS(calendarEvent.Start)); eventValues.Put(CalendarContract.Events.InterfaceConsts.Dtend, DateConversions.GetDateAsAndroidMS(calendarEvent.End)); eventValues.Put(CalendarContract.Events.InterfaceConsts.AllDay, calendarEvent.AllDay); eventValues.Put(CalendarContract.Events.InterfaceConsts.EventTimezone, Java.Util.TimeZone.Default.ID); if (!updateExisting) { calendarEvent.ExternalID = Insert(_eventsUri, eventValues); } else { Update(_eventsUri, existingId, eventValues); } }); }
/// <summary> /// Creates a new calendar or updates the name and color of an existing one. /// </summary> /// <param name="calendar">The calendar to create/update</param> /// <exception cref="System.ArgumentException">Calendar does not exist on device or is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task AddOrUpdateCalendarAsync(Calendar calendar) { await RequestCalendarAccess().ConfigureAwait(false); EKCalendar deviceCalendar = null; if (!string.IsNullOrEmpty(calendar.ExternalID)) { deviceCalendar = _eventStore.GetCalendar(calendar.ExternalID); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar does not exist on device", "calendar"); } } if (deviceCalendar == null) { deviceCalendar = CreateEKCalendar(calendar.Name, calendar.Color); calendar.ExternalID = deviceCalendar.CalendarIdentifier; // Update color in case iOS assigned one calendar.Color = ColorConversion.ToHexColor(deviceCalendar.CGColor); } else { deviceCalendar.Title = calendar.Name; if (!string.IsNullOrEmpty(calendar.Color)) { deviceCalendar.CGColor = ColorConversion.ToCGColor(calendar.Color); } NSError error = null; if (!_eventStore.SaveCalendar(deviceCalendar, true, out error)) { // Without this, the eventStore will continue to return the "updated" // calendar even though the save failed! // (this obviously also resets any other changes, but since we own the eventStore // we can be pretty confident that won't be an issue) // _eventStore.Reset(); if (error.Domain == _ekErrorDomain && error.Code == (int)EKErrorCode.CalendarIsImmutable) { throw new ArgumentException(error.LocalizedDescription, new NSErrorException(error)); } else { throw new PlatformException(error.LocalizedDescription, new NSErrorException(error)); } } } }
/// <summary> /// Gets all events for a calendar within the specified time range. /// </summary> /// <param name="calendar">Calendar containing events</param> /// <param name="start">Start of event range</param> /// <param name="end">End of event range</param> /// <returns>Calendar events</returns> /// <exception cref="System.ArgumentException">Calendar does not exist on device</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<IList<CalendarEvent>> GetEventsAsync(Calendar calendar, DateTime start, DateTime end) { await EnsureInitializedAsync().ConfigureAwait(false); AppointmentCalendar deviceCalendar = null; try { deviceCalendar = await _apptStore.GetAppointmentCalendarAsync(calendar.ExternalID).ConfigureAwait(false); } catch (ArgumentException ex) { throw new ArgumentException("Specified calendar not found on device", ex); } // Not all properties are populated by default // var options = new FindAppointmentsOptions { IncludeHidden = false }; options.FetchProperties.Add(AppointmentProperties.Subject); options.FetchProperties.Add(AppointmentProperties.Details); options.FetchProperties.Add(AppointmentProperties.StartTime); options.FetchProperties.Add(AppointmentProperties.Duration); options.FetchProperties.Add(AppointmentProperties.AllDay); var appointments = await deviceCalendar.FindAppointmentsAsync(start, end - start, options).ConfigureAwait(false); var events = appointments.Select(a => a.ToCalendarEvent()).ToList(); return events; }
/// <summary> /// Removes a calendar and all its events from the system. /// </summary> /// <param name="calendar">Calendar to delete</param> /// <returns>True if successfully removed</returns> /// <exception cref="System.ArgumentException">Calendar is read-only</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<bool> DeleteCalendarAsync(Calendar calendar) { if (string.IsNullOrEmpty(calendar.ExternalID)) { return false; } await RequestCalendarAccess().ConfigureAwait(false); var deviceCalendar = _eventStore.GetCalendar(calendar.ExternalID); if (deviceCalendar == null) { return false; } NSError error = null; if (!_eventStore.RemoveCalendar(deviceCalendar, true, out error)) { // Without this, the eventStore may act like the remove succeeded. // (this obviously also resets any other changes, but since we own the eventStore // we can be pretty confident that won't be an issue) // _eventStore.Reset(); throw new ArgumentException(error.LocalizedDescription, "calendar", new NSErrorException(error)); } return true; }
/// <summary> /// Not supported for Windows Store apps /// </summary> public Task<IList<CalendarEvent>> GetEventsAsync(Calendar calendar, DateTime start, DateTime end) { throw new NotSupportedException(); }
/// <summary> /// Gets all events for a calendar within the specified time range. /// </summary> /// <param name="calendar">Calendar containing events</param> /// <param name="start">Start of event range</param> /// <param name="end">End of event range</param> /// <returns>Calendar events</returns> /// <exception cref="System.ArgumentException">Calendar does not exist on device</exception> /// <exception cref="System.UnauthorizedAccessException">Calendar access denied</exception> /// <exception cref="Calendars.Plugin.Abstractions.PlatformException">Unexpected platform-specific error</exception> public async Task<IList<CalendarEvent>> GetEventsAsync(Calendar calendar, DateTime start, DateTime end) { await RequestCalendarAccess().ConfigureAwait(false); var deviceCalendar = _eventStore.GetCalendar(calendar.ExternalID); if (deviceCalendar == null) { throw new ArgumentException("Specified calendar not found on device"); } var query = _eventStore.PredicateForEvents(start.ToNSDate(), end.ToNSDate(), new EKCalendar[] { deviceCalendar }); var events = await Task.Run(() => { var iosEvents = _eventStore.EventsMatching(query); return iosEvents == null ? new List<CalendarEvent>() : iosEvents.Select(e => e.ToCalendarEvent()).ToList(); }).ConfigureAwait(false); return events; }
/// <summary> /// Not supported for Windows Store apps /// </summary> public Task AddOrUpdateCalendarAsync(Calendar calendar) { throw new NotSupportedException(); }
public async void Calendars_AddOrUpdateEvents_AddsEvents() { var events = new List<CalendarEvent> { new CalendarEvent { Name = "Bob", Description = "Bob's event", Start = DateTime.Today.AddDays(5), End = DateTime.Today.AddDays(5).AddHours(2), AllDay = false }, new CalendarEvent { Name = "Steve", Description = "Steve's event", Start = DateTime.Today.AddDays(7), End = DateTime.Today.AddDays(8), AllDay = true }, new CalendarEvent { Name = "Wheeee", Description = "Fun times", Start = DateTime.Today.AddDays(13), End = DateTime.Today.AddDays(15), AllDay = true } }; var calendar = new Calendar { Name = _calendarName }; await _service.AddOrUpdateCalendarAsync(calendar); //await _service.AddOrUpdateEventsAsync(calendar, events); foreach (var cev in events) { await _service.AddOrUpdateEventAsync(calendar, cev); } var eventResults = await _service.GetEventsAsync(calendar, DateTime.Today, DateTime.Today.AddDays(30)); Assert.That(eventResults, Is.EqualTo(events).Using<CalendarEvent>(_eventComparer)); // Extra check that DateTime.Kinds are local Assert.AreEqual(DateTimeKind.Local, eventResults.Select(e => e.Start.Kind).Distinct().Single()); Assert.AreEqual(DateTimeKind.Local, eventResults.Select(e => e.End.Kind).Distinct().Single()); }
/// <summary> /// Not supported for Windows Store apps /// </summary> public Task AddOrUpdateEventAsync(Calendar calendar, CalendarEvent calendarEvent) { throw new NotSupportedException(); }
public async void Calendars_AddOrUpdateEvent_UnspecifiedCalendarThrows() { var calendarEvent = new CalendarEvent { Name = "Bob", Start = DateTime.Today, End = DateTime.Today.AddHours(1) }; var calendar = new Calendar { Name = _calendarName }; Assert.IsTrue(await _service.AddOrUpdateEventAsync(calendar, calendarEvent).ThrowsAsync<ArgumentException>(), "Exception wasn't thrown"); }
/// <summary> /// Not supported for Windows Store apps /// </summary> public Task<bool> DeleteCalendarAsync(Calendar calendar) { throw new NotSupportedException(); }
public async void Calendars_AddOrUpdateEvents_UpdatesEvents() { // TODO: Test description var originalEvents = new List<CalendarEvent> { new CalendarEvent { Name = "Bob", Description = "Bob's event", Start = DateTime.Today.AddDays(5), End = DateTime.Today.AddDays(5).AddHours(2), AllDay = false }, new CalendarEvent { Name = "Steve", Description = "Steve's event", Start = DateTime.Today.AddDays(7), End = DateTime.Today.AddDays(8), AllDay = true }, new CalendarEvent { Name = "Wheeee", Description = "Fun times", Start = DateTime.Today.AddDays(13), End = DateTime.Today.AddDays(15), AllDay = true } }; var editedEvents = new List<CalendarEvent> { new CalendarEvent { Name = "Bob (edited)", Description = "Bob's edited event", Start = DateTime.Today.AddDays(5).AddHours(-2), End = DateTime.Today.AddDays(5).AddHours(1), AllDay = false }, new CalendarEvent { Name = "Steve (edited)", Description = "Steve's edited event", Start = DateTime.Today.AddDays(6), End = DateTime.Today.AddDays(7).AddHours(-1), AllDay = false }, new CalendarEvent { Name = "Yay (edited)", Description = "Edited fun times", Start = DateTime.Today.AddDays(12), End = DateTime.Today.AddDays(13), AllDay = true } }; var calendar = new Calendar { Name = _calendarName }; var queryStartDate = DateTime.Today; var queryEndDate = queryStartDate.AddDays(30); await _service.AddOrUpdateCalendarAsync(calendar); //await _service.AddOrUpdateEventsAsync(calendar, originalEvents); foreach (var cev in originalEvents) { await _service.AddOrUpdateEventAsync(calendar, cev); } var eventResults = await _service.GetEventsAsync(calendar, queryStartDate, queryEndDate); Assert.That(eventResults, Is.EqualTo(originalEvents).Using<CalendarEvent>(_eventComparer)); for (int i = 0; i < eventResults.Count; i++) { editedEvents.ElementAt(i).ExternalID = eventResults.ElementAt(i).ExternalID; } //await _service.AddOrUpdateEventsAsync(calendar, editedEvents); foreach (var cev in editedEvents) { await _service.AddOrUpdateEventAsync(calendar, cev); } var editedEventResults = await _service.GetEventsAsync(calendar, queryStartDate, queryEndDate); Assert.That(editedEventResults, Is.EqualTo(editedEvents).Using<CalendarEvent>(_eventComparer)); }
/// <summary> /// Not supported for Windows Store apps /// </summary> public Task<bool> DeleteEventAsync(Calendar calendar, CalendarEvent cev) { throw new NotSupportedException(); }
public async void Calendars_GetEvents_NonexistentCalendarThrows() { var calendar = new Calendar { Name = "Bob", ExternalID = "42" }; Assert.IsTrue(await _service.GetEventsAsync(calendar, DateTime.Today, DateTime.Today.AddDays(30)).ThrowsAsync<ArgumentException>(), "Exception wasn't thrown"); }
public async Task Calendars_AddOrUpdateEvents_UpdatesEvents() { var originalEvents = new List<CalendarEvent> { new CalendarEvent { Name = "Bob", Description = "Bob's event", Start = DateTime.Today.AddDays(5), End = DateTime.Today.AddDays(5).AddHours(2), AllDay = false }, new CalendarEvent { Name = "Steve", Description = "Steve's event", Start = DateTime.Today.AddDays(7), End = DateTime.Today.AddDays(8), AllDay = true }, new CalendarEvent { Name = "Wheeee", Description = "Fun times", Start = DateTime.Today.AddDays(13), End = DateTime.Today.AddDays(15), AllDay = true } }; var editedEvents = new List<CalendarEvent> { new CalendarEvent { Name = "Bob (edited)", Description = "Bob's edited event", Start = DateTime.Today.AddDays(5).AddHours(-2), End = DateTime.Today.AddDays(5).AddHours(1), AllDay = false }, new CalendarEvent { Name = "Steve (edited)", Description = "Steve's edited event", Start = DateTime.Today.AddDays(6), End = DateTime.Today.AddDays(7).AddHours(-1), AllDay = false }, new CalendarEvent { Name = "Yay (edited)", Description = "Edited fun times", Start = DateTime.Today.AddDays(12), End = DateTime.Today.AddDays(13), AllDay = true } }; var calendar = new Calendar { Name = _calendarName }; var queryStartDate = DateTime.Today; var queryEndDate = queryStartDate.AddDays(30); await _service.AddOrUpdateCalendarAsync(calendar); foreach (var cev in originalEvents) { await _service.AddOrUpdateEventAsync(calendar, cev); } var eventResults = await _service.GetEventsAsync(calendar, queryStartDate, queryEndDate); Assert.IsNotNull(eventResults); CollectionAssert.AreEqual((ICollection)originalEvents, (ICollection)eventResults, _eventComparer); for (int i = 0; i < eventResults.Count; i++) { editedEvents.ElementAt(i).ExternalID = eventResults.ElementAt(i).ExternalID; } foreach (var cev in editedEvents) { await _service.AddOrUpdateEventAsync(calendar, cev); } var editedEventResults = await _service.GetEventsAsync(calendar, queryStartDate, queryEndDate); Assert.IsNotNull(editedEventResults); CollectionAssert.AreEqual((ICollection)editedEvents, (ICollection)editedEventResults, _eventComparer); }