Namespace: ITHit.WebDAV.Server.CalDav
The ICalendarDiscovery type exposes the following members.
Name | Description | |
---|---|---|
CalendarHomeSetEnabled |
Returns true if calendar-home-set feature is enabled, false otherwise.
|
Name | Description | |
---|---|---|
GetCalendarHomeSetAsync |
Returns list of folder items that contain calendars owned by this principal.
|
The code below is part of 'CalDAVServer.SqlStorage.AspNet' C# & VB samples provided with the SDK.
public class Discovery : ICalendarDiscovery { protected DavContext Context; public Discovery(DavContext context) { this.Context = context; } public async Task<IEnumerable<IItemCollection>> GetCalendarHomeSetAsync() { return new[] { new CalendarsRootFolder(Context) }; } public bool CalendarHomeSetEnabled { get { return true; } }
The code below is part of 'CardDAVServer.SqlStorage.AspNet' C# & VB samples provided with the SDK.
public class Discovery : IAddressbookDiscovery { protected DavContext Context; public Discovery(DavContext context) { this.Context = context; } public async Task<IEnumerable<IItemCollection>> GetAddressbookHomeSetAsync() { return new[] { new AddressbooksRootFolder(Context) }; } public bool AddressbookHomeSetEnabled { get { return true; } }
The code below is part of 'CalDAVServer.SqlStorage.AspNet' C# & VB samples provided with the SDK.
public class CalendarsRootFolder : LogicalFolder, IFolder { private static readonly string calendarsRootFolderName = "calendars"; public static string CalendarsRootFolderPath = DavLocationFolder.DavLocationFolderPath + calendarsRootFolderName + '/'; public CalendarsRootFolder(DavContext context) : base(context, CalendarsRootFolderPath) { } public override async Task<PageResults> GetChildrenAsync(IList<PropertyName> propNames, long? offset, long? nResults, IList<OrderProperty> orderProps) { // Here we list calendars from back-end storage. // You can filter calendars if requied and return only calendars that user has access to. return new PageResults((await CalendarFolder.LoadAllAsync(Context)).OrderBy(x => x.Name), null); } public Task<IFile> CreateFileAsync(string name, Stream content, string contentType, long totalFileSize) { throw new DavException("Not implemented.", DavStatus.NOT_IMPLEMENTED); } public async Task<IFolder> CreateFolderAsync(string name) { return await CalendarFolder.CreateCalendarFolderAsync(Context, name, ""); } }
The code below is part of 'CalDAVServer.SqlStorage.AspNet' C# & VB samples provided with the SDK.
// Note: // - Mozilla Thunderbird Lightning requires ICurrentUserPrincipal on calendar folder, it does not support discovery. // - Outlook CalDAV Synchronizer requires IAclHierarchyItem on calendar folder. public class CalendarFolder : DavHierarchyItem, ICalendarFolder, IAppleCalendar, ICurrentUserPrincipal, IAclHierarchyItem { public static async Task<ICalendarFolder> LoadByIdAsync(DavContext context, Guid calendarFolderId) { // Load only calendar that the use has access to. // Also load complete ACL for this calendar. string sql = @"SELECT * FROM [cal_CalendarFolder] WHERE [CalendarFolderId] = @CalendarFolderId AND [CalendarFolderId] IN (SELECT [CalendarFolderId] FROM [cal_Access] WHERE [UserId]=@UserId) ; SELECT * FROM [cal_Access] WHERE [CalendarFolderId] = @CalendarFolderId AND [CalendarFolderId] IN (SELECT [CalendarFolderId] FROM [cal_Access] WHERE [UserId]=@UserId)"; return (await LoadAsync(context, sql, "@UserId", context.UserId , "@CalendarFolderId", calendarFolderId )).FirstOrDefault(); } public static async Task<IEnumerable<ICalendarFolder>> LoadAllAsync(DavContext context) { // Load only calendars that the use has access to. // Also load complete ACL for each calendar, but only if user has access to that calendar. string sql = @"SELECT * FROM [cal_CalendarFolder] WHERE [CalendarFolderId] IN (SELECT [CalendarFolderId] FROM [cal_Access] WHERE [UserId]=@UserId) ; SELECT * FROM [cal_Access] WHERE [CalendarFolderId] IN (SELECT [CalendarFolderId] FROM [cal_Access] WHERE [UserId]=@UserId)"; return await LoadAsync(context, sql, "@UserId", context.UserId); } private static async Task<IEnumerable<ICalendarFolder>> LoadAsync(DavContext context, string sql, params object[] prms) { IList<ICalendarFolder> calendarFolders = new List<ICalendarFolder>(); using (SqlDataReader reader = await context.ExecuteReaderAsync(sql, prms)) { DataTable calendars = new DataTable(); calendars.Load(reader); DataTable access = new DataTable(); access.Load(reader); foreach (DataRow rowCalendarFolder in calendars.Rows) { Guid calendarFolderId = rowCalendarFolder.Field<Guid>("CalendarFolderId"); string filter = string.Format("CalendarFolderId = '{0}'", calendarFolderId); DataRow[] rowsAccess = access.Select(filter); calendarFolders.Add(new CalendarFolder(context, calendarFolderId, rowCalendarFolder, rowsAccess)); } } return calendarFolders; } public static async Task<ICalendarFolder> CreateCalendarFolderAsync(DavContext context, string name, string description) { // 1. Create calendar. // 2. Grant owner privileges to the user on the created calendar. string sql = @"INSERT INTO [cal_CalendarFolder] ( [CalendarFolderId] , [Name] , [Description] ) VALUES ( @CalendarFolderId , @Name , @Description ) ; INSERT INTO [cal_Access] ( [CalendarFolderId] , [UserId] , [Owner] , [Read] , [Write] ) VALUES ( @CalendarFolderId , @UserId , @Owner , @Read , @Write )"; Guid calendarFolderId = Guid.NewGuid(); await context.ExecuteNonQueryAsync(sql, "@CalendarFolderId" , calendarFolderId , "@Name" , name , "@Description" , description , "@UserId" , context.UserId , "@Owner" , true , "@Read" , true , "@Write" , true ); return await LoadByIdAsync(context, calendarFolderId); } private readonly Guid calendarFolderId; private readonly DataRow rowCalendarFolder; private readonly DataRow[] rowsAccess; public override string Name { get { return rowCalendarFolder != null ? rowCalendarFolder.Field<string>("Name") : null; } } public override string Path { get { return string.Format("{0}{1}/", CalendarsRootFolder.CalendarsRootFolderPath, calendarFolderId); } } private CalendarFolder(DavContext context, Guid calendarFolderId, DataRow calendar, DataRow[] rowsAccess) : base(context) { this.calendarFolderId = calendarFolderId; this.rowCalendarFolder = calendar; this.rowsAccess = rowsAccess; } public async Task<IEnumerable<ICalendarFile>> MultiGetAsync(IEnumerable<string> pathList, IEnumerable<PropertyName> propNames) { // Get list of UIDs from path list. IEnumerable<string> uids = pathList.Select(a => System.IO.Path.GetFileNameWithoutExtension(a)); return await CalendarFile.LoadByUidsAsync(Context, uids, PropsToLoad.All); } public async Task<IEnumerable<ICalendarFile>> QueryAsync(string rawQuery, IEnumerable<PropertyName> propNames) { // For the sake of simplicity we just call GetChildren returning all items. // Typically you will return only items that match the query. return (await GetChildrenAsync(propNames.ToList(), null, null, null)).Page.Cast<ICalendarFile>(); } public IEnumerable<CalendarComponentType> SupportedComponentTypes { get { return new[] { CalendarComponentType.VEVENT, CalendarComponentType.VTODO, }; } } public string CalendarDescription { get { return rowCalendarFolder.Field<string>("Description"); } } public ulong MaxResourceSize { get { return ulong.MaxValue; } } public ulong MaxInstances { get { return ulong.MaxValue; } } public ulong MaxAttendeesPerInstance { get { return ulong.MaxValue; } } public DateTime UtcMinDateTime { get { return DateTime.MinValue.ToUniversalTime(); } } public DateTime UtcMaxDateTime { get { return DateTime.MaxValue.ToUniversalTime(); } } public async Task<PageResults> GetChildrenAsync(IList<PropertyName> propNames, long? offset, long? nResults, IList<OrderProperty> orderProps) { // Here we enumerate all events and to-dos contained in this calendar. // You can filter children items in this implementation and // return only items that you want to be available for this // particular user. // Typically only getcontenttype and getetag properties are requested in GetChildren call by CalDAV/CardDAV clients. // The iCalendar/vCard (calendar-data/address-data) is typically requested not in GetChildren, but in a separate multiget // report, in MultiGetAsync, that follow this request. // Bynari submits PROPFIND without props - Engine will request getcontentlength IList<IHierarchyItem> children = new List<IHierarchyItem>(); return new PageResults((await CalendarFile.LoadByCalendarFolderIdAsync(Context, calendarFolderId, PropsToLoad.Minimum)), null); } public async Task<IFile> CreateFileAsync(string name, Stream content, string contentType, long totalFileSize) { // The actual event or to-do object is created in datatbase in CardFile.Write call. return CalendarFile.CreateCalendarFile(Context, calendarFolderId); } public async Task<IFolder> CreateFolderAsync(string name) { throw new DavException("Not allowed.", DavStatus.NOT_ALLOWED); } public override async Task MoveToAsync(IItemCollection destFolder, string destName, MultistatusException multistatus) { // Here we support only calendars renaming. Check that user has permissions to write. string sql = @"UPDATE [cal_CalendarFolder] SET Name=@Name WHERE [CalendarFolderId]=@CalendarFolderId AND [CalendarFolderId] IN (SELECT [CalendarFolderId] FROM [cal_Access] WHERE [UserId] = @UserId AND [Write] = 1)"; if (await Context.ExecuteNonQueryAsync(sql, "@UserId" , Context.UserId , "@CalendarFolderId" , calendarFolderId , "@Name" , destName) < 1) { throw new DavException("Item not found or you do not have enough permissions to complete this operation.", DavStatus.FORBIDDEN); } } public override async Task DeleteAsync(MultistatusException multistatus) { // Delete calendar and all events / to-dos associated with it. Check that user has permissions to delete. string sql = @"DELETE FROM [cal_CalendarFolder] WHERE [CalendarFolderId]=@CalendarFolderId AND [CalendarFolderId] IN (SELECT [CalendarFolderId] FROM [cal_Access] WHERE [UserId] = @UserId AND [Owner] = 1)"; if (await Context.ExecuteNonQueryAsync(sql, "@UserId" , Context.UserId , "@CalendarFolderId" , calendarFolderId) < 1) { throw new DavException("Item not found or you do not have enough permissions to complete this operation.", DavStatus.FORBIDDEN); } } public override async Task<IEnumerable<PropertyValue>> GetPropertiesAsync(IList<PropertyName> names, bool allprop) { IList<PropertyValue> propVals = await GetPropertyValuesAsync( "SELECT [Name], [Namespace], [PropVal] FROM [cal_CalendarFolderProperty] WHERE [CalendarFolderId] = @CalendarFolderId", "@CalendarFolderId", calendarFolderId); if (allprop) { return propVals; } else { IList<PropertyValue> requestedPropVals = new List<PropertyValue>(); foreach (PropertyValue p in propVals) { if (names.Contains(p.QualifiedName)) { requestedPropVals.Add(p); } } return requestedPropVals; } } public override async Task UpdatePropertiesAsync( IList<PropertyValue> setProps, IList<PropertyName> delProps, MultistatusException multistatus) { foreach (PropertyValue p in setProps) { await SetPropertyAsync(p); // create or update property } foreach (PropertyName p in delProps) { await RemovePropertyAsync(p.Name, p.Namespace); } } private async Task<IList<PropertyValue>> GetPropertyValuesAsync(string command, params object[] prms) { List<PropertyValue> l = new List<PropertyValue>(); using (SqlDataReader reader = await Context.ExecuteReaderAsync(command, prms)) { while (await reader.ReadAsync()) { string name = reader.GetString(reader.GetOrdinal("Name")); string ns = reader.GetString(reader.GetOrdinal("Namespace")); string value = reader.GetString(reader.GetOrdinal("PropVal")); l.Add(new PropertyValue(new PropertyName(name, ns), value)); } } return l; } private async Task SetPropertyAsync(PropertyValue prop) { string selectCommand = @"SELECT Count(*) FROM [cal_CalendarFolderProperty] WHERE [CalendarFolderId] = @CalendarFolderId AND [Name] = @Name AND [Namespace] = @Namespace"; int count = await Context.ExecuteScalarAsync<int>( selectCommand, "@CalendarFolderId" , calendarFolderId, "@Name" , prop.QualifiedName.Name, "@Namespace" , prop.QualifiedName.Namespace); // insert if (count == 0) { string insertCommand = @"INSERT INTO [cal_CalendarFolderProperty] ([CalendarFolderId], [Name], [Namespace], [PropVal]) VALUES(@CalendarFolderId, @Name, @Namespace, @PropVal)"; await Context.ExecuteNonQueryAsync( insertCommand, "@PropVal" , prop.Value, "@CalendarFolderId" , calendarFolderId, "@Name" , prop.QualifiedName.Name, "@Namespace" , prop.QualifiedName.Namespace); } else { // update string command = @"UPDATE [cal_CalendarFolderProperty] SET [PropVal] = @PropVal WHERE [CalendarFolderId] = @CalendarFolderId AND [Name] = @Name AND [Namespace] = @Namespace"; await Context.ExecuteNonQueryAsync( command, "@PropVal" , prop.Value, "@CalendarFolderId" , calendarFolderId, "@Name" , prop.QualifiedName.Name, "@Namespace" , prop.QualifiedName.Namespace); } } private async Task RemovePropertyAsync(string name, string ns) { string command = @"DELETE FROM [cal_CalendarFolderProperty] WHERE [CalendarFolderId] = @CalendarFolderId AND [Name] = @Name AND [Namespace] = @Namespace"; await Context.ExecuteNonQueryAsync( command, "@CalendarFolderId" , calendarFolderId, "@Name" , name, "@Namespace" , ns); } public IEnumerable<AppleAllowedSharingMode> AllowedSharingModes { get { return new[] { AppleAllowedSharingMode.CanBePublished, AppleAllowedSharingMode.CanBeShared, }; } } public async Task UpdateSharingAsync(IList<AppleShare> sharesToAddAndRemove) { // Drop all shares first regardless of operation order. When resending // invitations Apple Calendar drops and adds shares for the user in one \ // request. foreach (AppleShare share in sharesToAddAndRemove) { if (share.Operation == AppleSharingOperation.Withdraw) { // remove sharing here // share.Address // share.CommonName } } // Add new shares foreach (AppleShare share in sharesToAddAndRemove) { if (share.Operation != AppleSharingOperation.Withdraw) { // enable sharing and send invitation here // share.Address // share.CommonName } } } public async Task<IEnumerable<SharingInvite>> GetInviteAsync() { IList<SharingInvite> invites = new List<SharingInvite>(); foreach (DataRow rowAccess in rowsAccess) { if (rowAccess.Field<bool>("Owner")) continue; string userId = rowAccess.Field<string>("UserId"); System.Web.Security.MembershipUser user = System.Web.Security.Membership.GetUser(userId); SharingInvite ace = new SharingInvite { Address = string.Format("email:{0}", user.Email) , Access = rowAccess.Field<bool>("Write") ? SharingInviteAccess.ReadWrite : SharingInviteAccess.Read , CommonName = user.UserName , Status = SharingInviteStatus.Accepted }; } return invites; } public async Task<CalendarSharedBy> GetSharedByAsync() { if (rowsAccess.Any(x => !x.Field<bool>("Owner"))) { return CalendarSharedBy.NotShared; } string ownerId = rowsAccess.First(x => x.Field<bool>("Owner")).Field<string>("UserId"); if (ownerId.Equals(Context.UserId, StringComparison.InvariantCultureIgnoreCase)) { return CalendarSharedBy.SharedByOwner; } else { return CalendarSharedBy.Shared; } } public Task SetOwnerAsync(IPrincipal value) { throw new DavException("Not implemented.", DavStatus.NOT_IMPLEMENTED); } public async Task<IPrincipal> GetOwnerAsync() { DataRow rowOwner = rowsAccess.FirstOrDefault(x => x.Field<bool>("Owner") == true); if (rowOwner == null) return null; return await Acl.User.GetUserAsync(Context, rowOwner.Field<string>("UserId")); } public Task SetGroupAsync(IPrincipal value) { throw new DavException("Group cannot be set", DavStatus.FORBIDDEN); } public async Task<IPrincipal> GetGroupAsync() { return null; // Groups are not supported. } public async Task<IEnumerable<SupportedPrivilege>> GetSupportedPrivilegeSetAsync() { return new[] { new SupportedPrivilege { Privilege = Privilege.Read, IsAbstract = false, DescriptionLanguage = "en", Description = "Allows or denies the user the ability to read content and properties of files/folders." }, new SupportedPrivilege { Privilege = Privilege.Write, IsAbstract = false, DescriptionLanguage = "en", Description = "Allows or denies locking an item or modifying the content, properties, or membership of a collection." } }; } public async Task<IEnumerable<Privilege>> GetCurrentUserPrivilegeSetAsync() { DataRow rowAccess = rowsAccess.FirstOrDefault(x => x.Field<string>("UserId") == Context.UserId); if (rowAccess == null) return null; List<Privilege> privileges = new List<Privilege>(); if (rowAccess.Field<bool>("Read")) privileges.Add(Privilege.Read); if (rowAccess.Field<bool>("Write")) privileges.Add(Privilege.Write); return privileges; } public async Task<IEnumerable<ReadAce>> GetAclAsync(IList<PropertyName> propertyNames) { IList<ReadAce> aceList = new List<ReadAce>(); foreach (DataRow rowAccess in rowsAccess) { ReadAce ace = new ReadAce(); ace.Principal = await Acl.User.GetUserAsync(Context, rowAccess.Field<string>("UserId")); if (rowAccess.Field<bool>("Read")) ace.GrantPrivileges.Add(Privilege.Read); if (rowAccess.Field<bool>("Write")) ace.GrantPrivileges.Add(Privilege.Write); ace.IsProtected = rowAccess.Field<bool>("Owner"); aceList.Add(ace); } return aceList; } public Task SetAclAsync(IList<WriteAce> aces) { throw new DavException("Not implemented.", DavStatus.NOT_IMPLEMENTED); } public async Task<AclRestriction> GetAclRestrictionsAsync() { return new AclRestriction { NoInvert = true, GrantOnly = true }; } public async Task<IEnumerable<IHierarchyItem>> GetInheritedAclSetAsync() { return new IHierarchyItem[] { }; } public async Task<IEnumerable<IPrincipalFolder>> GetPrincipalCollectionSetAsync() { return new IPrincipalFolder[] { new Acl.UsersFolder(Context) }; } public async Task<IPrincipal> ResolveWellKnownPrincipalAsync(WellKnownPrincipal wellKnownPrincipal) { return null; } public Task<IEnumerable<IAclHierarchyItem>> GetItemsByPropertyAsync(MatchBy matchBy, IList<PropertyName> props) { throw new DavException("Not implemented.", DavStatus.NOT_IMPLEMENTED); } }