cbfs
  • Introduction
  • Intro
    • Release History
    • About This Book
  • Getting Started
    • Installation
    • Providers
      • Local Provider
      • RAM Provider
      • S3 Provider
  • Usage
    • Disk Service
    • Disk Usage
      • Configuration Methods
      • File Methods
      • File Object
      • Directory Methods
      • Utility Methods
      • Stream Methods
      • Verification Methods
  • Digging Deeper
    • Interceptors
    • Module Disks
    • Custom Providers
Powered by GitBook
On this page

Was this helpful?

Edit on GitHub
Export as PDF
  1. Digging Deeper

Custom Providers

Learn how to build your own storage providers

You can implement your own custom Disk Provider into your applications by extending cbfs.models.AbstractDiskProvider and creating a provider which includes all of the methods listed in the sample component below:

component extends="cbfs.models.AbstractDiskProvider"{

	/**
	 * Get the unique UUID identifier for this disk
	 */
	string function getIdentifier();

	/**
	 * Returns true if the disk has been started up, false if not.
	 */
	boolean function hasStarted();

	/**
	 * Returns the name of the disk.
	 */
	string function getName();

	/**
	 * Retrieves the settings for the disk.
	 */
	struct function getProperties();

	/**
	 * Startup a disk provider with the instance data it needs to startup. It needs to make sure
	 * that it sets the "started" variable to true in order to operate.
	 *
	 * @name       The name of the disk
	 * @properties A struct of configuration data for this provider, usually coming from the configuration file
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 */
	any function startup( required string name, struct properties = {} );

	/**
	 * Called before the cbfs module is unloaded, or via reinits. This can be implemented
	 * as you see fit to gracefully shutdown connections, sockets, etc.
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 */
	any function shutdown();

	/**
	 * Create a file in the disk
	 *
	 * @path       The file path to use for storage
	 * @contents   The contents of the file to store
	 * @visibility The storage visibility of the file, available options are `public, private, readonly` or a custom data type the implemented driver can interpret
	 * @metadata   Struct of metadata to store with the file
	 * @override   Flag to overwrite the file at the destination, if it exists. Defaults to true.
	 * @mode       Applies to *nix systems. If passed, it overrides the visbility argument and uses these octal values instead
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws cbfs.FileOverrideException - When a file exists and no override has been provided
	 */
	function create(
		required path,
		required contents,
		string visibility,
		struct metadata,
		boolean overwrite = true,
		string mode
	);

	/**
	 * Uploads a file in to the disk
	 *
	 * @fieldName The file field name
	 * @directory the directory on disk to upload to
	 * @overload  We can overload the default because we can go directly to the disk with the file
	 */
	function upload( required fieldName, required directory );

	/**
	 * Set the storage visibility of a file, available options are `public, private, readonly` or a custom data type the implemented driver can interpret
	 *
	 * @path       The target file
	 * @visibility The storage visibility of the file, available options are `public, private, readonly` or a custom data type the implemented driver can interpret
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	any function setVisibility( required string path, required string visibility );

	/**
	 * Get the storage visibility of a file, the return format can be a string of `public, private, readonly` or a custom data type the implemented driver can interpret.
	 *
	 * @path The target file
	 *
	 * @return The visibility of the requested file
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	string function visibility( required string path );

	/**
	 * Prepend contents to the beginning of a file. If the file is missing and the throwOnMissing if false
	 * We will create the file with the contents provided.
	 *
	 * @path           The file path to use for storage
	 * @contents       The contents of the file to prepend
	 * @metadata       Struct of metadata to store with the file
	 * @throwOnMissing Boolean flag to throw if the file is missing. Otherwise it will be created if missing.
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	any function prepend(
		required string path,
		required contents,
		struct metadata,
		boolean throwOnMissing
	);

	/**
	 * Append contents to the end of a file. If the file is missing and the throwOnMissing if false
	 * We will create the file with the contents provided.
	 *
	 * @path           The file path to use for storage
	 * @contents       The contents of the file to append
	 * @metadata       Struct of metadata to store with the file
	 * @throwOnMissing Boolean flag to throw if the file is missing. Otherwise it will be created if missing.
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	any function append(
		required string path,
		required contents,
		struct metadata,
		boolean throwOnMissing
	);

	/**
	 * Copy a file from one destination to another
	 *
	 * @source      The source file path
	 * @destination The end destination path
	 * @override    Flag to overwrite the file at the destination, if it exists. Defaults to true.
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	any function copy(
		required source,
		required destination,
		boolean overwrite = true
	);

	/**
	 * Move a file from one destination to another
	 *
	 * @source      The source file path
	 * @destination The end destination path
	 * @override    Flag to overwrite the file at the destination, if it exists. Defaults to true.
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	any function move(
		required source,
		required destination,
		boolean overwrite = true
	);

	/**
	 * Get the contents of a file
	 *
	 * @path The file path to retrieve
	 *
	 * @return The contents of the file
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	any function get( required path );

	/**
	 * Get the contents of a file as binary, such as an executable or image
	 *
	 * @path The file path to retrieve
	 *
	 * @return A binary representation of the file
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	any function getAsBinary( required path );

	/**
	 * Validate if a file/directory exists
	 *
	 * @path The file/directory path to verify
	 */
	boolean function exists( required string path );

	/**
	 * Validate if a file/directory doesn't exist
	 *
	 * @path The file/directory path to verify
	 */
	boolean function missing( required string path );

	/**
	 * Delete a file or an array of file paths. If a file does not exist a `false` will be
	 * shown for it's return.
	 *
	 * @path           A single file path or an array of file paths
	 * @throwOnMissing Boolean to throw an exception if the file is missing.
	 *
	 * @return boolean or struct report of deletion
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	boolean function delete( required any path, boolean throwOnMissing );

	/**
	 * Create a new empty file if it does not exist
	 *
	 * @path       The file path
	 * @createPath if set to false, expects all parent directories to exist, true will generate necessary directories. Defaults to true.
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws cbfs.PathNotFoundException
	 */
	function touch( required path, boolean createpath );

	/**************************************** UTILITY METHODS ****************************************/

	/**
	 * Get the full url for the given file
	 *
	 * @path The file path to build the uri for
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	string function url( required string path )

	/**
	 * Get the URI for the given file
	 *
	 * @path The file path to build the URI for
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	string function uri( required string path );

	/**
	 * Retrieve the size of the file in bytes
	 *
	 * @path The file path location
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	numeric function size( required path );

	/**
	 * Retrieve the file's last modified timestamp
	 *
	 * @path The file path location
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	function lastModified( required path );

	/**
	 * Retrieve the file's mimetype
	 *
	 * @path The file path location
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	function mimeType( required path );

	/**
	 * Return information about the file.  Will contain keys such as lastModified, size, path, name, type, canWrite, canRead, isHidden and more
	 *
	 * @path The file path
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	struct function info( required path );

	/**
	 * Generate checksum for a file in different hashing algorithms
	 *
	 * @path      The file path
	 * @algorithm Default is MD5, but SHA-1, SHA-256, and SHA-512 can also be used.
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	string function checksum( required path, algorithm );

	/**
	 * Extract the file name from a file path
	 *
	 * @path The file path
	 */
	string function name( required path );

	/**
	 * Extract the extension from the file path
	 *
	 * @path The file path
	 */
	string function extension( required path );

	/**
	 * Sets the access attributes of the file on Unix based disks
	 *
	 * @path The file path
	 * @mode Access mode, the same attributes you use for the Linux command `chmod`
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 */
	function chmod( required string path, required string mode );

	/**
	 * Create a symbolic link in the system if it supports it.
	 *
	 * The target parameter is the target of the link. It may be an absolute or relative path and may not exist. When the target is a relative path then file system operations on the resulting link are relative to the path of the link.
	 *
	 * @link   The path of the symbolic link to create
	 * @target The target of the symbolic link
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws UnsupportedOperationException - if the implementation does not support symbolic links
	 */
	function createSymbolicLink( required link, required target );

	/**************************************** VERIFICATION METHODS ****************************************/

	/**
	 * Verifies if the passed path is an existent file
	 *
	 * @path The file path
	 *
	 * @throws cbfs.FileNotFoundException
	 */
	boolean function isFile( required path );

	/**
	 * Is the file writable or not
	 *
	 * @path The file path
	 *
	 * @throws cbfs.FileNotFoundException - If the filepath is missing
	 */
	boolean function isWritable( required path );

	/**
	 * Is the file readable or not
	 *
	 * @path The file path
	 *
	 * @throws cbfs.FileNotFoundException - If the filepath is missing
	 */
	boolean function isReadable( required path );

	/**
	 * Is the file executable or not
	 *
	 * @path The file path
	 *
	 * @throws cbfs.FileNotFoundException - If the filepath is missing
	 */
	boolean function isExecutable( required path );

	/**
	 * Is the file is hidden or not
	 *
	 * @path The file path
	 *
	 * @throws cbfs.FileNotFoundException - If the filepath is missing
	 */
	boolean function isHidden( required path );

	/**
	 * Is the file is a symbolic link
	 *
	 * @path The file path
	 *
	 * @throws cbfs.FileNotFoundException - If the filepath is missing
	 */
	boolean function isSymbolicLink( required path );

	/**************************************** STREAM METHODS ****************************************/

	/**
	 * Return a Java stream of the file using non-blocking IO classes. The stream will represent every line in the file so you can navigate through it.
	 * This method leverages the `cbstreams` library used accordingly by implementations (https://www.forgebox.io/view/cbstreams)
	 *
	 * @path
	 *
	 * @return Stream object: See https://apidocs.ortussolutions.com/coldbox-modules/cbstreams/1.1.0/index.html
	 */
	function stream( required path );

	/**
	 * Create a Java stream of the incoming array of files/directories usually called from this driver as well.
	 * <pre>
	 * disk.streamOf( disk.files( "my.path" ) )
	 * 	.filter( function( item ){
	 *		return item.startsWith( "a" );
	 *	} )
	 *	.forEach( function( item ){
	 *		writedump( item );
	 *	} );
	 * </pre>
	 *
	 * @target The target array of files/directories to generate a stream of
	 *
	 * @return Stream object: See https://apidocs.ortussolutions.com/coldbox-modules/cbstreams/1.1.0/index.html
	 */
	function streamOf( required array target );

	/**************************************** DIRECTORY METHODS ****************************************/

	/**
	 * Is the path a directory or not
	 *
	 * @path The directory path
	 *
	 * @throws cbfs.DirectoryNotFoundException - If the directory path is missing
	 */
	boolean function isDirectory( required path );

	/**
	 * Create a new directory
	 *
	 * @directory    The directory path to be created
	 * @createPath   Create parent directory paths when they do not exist. The default is true
	 * @ignoreExists If false, it will throw an error if the directory already exists, else it ignores it if it exists. This should default to true.
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws DirectoryExistsException - If the directory you are trying to create already exists and <code>ignoreExists</code> is true
	 */
	function createDirectory(
		required directory,
		boolean createPath   = true,
		boolean ignoreExists = true
	);

	/**
	 * Copies a directory to a destination
	 *
	 * The `filter` argument can be a closure and lambda with the following format
	 * <pre>
	 * boolean:function( path )
	 * </pre>
	 *
	 * @source      The source directory
	 * @destination The destination directory
	 * @recurse     If true, copies all subdirectories, otherwise only files in the source directory. Default is false.
	 * @filter      A string file extension filter to apply like *.jpg or server-*.json or a lambda/closure that receives the file path and should return true to copy it.
	 * @createPath  If false, expects all parent directories to exist, true will generate all necessary directories. Default is true.
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws cbfs.DirectoryNotFoundException - When the source does not exist
	 */
	function copyDirectory(
		required source,
		required destination,
		boolean recurse = false,
		any filter,
		boolean createPath = true
	);

	/**
	 * Move a directory
	 *
	 * @source      The source directory
	 * @destination The destination directory
	 * @createPath  If false, expects all parent directories to exist, true will generate all necessary directories. Default is true.
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws cbfs.DirectoryNotFoundException - When the old path does not exist
	 */
	function moveDirectory(
		required source,
		required destination,
		boolean createPath = true
	);

	/**
	 * Delete 1 or more directory locations
	 *
	 * @directory      The directory or an array of directories
	 * @recurse        Recurse the deletion or not, defaults to true
	 * @throwOnMissing Throws an exception if the directory does not exist, defaults to false
	 *
	 * @return A boolean value or a struct of booleans determining if the directory paths got deleted or not.
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	boolean function deleteDirectory(
		required string directory,
		boolean recurse,
		boolean throwOnMissing
	);

	/**
	 * Empty the specified directory of all files and folders.
	 *
	 * @directory      The directory
	 * @throwOnMissing Throws an exception if the directory does not exist, defaults to false
	 *
	 * @return cbfs.models.AbstractDiskProvider
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	function cleanDirectory( required directory, boolean throwOnMissing );

	/**
	 * Get an array listing of all files and directories in a directory.
	 *
	 * @directory The directory
	 * @filter    A string wildcard or a lambda/closure that receives the file path and should return true to include it in the returned array or not.
	 * @sort      Columns by which to sort. e.g. Directory, Size DESC, DateLastModified.
	 * @recurse   Recurse into subdirectories, default is false
	 * @type      Filter the result to only include files, directories, or both. ('file|files', 'dir|directory', 'all'). Default is 'all'
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	array function contents(
		required directory,
		any filter,
		sort,
		boolean recurse = false,
		type            = "all"
	);

	/**
	 * Get an array listing of all files and directories in a directory using recursion
	 *
	 * @directory The directory
	 * @filter    A string wildcard or a lambda/closure that receives the file path and should return true to include it in the returned array or not.
	 * @sort      Columns by which to sort. e.g. Directory, Size DESC, DateLastModified.
	 * @type      Filter the result to only include files, directories, or both. ('file|files', 'dir|directory', 'all'). Default is 'all'
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	array function allContents(
		required directory,
		any filter,
		sort,
		type = "all"
	);

	/**
	 * Get an array of all files in a directory.
	 *
	 * @directory The directory
	 * @filter    A string wildcard or a lambda/closure that receives the file path and should return true to include it in the returned array or not.
	 * @sort      Columns by which to sort. e.g. Directory, Size DESC, DateLastModified.
	 * @recurse   Recurse into subdirectories, default is false
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	array function files(
		required directory,
		any filter,
		sort,
		boolean recurse = false
	);

	/**
	 * Get an array of all directories in a directory.
	 *
	 * @directory The directory
	 * @filter    A string wildcard or a lambda/closure that receives the file path and should return true to include it in the returned array or not.
	 * @sort      Columns by which to sort. e.g. Directory, Size DESC, DateLastModified.
	 * @recurse   Recurse into subdirectories, default is false
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	array function directories(
		required directory,
		any filter,
		sort,
		boolean recurse = false
	);

	/**
	 * Get an array of all files in a directory using recursion, this is a shortcut to the `files()` with recursion
	 *
	 * @directory The directory
	 * @filter    A string wildcard or a lambda/closure that receives the file path and should return true to include it in the returned array or not.
	 * @sort      Columns by which to sort. e.g. Directory, Size DESC, DateLastModified.
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	array function allFiles( required directory, any filter, sort );

	/**
	 * Get an array of all directories in a directory using recursion
	 *
	 * @directory The directory
	 * @filter    A string wildcard or a lambda/closure that receives the file path and should return true to include it in the returned array or not.
	 * @sort      Columns by which to sort. e.g. Directory, Size DESC, DateLastModified.
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	array function allDirectories( required directory, any filter, sort );

	/**
	 * Get an array of structs of all files in a directory and their appropriate information map:
	 * - Attributes
	 * - DateLastModified
	 * - Directory
	 * - Link
	 * - Mode
	 * - Name
	 * - Size
	 * - etc
	 *
	 * @directory The directory
	 * @filter    A string wildcard or a lambda/closure that receives the file path and should return true to include it in the returned array or not.
	 * @sort      Columns by which to sort. e.g. Directory, Size DESC, DateLastModified.
	 * @recurse   Recurse into subdirectories, default is false
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	array function filesMap(
		required directory,
		any filter,
		sort,
		boolean recurse = false
	);

	/**
	 * Get an array of structs of all files in a directory with recursion and their appropriate information map:
	 * - Attributes
	 * - DateLastModified
	 * - Directory
	 * - Link
	 * - Mode
	 * - Name
	 * - Size
	 * - etc
	 *
	 * @directory The directory
	 * @filter    A string wildcard or a lambda/closure that receives the file path and should return true to include it in the returned array or not.
	 * @sort      Columns by which to sort. e.g. Directory, Size DESC, DateLastModified.
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	array function allFilesMap( required directory, any filter, sort );

	/**
	 * Get an array of content from all the files from a specific directory
	 *
	 * @directory The directory
	 * @filter    A string wildcard or a lambda/closure that receives the file path and should return true to include it in the returned array or not.
	 * @sort      Columns by which to sort. e.g. Directory, Size DESC, DateLastModified.
	 * @recurse   Recurse into subdirectories, default is false
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	array function contentsMap(
		required directory,
		any filter,
		sort,
		boolean recurse = false
	);

	/**
	 * Get an array of content from all the files from a specific directory with recursion
	 *
	 * @directory The directory
	 * @filter    A string wildcard or a lambda/closure that receives the file path and should return true to include it in the returned array or not.
	 * @sort      Columns by which to sort. e.g. Directory, Size DESC, DateLastModified.
	 *
	 * @throws cbfs.DirectoryNotFoundException
	 */
	array function allContentsMap( required directory, any filter, sort );

	/**
	 * Find path names matching a given globbing pattern
	 *
	 * @pattern The globbing pattern to match
	 */
	array function glob( required pattern );

}

