Click or drag to resize

IAddressbookDiscovery Interface

IT Hit WebDAV Classes Reference
Assists in finding address books on a CardDAV server. Enables addressbook-home-set feature support discovery.

Namespace:  ITHit.WebDAV.Server.CardDav
Assembly:  ITHit.WebDAV.Server (in ITHit.WebDAV.Server.dll) Version: 13.3.13068
Syntax
public interface IAddressbookDiscovery : IAddressbookItem

The IAddressbookDiscovery type exposes the following members.

Properties
  NameDescription
Public propertyAddressbookHomeSetEnabled
Returns true if addressbook-home-set feature is enabled, false otherwise.
Top
Methods
  NameDescription
Public methodGetAddressbookHomeSetAsync
Returns list of folder items that contain address books owned by this principal.
Top
Remarks
This interface helps finding folders that contain address books. You will implement this interface on principal items, as well as on any other items that you wish to report addressbook-home-set feature support and list folders that contain address books owned by currently logged-in user. This interface provides GetAddressbookHomeSetAsync) method that is called by the Engine when client is discovering list of folders that contain address books.
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 'CardDAVServer.SqlStorage.AspNet' C# & VB samples provided with the SDK.

public class AddressbooksRootFolder : LogicalFolder, IFolder
{
    private static readonly string addressbooksRootFolderName = "addressbooks";

    public static string AddressbooksRootFolderPath = DavLocationFolder.DavLocationFolderPath + addressbooksRootFolderName + '/';

    public AddressbooksRootFolder(DavContext context)
        : base(context, AddressbooksRootFolderPath)
    {
    }

