nativeloop
Version:
⭐ Axway Amplify module for using nativeloop with Appcelerator Titanium SDK Framework
896 lines (798 loc) • 21.7 kB
JavaScript
;
/***
* __ _ __ __
* ____ ___ ____ / /_ (_)/ /___ / /_ ___ _____ ____
* / __ `__ \ / __ \ / __ \ / // // _ \ / __ \ / _ \ / ___// __ \
* / / / / / // /_/ // /_/ // // // __// / / // __// / / /_/ /
* /_/ /_/ /_/ \____//_.___//_//_/ \___//_/ /_/ \___//_/ \____/
*
* mobile solutions for everyday heroes
*
* @file This is a partially implemented replacement for node.js fs module.
* This module is a fork of https://github.com/tonylukasavage/ti-fs
* @module nativeloop/fs
* @author Brenton House <brenton.house@gmail.com>
* @version 1.0.0
* @since 1.0.0
* @copyright Copyright (c) 2017 by Superhero Studios Incorporated. All Rights Reserved.
* @license Licensed under the terms of the MIT License (MIT)
*
*/
var $F = Ti.Filesystem;
var fs = exports;
var util = require( 'util' );
var IS_ANDROID = Ti.Platform.osname === 'android';
var MODE_MAP = {};
MODE_MAP[ 'r' ] = MODE_MAP[ 'r+' ] = MODE_MAP[ 'rs' ] = MODE_MAP[ 'rs+' ] = $F.MODE_READ;
MODE_MAP[ 'w' ] = MODE_MAP[ 'w+' ] = MODE_MAP[ 'wx' ] = MODE_MAP[ 'wx+' ] = $F.MODE_WRITE;
MODE_MAP[ 'a' ] = MODE_MAP[ 'a+' ] = MODE_MAP[ 'ax' ] = MODE_MAP[ 'ax+' ] = $F.MODE_APPEND;
fs.Stats = function Stats( path ) {
this.__file = null;
this.dev = 0;
this.ino = 0;
this.nlink = 0;
this.uid = 0;
this.gid = 0;
this.rdev = 0;
this.size = 0;
this.blksize = 4096;
this.blocks = 8;
this.ctime = this.atime = this.mtime = 0;
if( path ) {
this.__file = $F.getFile( path );
if( !this.__file.exists() ) {
throw new Error( 'file does not exist' );
}
this.size = this.__file.size;
this.mode = 0;
this.ctime = new Date( this.__file.createTimestamp() );
this.atime = this.mtime = new Date( this.__file.modificationTimestamp() );
}
};
fs.Stats.prototype.isDirectory = function( property ) {
return this.__file.isDirectory();
};
fs.Stats.prototype.isFile = function( property ) {
return this.__file.isFile();
};
fs.Stats.prototype.isBlockDevice = function( property ) {
return false;
};
fs.Stats.prototype.isCharacterDevice = function( property ) {
return false;
};
fs.Stats.prototype.isSymbolicLink = function( property ) {
return this.__file.symbolicLink;
};
fs.Stats.prototype.isFIFO = function( property ) {
return false;
};
fs.Stats.prototype.isSocket = function( property ) {
return false;
};
fs.exists = function exists( path, callback ) {
setTimeout( function() {
return callback( fs.existsSync( path ) );
}, 0 );
};
fs.existsSync = function existsSync( path ) {
return $F.getFile( path ).exists();
};
fs.readFile = function readFile( path, options, callback_ ) {
var callback = maybeCallback( arguments[ arguments.length - 1 ] );
if( !options || util.isFunction( options ) ) {
options = {
encoding: null,
flag: 'r'
};
} else if( util.isString( options ) ) {
options = {
encoding: options,
flag: 'r'
};
} else if( !util.isObject( options ) ) {
throw new TypeError( 'Bad arguments' );
}
var encoding = options.encoding,
flag = options.flag || 'r';
assertEncoding( options.encoding );
fs.open( path, flag, function( err, fd ) {
if( err ) {
return callback( err );
}
fs.fstat( fd, function( err, stats ) {
if( err ) {
return callback( err );
}
var buffer = Ti.createBuffer( {
length: stats.size
} );
fs.read( fd, buffer, function( err, data ) {
if( err ) {
return callback( err );
}
fs.close( fd, function( err ) {
if( err ) {
return callback( err );
}
return callback( err, encoding ? convertBuffer( buffer, encoding ) : buffer );
} );
} );
} );
} );
};
fs.readFileSync = function readFileSync( path, options ) {
if( !options ) {
options = {
encoding: null,
flag: 'r'
};
} else if( util.isString( options ) ) {
options = {
encoding: options,
flag: 'r'
};
} else if( !util.isObject( options ) ) {
throw new TypeError( 'Bad arguments' );
}
var encoding = options.encoding,
flag = options.flag || 'r';
assertEncoding( options.encoding );
var fd = fs.openSync( path, flag /*, mode */ ),
size = fs.fstatSync( fd ).size,
buffer = Ti.createBuffer( {
length: size
} );
fs.readSync( fd, buffer );
fs.closeSync( fd );
return encoding ? convertBuffer( buffer, encoding ) : buffer;
};
fs.close = function close( fd, callback ) {
setTimeout( function() {
var err = null;
try {
fd.close();
} catch( e ) {
err = e;
}
return callback( err );
}, 0 );
};
fs.closeSync = function closeSync( fd ) {
fd.close();
};
fs.open = function open( path, flags, mode, callback ) {
callback = maybeCallback( arguments[ arguments.length - 1 ] );
if( !mode || util.isFunction( mode ) ) {
mode = null;
}
setTimeout( function() {
var fd = null,
err = null;
try {
fd = fs.openSync( path, flags, mode );
} catch( e ) {
err = e;
}
return callback( err, fd );
}, 0 );
};
fs.openSync = function openSync( path, flags, mode ) {
var tiMode = assertFlags( flags ),
file = $F.getFile( path );
if( tiMode === $F.MODE_APPEND && !fs.existsSync( path ) ) {
file.open( $F.MODE_WRITE ).close();
}
var fd = file.open( tiMode );
fd.__path = path;
return fd;
};
fs.read = function read( fd, buffer, offset, length, position, callback ) {
// position is not handled in Titanium streams
callback = maybeCallback( arguments[ arguments.length - 1 ] );
if( util.isFunction( position ) ) {
position = undefined;
}
if( util.isFunction( length ) ) {
length = undefined;
}
if( util.isFunction( offset ) ) {
offset = undefined;
}
// TODO: This should be Ti.Stream.read(), but it doesn't appear to do
// anything when targeting a FileStream, despite the docs.
setTimeout( function() {
var bytes = null,
err = null;
try {
bytes = fs.readSync( fd, buffer, offset, length, position );
} catch( e ) {
err = e;
}
return callback( err, bytes, buffer );
}, 0 );
};
// Android improperly handles undefined args passed to offset and/or length
if( IS_ANDROID ) {
fs.readSync = function readSync( fd, buffer, offset, length, position ) {
if( offset == null && length == null ) {
return fd.read( buffer );
} else {
return fd.read( buffer, offset, length );
}
};
} else {
fs.readSync = function readSync( fd, buffer, offset, length, position ) {
return fd.read( buffer, offset, length );
};
}
fs.write = function write( fd, buffer, offset, length, position, callback ) {
// position is not handled in Titanium streams
callback = maybeCallback( arguments[ arguments.length - 1 ] );
if( util.isFunction( position ) ) {
position = undefined;
}
if( util.isFunction( length ) ) {
length = undefined;
}
if( util.isFunction( offset ) ) {
offset = undefined;
}
// TODO: This should be Ti.Stream.write(), but it doesn't appear to do
// anything when targeting a FileStream, despite the docs.
setTimeout( function() {
var bytes = null,
err = null;
try {
bytes = fs.writeSync( fd, buffer, offset, length, position );
} catch( e ) {
err = e;
}
return callback( err, bytes, buffer );
}, 0 );
};
// Android improperly handles undefined args passed to offset and/or length
if( IS_ANDROID ) {
fs.writeSync = function writeSync( fd, buffer, offset, length, position ) {
if( offset == null && length == null ) {
return fd.write( buffer );
} else {
return fd.write( buffer, offset, length );
}
};
} else {
fs.writeSync = function writeSync( fd, buffer, offset, length, position ) {
return fd.write( buffer, offset, length );
};
}
fs.rename = function rename( oldPath, newPath, callback ) {
setTimeout( function() {
var err = null,
good = false;
try {
good = $F.getFile( oldPath ).move( newPath );
if( !good ) {
err = new Error( 'could not move file' );
}
} catch( e ) {
err = e;
}
return callback( err );
}, 0 );
};
fs.renameSync = function renameSync( oldPath, newPath ) {
$F.getFile( oldPath ).move( newPath );
};
fs.truncate = function truncate( path, len, callback ) {
callback = maybeCallback( arguments[ arguments.length - 1 ] );
if( !len || util.isFunction( len ) ) {
len = 0;
}
if( len ) {
fs.open( path, 'r', function( err, fd ) {
if( err ) {
return callback( err );
}
var buffer = Ti.createBuffer( {
length: len
} );
fs.read( fd, buffer, 0, len, function( err, bytes, buffer ) {
if( err ) {
return callback( err );
}
fs.close( fd, function( err ) {
if( err ) {
return callback( err );
}
fs.writeFile( path, buffer, callback );
} );
} );
} );
} else {
fs.writeFile( path, '', callback );
}
};
fs.truncateSync = function truncateSync( path, len ) {
len = len || 0;
if( len ) {
var fd = fs.openSync( path, 'r' ),
buffer = Ti.createBuffer( {
length: len
} );
fs.readSync( fd, buffer, 0, len );
fs.closeSync( fd );
fs.writeFileSync( path, buffer );
} else {
fs.writeFileSync( path, '' );
}
};
fs.ftruncate = function ftruncate( fd, len, callback ) {
if( !fd.__path ) {
throw new Error( 'invalid file descriptor' );
}
callback = maybeCallback( arguments[ arguments.length - 1 ] );
if( !len || util.isFunction( len ) ) {
len = 0;
}
if( len ) {
var buffer = Ti.createBuffer( {
length: len
} );
fs.open( fd.__path, 'r', function( err, fd2 ) {
if( err ) {
return callback( err );
}
fs.read( fd2, buffer, 0, len, function( err, bytes, buffer ) {
if( err ) {
return callback( err );
}
fs.close( fd2, function( err ) {
if( err ) {
return callback( err );
}
fs.writeFile( fd.__path, buffer, callback );
} );
} );
} );
} else {
fs.writeFile( fd.__path, '', callback );
}
};
fs.ftruncateSync = function ftruncateSync( fd, len ) {
len = len || 0;
if( !fd.__path ) {
throw new Error( 'invalid file descriptor' );
}
if( len ) {
var buffer = Ti.createBuffer( {
length: len
} ),
fd2 = fs.openSync( fd.__path, 'r' );
fs.readSync( fd2, buffer, 0, len );
fs.closeSync( fd2 );
fs.writeFileSync( fd.__path, buffer );
} else {
fs.writeFileSync( fd.__path, '' );
}
};
fs.rmdir = function rmdir( path, callback ) {
setTimeout( function() {
var err = null;
try {
if( !$F.getFile( path ).deleteDirectory() ) {
err = new Error( 'could not delete directory' );
}
} catch( e ) {
err = e;
}
return callback( err );
}, 0 );
};
fs.rmdirSync = function rmdirSync( path ) {
if( !$F.getFile( path ).deleteDirectory() ) {
throw new Error( 'could not delete directory' );
}
};
if( IS_ANDROID ) {
fs.mkdir = function mkdir( path, mode, callback ) {
callback = maybeCallback( arguments[ arguments.length - 1 ] );
setTimeout( function() {
var err = null;
try {
$F.getFile( path ).createDirectory();
if( !$F.getFile( path ).exists() ) {
throw new Error( 'could not create directory' );
}
} catch( e ) {
err = e;
}
return callback( err );
}, 0 );
};
} else {
fs.mkdir = function mkdir( path, mode, callback ) {
callback = maybeCallback( arguments[ arguments.length - 1 ] );
setTimeout( function() {
var err = null;
try {
if( !$F.getFile( path ).createDirectory() ) {
err = new Error( 'could not create directory' );
}
} catch( e ) {
err = e;
}
return callback( err );
}, 0 );
};
}
if( IS_ANDROID ) {
fs.mkdirSync = function mkdirSync( path, mode ) {
$F.getFile( path ).createDirectory();
if( !$F.getFile( path ).exists() ) {
throw new Error( 'could not create directory' );
}
};
} else {
fs.mkdirSync = function mkdirSync( path, mode ) {
if( !$F.getFile( path ).createDirectory() ) {
throw new Error( 'could not create directory' );
}
};
}
fs.readdir = function readdir( path, callback ) {
setTimeout( function() {
var files = [],
err = null;
try {
files = fs.readdirSync( path );
} catch( e ) {
err = e;
}
return callback( err, files );
}, 0 );
};
fs.readdirSync = function readdirSync( path ) {
return $F.getFile( path ).getDirectoryListing();
};
fs.fstat = function fstat( fd, callback ) {
setTimeout( function() {
var stats = null,
err = null;
try {
stats = fs.fstatSync( fd );
} catch( e ) {
err = e;
}
return callback( err, stats );
}, 0 );
};
fs.lstat = function lstat( path, callback ) {
setTimeout( function() {
var stats = null,
err = null;
try {
stats = fs.lstatSync( path );
} catch( e ) {
err = e;
}
return callback( err, stats );
}, 0 );
};
fs.stat = function stat( path, callback ) {
setTimeout( function() {
var stats = null,
err = null;
try {
stats = fs.statSync( path );
} catch( e ) {
err = e;
}
return callback( err, stats );
}, 0 );
};
fs.fstatSync = function fstatSync( fd ) {
if( fd.__path ) {
return fs.statSync( fd.__path );
} else {
throw new Error( 'invalid file descriptor' );
}
};
fs.lstatSync = function lstatSync( path ) {
return fs.statSync( path );
};
fs.statSync = function statSync( path ) {
return new fs.Stats( path );
};
fs.readlink = function readlink( path, callback ) {
setTimeout( function() {
var result = null,
err = null;
try {
result = fs.readlinkSync( path );
} catch( e ) {
err = e;
}
return callback( err, result );
}, 0 );
};
fs.readlinkSync = function readlinkSync( path ) {
var file = $F.getFile( path );
if( !file.symbolicLink ) {
throw new Error( 'invalid argument \'' + path + '\'' );
}
return file.resolve();
};
fs.unlink = function unlink( path, callback ) {
setTimeout( function() {
var err = null;
try {
fs.unlinkSync( path );
} catch( e ) {
err = e;
}
return callback( err );
}, 0 );
};
fs.unlinkSync = function unlinkSync( path ) {
var file = $F.getFile( path );
if( file.isFile() || file.symbolicLink ) {
if( !file.deleteFile() ) {
throw new Error( 'unable to delete file' );
}
} else {
throw new Error( 'operation not permitted \'' + path + '\'' );
}
};
fs.writeFile = function writeFile( path, data, options, callback ) {
callback = maybeCallback( arguments[ arguments.length - 1 ] );
if( !options || util.isFunction( options ) ) {
options = {};
}
setTimeout( function() {
var err = null;
try {
fs.writeFileSync( path, data, options );
} catch( e ) {
err = e;
}
return callback( err );
}, 0 );
};
fs.writeFileSync = function writeFileSync( path, data, options ) {
options = options || {};
var encoding = options.encoding || 'utf8',
fd = fs.openSync( path, 'w' ),
buffer;
if( data.apiName === 'Ti.Buffer' ) {
buffer = data;
} else {
buffer = Ti.createBuffer( {
value: data
} );
}
fs.writeSync( fd, buffer );
fs.closeSync( fd );
};
fs.appendFile = function appendFile( path, data, options, callback_ ) {
var callback = maybeCallback( arguments[ arguments.length - 1 ] );
if( !options || util.isFunction( options ) ) {
options = {};
}
setTimeout( function() {
var err = null;
try {
fs.appendFileSync( path, data, options );
} catch( e ) {
err = e;
}
return callback( err );
}, 0 );
};
fs.appendFileSync = function appendFileSync( path, data, options ) {
options = options || {};
var encoding = options.encoding || 'utf8',
fd = fs.openSync( path, 'a' ),
buffer;
if( data.apiName === 'Ti.Buffer' ) {
buffer = data;
} else {
buffer = Ti.createBuffer( {
value: data
} );
}
fs.writeSync( fd, buffer );
fs.closeSync( fd );
};
// var splitRootRe = /^[\/]*/;
// var nextPartRe = /(.*?)(?:[\/]+|$)/g;
fs.realpathSync = function realpathSync( p, cache ) {
return $F.getFile( p ).resolve();
// p = $F.getFile(p).resolve();
// if (cache && Object.prototype.hasOwnProperty.call(cache, p)) {
// return cache[p];
// }
// var original = p,
// seenLinks = {},
// knownHard = {};
// // current character position in p
// var pos;
// // the partial path so far, including a trailing slash if any
// var current;
// // the partial path without a trailing slash (except when pointing at a root)
// var base;
// // the partial path scanned in the previous round, with slash
// var previous;
// start();
// function start() {
// // Skip over roots
// var m = splitRootRe.exec(p);
// pos = m[0].length;
// current = m[0];
// base = m[0];
// previous = '';
// }
// // walk down the path, swapping out linked pathparts for their real
// // values
// // NB: p.length changes.
// while (pos < p.length) {
// // find the next part
// nextPartRe.lastIndex = pos;
// var result = nextPartRe.exec(p);
// previous = current;
// current += result[0];
// base = previous + result[1];
// pos = nextPartRe.lastIndex;
// // continue if not a symlink
// if (knownHard[base] || (cache && cache[base] === base)) {
// continue;
// }
// var resolvedLink;
// if (cache && Object.prototype.hasOwnProperty.call(cache, base)) {
// // some known symbolic link. no need to stat again.
// resolvedLink = cache[base];
// } else {
// var stat = fs.lstatSync(base);
// if (!stat.isSymbolicLink()) {
// knownHard[base] = true;
// if (cache) cache[base] = base;
// continue;
// }
// fs.statSync(base);
// var linkTarget = fs.readlinkSync(base);
// resolvedLink = pathModule.resolve(previous, linkTarget);
// // track this, if given a cache.
// if (cache) cache[base] = resolvedLink;
// }
// // resolve the link, then start over
// p = pathModule.resolve(resolvedLink, p.slice(pos));
// start();
// }
// if (cache) cache[original] = p;
// return p;
};
fs.realpath = function realpath( p, cache, cb ) {
cb = maybeCallback( arguments[ arguments.length - 1 ] );
if( !cache || util.isFunction( cache ) ) {
cache = {};
}
setTimeout( function() {
var err = null,
res = null;
try {
res = fs.realpathSync( p, cache );
} catch( e ) {
err = e;
}
return cb( err, res );
}, 0 );
};
fs.createReadStream = function createReadStream( path, options ) {
throw new Error( 'createReadStream not implemented' );
};
fs.ReadStream = function ReadStream( path, options ) {
throw new Error( 'ReadStream not implemented' );
};
fs.FileReadStream = function FileReadStream( path, options ) {
throw new Error( 'FileReadStream not implemented' );
};
fs.createWriteStream = function createWriteStream( path, options ) {
throw new Error( 'createWriteStream not implemented' );
};
fs.WriteStream = function WriteStream( path, options ) {
throw new Error( 'WriteStream not implemented' );
};
fs.FileWriteStream = function FileWriteStream( path, options ) {
throw new Error( 'FileWriteStream not implemented' );
};
// no-ops
fs.fsync = function fsync( fd, callback ) {
return callback();
};
fs.fsyncSync = function fsyncSync( fd ) {};
fs.fchmod = function fchmod( fd, mode, callback ) {
return callback();
};
fs.fchmodSync = function fchmodSync( fd, mode ) {};
fs.lchmod = function lchmod( path, mode, callback ) {
return callback();
};
fs.lchmodSync = function lchmodSync( path, mode ) {};
fs.chmod = function chmod( path, mode, callback ) {
return callback();
};
fs.chmodSync = function chmodSync( path, mode ) {};
fs.lchown = function lchown( path, uid, gid, callback ) {
return callback();
};
fs.lchownSync = function lchownSync( path, uid, gid ) {};
fs.fchown = function fchown( fd, uid, gid, callback ) {
return callback();
};
fs.fchownSync = function fchownSync( fd, uid, gid ) {};
fs.chown = function chown( path, uid, gid, callback ) {
return callback();
};
fs.chownSync = function chownSync( path, uid, gid ) {};
fs.symlink = function symlink( destination, path, type_, callback ) {
return maybeCallback( arguments[ arguments.length - 1 ] )();
};
fs.symlinkSync = function symlinkSync( destination, path, type ) {};
fs.link = function link( srcpath, dstpath, callback ) {
return callback();
};
fs.linkSync = function linkSync( srcpath, dstpath ) {};
fs.watch = function watch( filename ) {};
fs.watchFile = function watchFile( filename ) {};
fs.unwatchFile = function unwatchFile( filename, listener ) {};
fs.utimes = function utimes( path, atime, mtime, callback ) {
return callback();
};
fs.utimesSync = function utimesSync( path, atime, mtime ) {};
fs.futimes = function futimes( fd, atime, mtime, callback ) {
return callback();
};
fs.futimesSync = function futimesSync( fd, atime, mtime ) {};
// helpers
function maybeCallback( o ) {
return o && util.isFunction( o ) ? o : function( err ) {
if( err ) {
throw err;
throw err;
}
};
}
function assertFlags( flags ) {
var tiMode = MODE_MAP[ flags ];
if( tiMode == null ) {
throw new Error( 'Unknown file open flag: ' + flags );
}
return tiMode;
}
var ENCODINGS = [ 'ascii', 'utf8', 'utf-8', 'base64', 'binary' ];
function assertEncoding( encoding ) {
if( encoding && ENCODINGS.indexOf( encoding.toLowerCase() ) === -1 ) {
throw new Error( 'Unknown encoding: ' + encoding );
}
}
function convertBuffer( buffer, encoding ) {
switch( encoding.toLowerCase() ) {
case 'ascii':
case 'binary':
var ret = '';
for( var i = 0; i < buffer.length; i++ ) {
ret += String.fromCharCode( Ti.Codec.decodeNumber( {
source: buffer,
type: Ti.Codec.TYPE_BYTE,
position: i
} ) );
}
return ret;
case 'utf8':
case 'utf-8':
return buffer.toString();
case 'base64':
return Ti.Utils.base64encode( buffer.toString() ).toString();
default:
throw new Error( 'Unknown encoding: ' + encoding );
}
}