Testing

To create your own provider tests, create a new Spec which extends cbfs.models.testing.AbstractDiskSpec

Within your tests directory you will need to add a file at tests/resources/assets/binary_file.png which will be used to test binary uploads for the provider you create. Example:

omponent extends="cbfs.models.testing.AbstractDiskSpec" {

	// The name of the provider in the test-harness we want to test
	variables.providerName = "MyCustom";
	variables.TEST_PATH    = expandPath( "/tests/storage" );
	// Which features does this disk support for testing
	variables.testFeatures = { symbolicLink : true };

	function beforeAll(){
		if ( directoryExists( variables.TEST_PATH ) ) {
			directoryDelete( variables.TEST_PATH, true );
		}
		super.beforeAll();
	}

	function run(){
		super.run();

		// Localized Suites

		describe( "MyCustom Provider Extended Specs", function(){
			beforeEach( function( currentSpec ){
				disk = getDisk();
			} );

			story( "I want to test some custom function in my provider", function(){
				...
			} );

		} );
	}

	/**
	 * ------------------------------------------------------------
	 * Concrete Expectations that can be implemented by each provider
	 * ------------------------------------------------------------
	 */
	function validateInfoStruct( required info, required disk ){
		expect( info ).toHaveKey( "path,size,name,type,canWrite,canRead,isHidden" );
	}

}
PreviousModule Disks

Last updated 2 years ago

Was this helpful?