    public override async Task<PageResults> GetChildrenAsync(IList<PropertyName> propNames, long? offset, long? nResults, IList<OrderProperty> orderProps)
    {
        // Here we list addressbooks from back-end storage. 
        // You can filter addressbooks if requied and return only addressbooks that user has access to.
        return new PageResults((await AddressbookFolder.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 AddressbookFolder.CreateAddressbookFolderAsync(Context, name, "");
    }
}
Examples

The code below is part of 'CardDAVServer.SqlStorage.AspNet' C# & VB samples provided with the SDK.

public class AddressbookFolder : DavHierarchyItem, IAddressbookFolder, ICurrentUserPrincipal, IAclHierarchyItem
{
    public static async Task<IAddressbookFolder> LoadByIdAsync(DavContext context, Guid addressbookFolderId)
    {
        // Load only address book that the use has access to. 
        // Also load complete ACL for this address book.
        string sql =
            @"SELECT * FROM [card_AddressbookFolder] 
              WHERE [AddressbookFolderId] = @AddressbookFolderId
              AND [AddressbookFolderId] IN (SELECT [AddressbookFolderId] FROM [card_Access] WHERE [UserId]=@UserId)

            ; SELECT * FROM [card_Access]
              WHERE [AddressbookFolderId] = @AddressbookFolderId
              AND [AddressbookFolderId] IN (SELECT [AddressbookFolderId] FROM [card_Access] WHERE [UserId]=@UserId)";

        return (await LoadAsync(context, sql,
              "@UserId"             , context.UserId
            , "@AddressbookFolderId", addressbookFolderId
            )).FirstOrDefault();
    }

    public static async Task<IEnumerable<IAddressbookFolder>> LoadAllAsync(DavContext context)
    {
        // Load only address books that the use has access to. 
        // Also load complete ACL for each address book, but only if user has access to that address book.
        string sql =
            @"SELECT * FROM [card_AddressbookFolder] 
              WHERE [AddressbookFolderId] IN (SELECT [AddressbookFolderId] FROM [card_Access] WHERE [UserId]=@UserId)

            ; SELECT * FROM [card_Access] 
              WHERE [AddressbookFolderId] IN (SELECT [AddressbookFolderId] FROM [card_Access] WHERE [UserId]=@UserId)";

        return await LoadAsync(context, sql, "@UserId", context.UserId);
    }

    private static async Task<IEnumerable<IAddressbookFolder>> LoadAsync(DavContext context, string sql, params object[] prms)
    {
        IList<IAddressbookFolder> addressbookFolders = new List<IAddressbookFolder>();

        using (SqlDataReader reader = await context.ExecuteReaderAsync(sql, prms))            
        {
            DataTable addressbooks = new DataTable();
            addressbooks.Load(reader);

            DataTable access = new DataTable();
            access.Load(reader);

            foreach (DataRow rowAddressbookFolder in addressbooks.Rows)
            {
                Guid addressbookFolderId = rowAddressbookFolder.Field<Guid>("AddressbookFolderId");

                string filter = string.Format("AddressbookFolderId = '{0}'", addressbookFolderId);
                DataRow[] rowsAccess = access.Select(filter);

                addressbookFolders.Add(new AddressbookFolder(context, addressbookFolderId, rowAddressbookFolder, rowsAccess));
            }
        }

        return addressbookFolders;
    }

    internal static async Task<IAddressbookFolder> CreateAddressbookFolderAsync(DavContext context, string name, string description)
    {
        // 1. Create address book.
        // 2. Grant owner privileges to the user on the created address book(s).
        string sql = @"INSERT INTO [card_AddressbookFolder] (
                      [AddressbookFolderId]
                    , [Name]
                    , [Description]
                ) VALUES (
                      @AddressbookFolderId
                    , @Name
                    , @Description
                )
                ; INSERT INTO [card_Access] (
                      [AddressbookFolderId]
                    , [UserId]
                    , [Owner]
                    , [Read]
                    , [Write]
                ) VALUES (
                      @AddressbookFolderId
                    , @UserId
                    , @Owner
                    , @Read
                    , @Write
                )";

        Guid addressbookFolderId = Guid.NewGuid();

        await context.ExecuteNonQueryAsync(sql,
              "@AddressbookFolderId", addressbookFolderId
            , "@Name"               , name
            , "@Description"        , description
            , "@UserId"             , context.UserId
            , "@Owner"              , true
            , "@Read"               , true
            , "@Write"              , true
            );
        return await LoadByIdAsync(context, addressbookFolderId);
    }

    private readonly Guid addressbookFolderId;

    private readonly DataRow rowAddressbookFolder;

    private readonly DataRow[] rowsAccess;

    public override string Name
    {
        get { return rowAddressbookFolder != null ? rowAddressbookFolder.Field<string>("Name") : null; }
    }

    public override string Path
    {
        get
        {
            return string.Format("{0}{1}/", AddressbooksRootFolder.AddressbooksRootFolderPath, addressbookFolderId);
        }
    }

    private AddressbookFolder(DavContext context, Guid addressbookFolderId, DataRow addressbook, DataRow[] rowsAccess)
        : base(context)
    {
        this.addressbookFolderId    = addressbookFolderId;
        this.rowAddressbookFolder   = addressbook;
        this.rowsAccess             = rowsAccess;
    }

    public async Task<IEnumerable<ICardFile>> MultiGetAsync(IEnumerable<string> pathList, IEnumerable<PropertyName> propNames)
    {
        // Get list of file names from path list.
        IEnumerable<string> fileNames = pathList.Select(a => System.IO.Path.GetFileNameWithoutExtension(a));

        return await CardFile.LoadByFileNamesAsync(Context, fileNames, PropsToLoad.All);
    }

    public async Task<IEnumerable<ICardFile>> 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<ICardFile>();
    }

    public string AddressbookDescription 
    {
        get { return rowAddressbookFolder.Field<string>("Description"); }
    }

    public async Task<PageResults> GetChildrenAsync(IList<PropertyName> propNames, long? offset, long? nResults, IList<OrderProperty> orderProps)
    {
        // Here we enumerate all business cards contained in this address book.
        // 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() method call, that follows this request.

        // Bynari submits PROPFIND without props - Engine will request getcontentlength

        IList<IHierarchyItem> children = new List<IHierarchyItem>();
        return new PageResults((await CardFile.LoadByAddressbookFolderIdAsync(Context, addressbookFolderId, PropsToLoad.Minimum)), null);
    }

