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 within a  . 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   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).

Files will be stored using the provider configured within the CentralizedFileStorage section of environment configuration .

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() and IsAccessibleAsync() methods. 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 System.Threading.Tasks;
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);
		}

		public Task<bool> IsAccessibleAsync(string path, string fileName)
		{
			return TaskUtility.FromSync<bool>(() => IsAccessible(path, fileName));
		}

		#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 ISecuredCentralizedFileStore  interface on the existing ICentralizedFileStore and define the UserHasAccess() and UserHasAccessAsync() methods. 

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 System.Threading.Tasks;
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;
		}

		public Task<bool> UserHasAccessAsync(int userId, string path, string fileName)
		{
			return TaskUtility.FromSync<bool>(() => UserHasAccess(userId, path, fileName));
		}

		#endregion
	}
}

This sample uses the Users API to retrieve user details for the userId provided to the UserHasAccess() and UserHasAccessAsync() methods 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. If the IFindableCentralizedFileStore can find (or generate the file), it can return the resolved file.  If the file cannot be found or generated, returning null will serve the regular 404/not-found response. 

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

using System;
using System.Threading.Tasks;
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 CentralizedFile FindFile(string fileStoreKey, string path, string fileName)
		{
			return CentralizedFileStorage.GetFileStore(fileStoreKey)?.GetFile("notfound.png");
		}

		public async Task<CentralizedFile> FindFileAsync(string fileStoreKey, string path, string fileName)
		{
			var fileStore = CentralizedFileStorage.GetFileStore(fileStoreKey);
			if (fileStore == null)
				return null;

			return await fileStore.GetFileAsync("notfound.png");
		}

		#endregion
	}
}