Click or drag to resize

ICalendarDiscovery Interface

IT Hit WebDAV Classes Reference
Assists in finding calendars on a CalDAV server. Enables calendar-home-set feature support discovery.

Namespace:  ITHit.WebDAV.Server.CalDav
Assembly:  ITHit.WebDAV.Server (in ITHit.WebDAV.Server.dll) Version: 13.3.13068
Syntax
public interface ICalendarDiscovery : ICalendarItem

The ICalendarDiscovery type exposes the following members.

Properties
  NameDescription
Public propertyCalendarHomeSetEnabled
Returns true if calendar-home-set feature is enabled, false otherwise.
Top
Methods
  NameDescription
Public methodGetCalendarHomeSetAsync
Returns list of folder items that contain calendars owned by this principal.
Top
Remarks
This interface helps finding folders that contain calendars. You will implement this interface on principal items, as well as on any other items that you wish to report calendar-home-set feature support and list folders that contain calendars owned by currently logged-in user. This interface provides GetCalendarHomeSetAsync) method that is called by the Engine when client is discovering list of folders that contain calendars.
Examples

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;
        }
    }
Examples

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;
        }
    }
Examples

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, "");
    }
}
Examples

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);
    }
}
See Also