    public async Task<IFile> CreateFileAsync(string name, Stream content, string contentType, long totalFileSize)
    {
        // The actual business card file is created in datatbase in CardFile.Write call.
        string fileName = System.IO.Path.GetFileNameWithoutExtension(name);
        return CardFile.CreateCardFile(Context, addressbookFolderId, fileName);
    }

    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 addressbooks renaming. Check that user has permissions to write.
        string sql = @"UPDATE [card_AddressbookFolder] SET Name=@Name 
            WHERE [AddressbookFolderId]=@AddressbookFolderId
            AND [AddressbookFolderId] IN (SELECT [AddressbookFolderId] FROM [card_Access] WHERE [UserId]=@UserId AND [Write] = 1)";

        if (await Context.ExecuteNonQueryAsync(sql, 
              "@Name"               , destName
            , "@UserId"             , Context.UserId
            , "@AddressbookFolderId", addressbookFolderId) < 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 address book and all vCards associated with it. Check that user has permissions to delete.
        string sql = @"DELETE FROM [card_AddressbookFolder] 
            WHERE [AddressbookFolderId]=@AddressbookFolderId
            AND [AddressbookFolderId] IN (SELECT [AddressbookFolderId] FROM [card_Access] WHERE [UserId]=@UserId AND [Owner] = 1)";

        if (await Context.ExecuteNonQueryAsync(sql,
              "@UserId"             , Context.UserId
            , "@AddressbookFolderId", addressbookFolderId) < 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 [card_AddressbookFolderProperty] WHERE [AddressbookFolderId] = @AddressbookFolderId",
                "@AddressbookFolderId", addressbookFolderId);

        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 (reader.Read())
            {
                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 [card_AddressbookFolderProperty]
              WHERE [AddressbookFolderId] = @AddressbookFolderId AND [Name] = @Name AND [Namespace] = @Namespace";

        int count = await Context.ExecuteScalarAsync<int>(
            selectCommand,
            "@AddressbookFolderId"  , addressbookFolderId,
            "@Name"                 , prop.QualifiedName.Name,
            "@Namespace"            , prop.QualifiedName.Namespace);

        // insert
        if (count == 0)
        {
            string insertCommand = @"INSERT INTO [card_AddressbookFolderProperty] ([AddressbookFolderId], [Name], [Namespace], [PropVal])
                                      VALUES(@AddressbookFolderId, @Name, @Namespace, @PropVal)";

            await Context.ExecuteNonQueryAsync(
                insertCommand,
                "@PropVal"              , prop.Value,
                "@AddressbookFolderId"  , addressbookFolderId,
                "@Name"                 , prop.QualifiedName.Name,
                "@Namespace"            , prop.QualifiedName.Namespace);
        }
        else
        {
            // update
            string command = @"UPDATE [card_AddressbookFolderProperty]
                  SET [PropVal] = @PropVal
                  WHERE [AddressbookFolderId] = @AddressbookFolderId AND [Name] = @Name AND [Namespace] = @Namespace";

            await Context.ExecuteNonQueryAsync(
                command,
                "@PropVal"              , prop.Value,
                "@AddressbookFolderId"  , addressbookFolderId,
                "@Name"                 , prop.QualifiedName.Name,
                "@Namespace"            , prop.QualifiedName.Namespace);
        }
    }

    private async Task RemovePropertyAsync(string name, string ns)
    {
        string command = @"DELETE FROM [card_AddressbookFolderProperty]
                          WHERE [AddressbookFolderId] = @AddressbookFolderId
                          AND [Name] = @Name
                          AND [Namespace] = @Namespace";

        await Context.ExecuteNonQueryAsync(
            command,
            "@AddressbookFolderId"  , addressbookFolderId,
            "@Name"                 , name,
            "@Namespace"            , ns);
    }

    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