# Source: fs.js

    'use strict';
    'require rpc';

    /**
     * @typedef {Object} FileStatEntry
     * @memberof LuCI.fs

     * @property {string} name - Name of the directory entry
     * @property {string} type - Type of the entry, one of `block`, `char`, `directory`, `fifo`, `symlink`, `file`, `socket` or `unknown`
     * @property {number} size - Size in bytes
     * @property {number} mode - Access permissions
     * @property {number} atime - Last access time in seconds since epoch
     * @property {number} mtime - Last modification time in seconds since epoch
     * @property {number} ctime - Last change time in seconds since epoch
     * @property {number} inode - Inode number
     * @property {number} uid - Numeric owner id
     * @property {number} gid - Numeric group id
     */

    /**
     * @typedef {Object} FileExecResult
     * @memberof LuCI.fs
     *
     * @property {number} code - The exit code of the invoked command
     * @property {string} [stdout] - The stdout produced by the command, if any
     * @property {string} [stderr] - The stderr produced by the command, if any
     */

    var callFileList, callFileStat, callFileRead, callFileWrite, callFileRemove,
        callFileExec, callFileMD5;

    callFileList = rpc.declare({
    	object: 'file',
    	method: 'list',
    	params: [ 'path' ]
    });

    callFileStat = rpc.declare({
    	object: 'file',
    	method: 'stat',
    	params: [ 'path' ]
    });

    callFileRead = rpc.declare({
    	object: 'file',
    	method: 'read',
    	params: [ 'path' ]
    });

    callFileWrite = rpc.declare({
    	object: 'file',
    	method: 'write',
    	params: [ 'path', 'data', 'mode' ]
    });

    callFileRemove = rpc.declare({
    	object: 'file',
    	method: 'remove',
    	params: [ 'path' ]
    });

    callFileExec = rpc.declare({
    	object: 'file',
    	method: 'exec',
    	params: [ 'command', 'params', 'env' ]
    });

    callFileMD5 = rpc.declare({
    	object: 'file',
    	method: 'md5',
    	params: [ 'path' ]
    });

    var rpcErrors = [
    	null,
    	'InvalidCommandError',
    	'InvalidArgumentError',
    	'MethodNotFoundError',
    	'NotFoundError',
    	'NoDataError',
    	'PermissionError',
    	'TimeoutError',
    	'UnsupportedError'
    ];

    function handleRpcReply(expect, rc) {
    	if (typeof(rc) == 'number' && rc != 0) {
    		var e = new Error(rpc.getStatusText(rc)); e.name = rpcErrors[rc] || 'Error';
    		throw e;
    	}

    	if (expect) {
    		var type = Object.prototype.toString;

    		for (var key in expect) {
    			if (rc != null && key != '')
    				rc = rc[key];

    			if (rc == null || type.call(rc) != type.call(expect[key])) {
    				var e = new Error(_('Unexpected reply data format')); e.name = 'TypeError';
    				throw e;
    			}

    			break;
    		}
    	}

    	return rc;
    }

    /**
     * @class fs
     * @memberof LuCI
     * @hideconstructor
     * @classdesc
     *
     * Provides high level utilities to wrap file system related RPC calls.
     * To import the class in views, use `'require fs'`, to import it in
     * external JavaScript, use `L.require("fs").then(...)`.
     */
    var FileSystem = L.Class.extend(/** @lends LuCI.fs.prototype */ {
    	/**
    	 * Obtains a listing of the specified directory.
    	 *
    	 * @param {string} path
    	 * The directory path to list.
    	 *
    	 * @returns {Promise<LuCI.fs.FileStatEntry[]>}
    	 * Returns a promise resolving to an array of stat detail objects or
    	 * rejecting with an error stating the failure reason.
    	 */
    	list: function(path) {
    		return callFileList(path).then(handleRpcReply.bind(this, { entries: [] }));
    	},

    	/**
    	 * Return file stat information on the specified path.
    	 *
    	 * @param {string} path
    	 * The filesystem path to stat.
    	 *
    	 * @returns {Promise<LuCI.fs.FileStatEntry>}
    	 * Returns a promise resolving to a stat detail object or
    	 * rejecting with an error stating the failure reason.
    	 */
    	stat: function(path) {
    		return callFileStat(path).then(handleRpcReply.bind(this, { '': {} }));
    	},

    	/**
    	 * Read the contents of the given file and return them.
    	 * Note: this function is unsuitable for obtaining binary data.
    	 *
    	 * @param {string} path
    	 * The file path to read.
    	 *
    	 * @returns {Promise<string>}
    	 * Returns a promise resolving to a string containing the file contents or
    	 * rejecting with an error stating the failure reason.
    	 */
    	read: function(path) {
    		return callFileRead(path).then(handleRpcReply.bind(this, { data: '' }));
    	},

    	/**
    	 * Write the given data to the specified file path.
    	 * If the specified file path does not exist, it will be created, given
    	 * sufficient permissions.
    	 *
    	 * Note: `data` will be converted to a string using `String(data)` or to
    	 * `''` when it is `null`.
    	 *
    	 * @param {string} path
    	 * The file path to write to.
    	 *
    	 * @param {*} [data]
    	 * The file data to write. If it is null, it will be set to an empty
    	 * string.
    	 *
    	 * @param {number} [mode]
    	 * The permissions to use on file creation. Default is 420 (0644).
    	 *
    	 * @returns {Promise<number>}
    	 * Returns a promise resolving to `0` or rejecting with an error stating
    	 * the failure reason.
    	 */
    	write: function(path, data, mode) {
    		data = (data != null) ? String(data) : '';
    		mode = (mode != null) ? mode : 420; // 0644
    		return callFileWrite(path, data, mode).then(handleRpcReply.bind(this, { '': 0 }));
    	},

    	/**
    	 * Unlink the given file.
    	 *
    	 * @param {string}
    	 * The file path to remove.
    	 *
    	 * @returns {Promise<number>}
    	 * Returns a promise resolving to `0` or rejecting with an error stating
    	 * the failure reason.
    	 */
    	remove: function(path) {
    		return callFileRemove(path).then(handleRpcReply.bind(this, { '': 0 }));
    	},

    	/**
    	 * Execute the specified command, optionally passing params and
    	 * environment variables.
    	 *
    	 * Note: The `command` must be either the path to an executable,
    	 * or a basename without arguments in which case it will be searched
    	 * in $PATH. If specified, the values given in `params` will be passed
    	 * as arguments to the command.
    	 *
    	 * The key/value pairs in the optional `env` table are translated to
    	 * `setenv()` calls prior to running the command.
    	 *
    	 * @param {string} command
    	 * The command to invoke.
    	 *
    	 * @param {string[]} [params]
    	 * The arguments to pass to the command.
    	 *
    	 * @param {Object.<string, string>} [env]
    	 * Environment variables to set.
    	 *
    	 * @returns {Promise<LuCI.fs.FileExecResult>}
    	 * Returns a promise resolving to an object describing the execution
    	 * results or rejecting with an error stating the failure reason.
    	 */
    	exec: function(command, params, env) {
    		if (!Array.isArray(params))
    			params = null;

    		if (!L.isObject(env))
    			env = null;

    		return callFileExec(command, params, env).then(handleRpcReply.bind(this, { '': {} }));
    	},

    	/**
    	 * Read the contents of the given file, trim leading and trailing white
    	 * space and return the trimmed result. In case of errors, return an empty
    	 * string instead.
    	 *
    	 * Note: this function is useful to read single-value files in `/sys`
    	 * or `/proc`.
    	 *
    	 * This function is guaranteed to not reject its promises, on failure,
    	 * an empty string will be returned.
    	 *
    	 * @param {string} path
    	 * The file path to read.
    	 *
    	 * @returns {Promise<string>}
    	 * Returns a promise resolving to the file contents or the empty string
    	 * on failure.
    	 */
    	trimmed: function(path) {
    		return L.resolveDefault(this.read(path), '').then(function(s) {
    			return s.trim();
    		});
    	},

    	/**
    	 * Read the contents of the given file, split it into lines, trim
    	 * leading and trailing white space of each line and return the
    	 * resulting array.
    	 *
    	 * This function is guaranteed to not reject its promises, on failure,
    	 * an empty array will be returned.
    	 *
    	 * @param {string} path
    	 * The file path to read.
    	 *
    	 * @returns {Promise<string[]>}
    	 * Returns a promise resolving to an array containing the stripped lines
    	 * of the given file or `[]` on failure.
    	 */
    	lines: function(path) {
    		return L.resolveDefault(this.read(path), '').then(function(s) {
    			var lines = [];

    			s = s.trim();

    			if (s != '') {
    				var l = s.split(/\n/);

    				for (var i = 0; i < l.length; i++)
    					lines.push(l[i].trim());
    			}

    			return lines;
    		});
    	}
    });

    return FileSystem;