Article Interacting With Files

The Verint Community platform provides support for creating file stores within the Centralized File Storage System to enable plugins to store, secure, serve, and generate files.

Why Would I Create a File Store?

A file store is a unique set of paths and files located within the Centralized File Storage System (CFS). Each file store is a self-contained set of files that can be independently secured and located on the storage provider (operating system file system, Amazon S3, etc) of choice. 

When implementing a plugin that needs to store files, it is best to store those files in the CFS within a file store that you maintain. It is not advised to reuse an existing file store.

Creating a File Store

Creating a new file store within the CFS requires implementing the ICentralizedFileStore plugin type. ICentralizedFileStore adds only a single member to IPlugin, the programmatic name of the file store. The name must be unique and must meet the naming guidelines of the CFS.

As a sample, the is the full implementation of a sample file store with the key "sample":

using System;
using Telligent.Evolution.Extensibility.Version1;
using Telligent.Evolution.Extensibility.Storage.Version1;

namespace Samples
{
    public class SampleFileStore : IPlugin, ICentralizedFileStore
    {
        #region IPlugin Implementation

        public string Name
        {
            get
            {
                return "Sample File Store";
            }
        }

        public string Description
        {
            get
            {
                return "A sample store of files.";
            }
        }

        public void Initialize()
        {
        }

        #endregion

        #region ICentralizedFileStore Implementation

        public string FileStoreKey
        {
            get
            {
                return "samples";
            }
        }

        #endregion
    }
}

Once the plugin is installed and enabled, the "sample" file store can be used by this or other plugins to add, remove or serve files (see Centralized File Storage for examples of CFS usage).

By default, the files of a file store are stored within the default file store configured in the communityserver.config file, however, the CFS configuration can be customized to explicitly identify where specify which file storage provider (file system, Amazon S3, etc) should be used for each file store. This configuration is out of scope for this topic, however.

Securing a File Store

File stores are always fully accessible to code, however, the Verint Community platform enables securing the accessibility of users to read files by URL using extension plugin types to ICentralizedFileStore: IGloballySecuredCentralizedFileStore and ISecuredCentralizedFileStore.

Globally Secured File Stores

Globally secured file stores are the simplest form of secured file storage -- either everyone has access to a file or no one has access to a file. The accessibility is always global (applying to all users).

To implement a globally secured file store, implement the IGloballySecuredCentralizedFileStore interface on your existing ICentralizedFileStore and define the accessibility logic within the IsAccessible() method. For example, below is a complete example showing an implementation of IGloballySecuredCentralizedFileStore that disables all access to files within the path "secured" in the "sample" file store:

using System;
using Telligent.Evolution.Extensibility.Version1;
using Telligent.Evolution.Extensibility.Storage.Version1;

namespace Samples
{
    public class SampleGloballySecuredFileStore : IPlugin, ICentralizedFileStore, IGloballySecuredCentralizedFileStore
    {
        #region IPlugin Implementation

        public string Name
        {
            get
            {
                return "Sample File Store";
            }
        }

        public string Description
        {
            get
            {
                return "A sample store of files.";
            }
        }

        public void Initialize()
        {
        }

        #endregion

        #region ICentralizedFileStore Implementation

        public string FileStoreKey
        {
            get
            {
                return "samples";
            }
        }

        #endregion

        #region IGloballySecuredCentralizedFileStore

        public bool IsAccessible(string path, string fileName)
        {
            return string.IsNullOrEmpty(path) || !path.StartsWith("secure", StringComparison.OrdinalIgnoreCase);
        }

        #endregion
    }
}

User-specific Secured File Stores

Unlike globally-secured file stores, user-specific secured file stores can be secured differently depending on the user attempting to access a file by URL. To implement a user-specific secured file store, implement the ISecuredFileStore interface on the existing ICentralizedFileStore and define the UserHasAccess() method. 

For example, I've secured the original "sample" file store so that only registered users (non-anonymous users) can access any files within the file store:

using System;
using Telligent.Evolution.Extensibility.Version1;
using Telligent.Evolution.Extensibility.Storage.Version1;
using Telligent.Evolution.Extensibility.Api.Version1;
using Telligent.Evolution.Extensibility;

namespace Samples
{
    public class SampleSecuredFileStore : IPlugin, ICentralizedFileStore, ISecuredCentralizedFileStore
    {
        #region IPlugin Implementation

        public string Name
        {
            get
            {
                return "Sample File Store";
            }
        }

        public string Description
        {
            get
            {
                return "A sample store of files.";
            }
        }

        public void Initialize()
        {
        }

        #endregion

        #region ICentralizedFileStore Implementation

        public string FileStoreKey
        {
            get
            {
                return "samples";
            }
        }

        #endregion

        #region ISecuredCentralizedFileStore

        public bool UserHasAccess(int userId, string path, string fileName)
        {
            var userApi = Apis.Get<IUsers>();
            if (userApi == null)
                return false;

            var user = userApi.Get(new UsersGetOptions
            {
                Id = userId
            });

            return user != null && !user.HasErrors() && user.Username != userApi.AnonymousUserName;
        }

        #endregion
    }
}

This sample uses the Users API to retrieve user details for the userId provided to the UserHasAccess() method and to determine if the provided user is anonymous.

Generating Files Or Redirecting Within a File Store

At times, it may be necessary to redirect files within a file store or generate files on first access. For example, if a file must be processed and the processing is intensive (for example, LESS files processing or image resizing), the result should be stored, but it may be worthwhile to wait until the file is requested to create the processed version of the file.

To enable these scenarios, the Verint Community platform defines the IFindableCentralizedFileStore extension to ICentralizedFileStore. This extension provides a file store with the opportunity to be notified when a file being requested doesn't exist and enable the file store to adjust the file request.

When a file isn't found, the IFindableCentralizedFileStore is provided with the file store key, path, and file name requested as referenced parameters. If the IFindableCentralizedFileStore can find (or generate the file), it can update these identifiers and return true (to identify the file as found).  If the file cannot be found or generated, returning false will serve the regular 404/not-found response. The finding process is generally transparent to the user, but may require an HTTP redirect depending on the file storage provider configured to serve the file store.

In the following sample, I've updated the original sample file store to also implement IFindableCentralizedFileStore with FindFile() defined to redirect any missing files to the "notfound.png" file in the root of the file store:

using System;
using Telligent.Evolution.Extensibility.Version1;
using Telligent.Evolution.Extensibility.Storage.Version1;

namespace Samples
{
    public class SampleFindableFileStore : IPlugin, ICentralizedFileStore, IFindableCentralizedFileStore
    {
        #region IPlugin Implementation

        public string Name
        {
            get
            {
                return "Sample File Store";
            }
        }

        public string Description
        {
            get
            {
                return "A sample store of files.";
            }
        }

        public void Initialize()
        {
        }

        #endregion

        #region ICentralizedFileStore Implementation

        public string FileStoreKey
        {
            get
            {
                return "samples";
            }
        }

        #endregion

        #region IFindableCentralizedFileStore

        public bool FindFile(ref string fileStoreKey, ref string path, ref string fileName)
        {
            path = string.Empty;
            fileName = "notfound.png";
            return true;
        }

        #endregion
    }
}