File: /var/www/tana/frontend/node_modules/vinyl-ftp/lib/ftp.js
var Stream = require( 'stream' );
var Path = require( 'path' );
var Ftp = require( 'ftp' );
var Vinyl = require( 'vinyl' );
var mlsd = require( './mlsd' );
var Cache = require( './cache' );
module.exports = {
	upload: function ( file, path, cb ) {
		var self = this;
		var stream = new Stream.PassThrough();
		if ( file.isNull() ) {
			if ( file.stat && file.stat.isDirectory() ) this.mkdirp( path, cb );
			else cb( null, file );
			return;
		}
		if ( file.isStream() ) {
			file.contents.pipe( stream );
		} else if ( file.isBuffer() ) {
			stream.end( file.contents );
		}
		// ensure that parent directory exists
		self.mkdirp( Path.dirname( path ), onParent );
		function onParent( err ) {
			if ( err ) return final( err );
			self.acquire( onAcquire );
		}
		var rel;
		function onAcquire( err, ftp ) {
			rel = ftp;
			if ( err ) return final( err );
			self.log( 'PUT  ', path );
			ftp.put( stream, path, final );
			// THE FOLLOWING MUST BE AFTER ftp.put()
			// Somehow, if you attach a 'data' handler before
			// ftp.put, the callback of ftp.put is never called
			if ( file.stat ) {
				var uploaded = 0;
				var size = file.stat.size;
				stream.on( 'data', function ( chunk ) {
					uploaded += chunk.length;
					var progress = Math.floor( uploaded / size * 100 ).toString();
					if ( progress.length === 1 ) progress = '  ' + progress;
					if ( progress.length === 2 ) progress = ' ' + progress;
					self.log( 'UP   ', progress + '% ' + path );
				} );
			}
		}
		function final( err ) {
			self.release( rel );
			cb( err, file );
		}
	},
	downbuffer: function ( path, cb ) {
		this.downstream( path, function ( err, stream ) {
			if ( err ) return cb( err );
			var bufs = [];
			stream.on( 'data', function ( data ) {
				bufs.push( data );
			} );
			stream.on( 'end', function () {
				cb( null, Buffer.concat( bufs ) );
			} );
			stream.on( 'error', function ( err ) {
				 cb( err );
			} );
		} );
	},
	downstream: function ( path, cb ) {
		var self = this;
		var remote, rel;
		this.remote( path, onRemote );
		function onRemote( err, rem ) {
			if ( err ) return cb( err );
			if ( !rem ) return cb( new Error( 'No such file' ) );
			remote = rem;
			self.acquire( onAcquire );
		}
		function onAcquire( err, ftp ) {
			rel = ftp;
			if ( err ) return onStream( err );
			self.log( 'GET  ', path );
			ftp.get( path, onStream );
		}
		function onStream( err, stream ) {
			if ( err ) {
				self.release( rel );
				return cb( err );
			}
			stream.on( 'end', function () {
				self.release( rel );
			} );
			stream.on( 'error', function () {
				self.release( rel, true );
			} );
			var bytes = 0;
			var size = remote.ftp.size;
			stream.on( 'data', function ( chunk ) {
				bytes += chunk.length;
				var progress = Math.floor( bytes / size * 100 ).toString();
				if ( progress.length === 1 ) progress = '  ' + progress;
				if ( progress.length === 2 ) progress = ' ' + progress;
				self.log( 'DOWN ', progress + '% ' + path );
			} );
			// the socket stream returned by the ftp client cannot be paused
			// add intermediate passthrough stream so piped streams get data
			stream = stream.pipe( new Stream.PassThrough() );
			cb( null, stream );
		}
	},
	mkdirp: function ( path, cb ) {
		if ( !this._mkdirp ) {
			var self = this;
			this._mkdirp = new Cache( function ( path, cb ) {
				// skip if path is root
				if ( path === '/' || path === '' ) {
					return final();
				}
				self.remote( path, onRemote );
				function onRemote( err, remote ) {
					if ( err ) return final( err );
					if ( remote && !self.isDirectory( remote ) ) return final( new Error( path + ' is a file, cannot MKDIR' ) );
					if ( remote ) return final(); // skip if exists
					// ensure that parent directory exists
					self.mkdirp( Path.dirname( path ), onParent );
				}
				function onParent( err ) {
					if ( err ) return final( err );
					self.acquire( onAcquire );
				}
				var rel;
				function onAcquire( err, ftp ) {
					rel = ftp;
					if ( err ) return final( err );
					self.log( 'MKDIR', path );
					ftp.mkdir( path, final );
				}
				function final( err ) {
					self.release( rel );
					if ( err && err.message.match( /file exists/i ) ) return cb();
					cb( err );
				}
			} );
		}
		path = this.join( '/', path );
		return this._mkdirp.get( path, cb );
	},
	chmod: function ( path, mode, cb ) {
		var self = this;
		path = this.join( '/', path );
		var rel;
		self.acquire( onAcquire );
		function onAcquire( err, ftp ) {
			rel = ftp;
			if ( err ) return final( err );
			self.log( 'SITE ', 'CHMOD', mode, path );
			ftp.site( 'CHMOD ' + mode + ' ' + path, final );
		}
		function final( err ) {
			self.release( rel );
			cb( err );
		}
	},
	remote: function ( path, cb ) {
		var self = this;
		path = this.join( '/', path );
		var basename = Path.basename( path );
		var dirname = Path.dirname( path );
		self.mlsdOrList( dirname, onFiles );
		function onFiles( err, files ) {
			if ( err ) return cb( err );
			for ( var i = 0; i < files.length; ++i ) {
				if ( files[ i ].ftp.name === basename ) return cb( null, files[ i ] );
			}
			cb();
		}
	},
	mlsdOrList: function ( path, cb ) {
		var self = this;
		if ( this.noMlsd ) return this.list( path, cb );
		setImmediate(function() {
			self.mlsd( path, onMlsd );
		});
		function onMlsd( err, files ) {
			if ( err && ( err.code === 502 || err.code === 500 ) ) {
				// mlsd not implemented, fallback to LIST
				return self.list( path, cb );
			} else if ( files && files.length > 0 && !files[ 0 ].ftp.date ) {
				// mlsd didnt send any date, try LIST
				return self.list( path, cb );
			}
			cb( err, files );
		}
	},
	mlsd: function ( path, cb ) {
		if ( !this._mlsd ) {
			var self = this;
			this._mlsd = new Cache( function ( path, cb ) {
				var rel;
				self.acquire( onAcquire );
				function onAcquire( err, ftp ) {
					rel = ftp;
					if ( err ) return final( err );
					self.log( 'MLSD ', path );
					mlsd.bind( ftp )( path, onFiles );
				}
				function onFiles( err,  files ) {
					// no such file or directory
					if ( err && ( err.code === 501 || err.code === 550 ) ) return final( null, [] );
					if ( err ) return final( err );
					final( null, self.vinylFiles( path, files ) );
				}
				function final( err, files ) {
					self.release( rel );
					cb( err, files );
				}
			} );
		}
		path = this.join( '/', path );
		this._mlsd.get( path, cb );
	},
	list: function ( path, cb ) {
		if ( !this._list ) {
			var self = this;
			this._list = new Cache( function ( path, cb ) {
				var rel;
				self.acquire( onAcquire );
				function onAcquire( err, ftp ) {
					rel = ftp;
					if ( err ) return final( err );
					self.log( 'LIST ', path );
					ftp.list( path, onFiles );
				}
				function onFiles( err, files ) {
					// no such file or directory
					if ( err && ( err.code === 550 || err.code === 450 ) ) return final( null, [] );
					if ( err ) return final( err );
					final( null, self.vinylFiles( path, files ) );
				}
				function final( err, files ) {
					self.release( rel );
					cb( err, files );
				}
			} );
		}
		path = this.join( '/', path );
		this._list.get( path, cb );
	},
	vinylFiles: function ( dirname, files ) {
		var self = this;
		return files.filter( function ( file ) {
			return file.name !== '.' && file.name !== '..';
		} ).map( function ( file ) {
			file.date = self.fixDate( file.date );
			var vinyl = new Vinyl( {
				cwd: '/',
				path: self.join( dirname, file.name )
			} );
			vinyl.ftp = file;
			return vinyl;
		} );
	},
	acquire: function ( cb ) {
		if ( this.idle.length > 0 ) {
			cb( null, this.idle.shift() );
		} else if ( this.connectionCount < this.config.maxConnections ) {
			this.log( 'CONN ' );
			var self = this;
			var ftp = new Ftp();
			var called = false;
			++this.connectionCount;
			ftp.on( 'ready', function () {
				self.log( 'READY' );
				called = true;
				cb( null, ftp );
			} );
			ftp.on( 'error', function ( err ) {
				var code = err.code ? (' (' + err.code + ')') : '';
				self.log( 'ERROR', err.stack + code );
				self.release( ftp, true );
				// only enqueue callback if not called yet
				if ( !called ) {
					called = true;
					if ( self.connectionCount === 0 ) {
						// there's no hope that a working connection will be released
						// pass error
						return cb( err );
					}
					self.queue.push( cb );
				}
			} );
			ftp.connect( this.config );
		} else {
			this.queue.push( cb );
		}
	},
	release: function ( ftp, force ) {
		if ( !ftp ) return;
		if ( force ) {
			this.log( 'DISC ' );
			ftp.end();
			--this.connectionCount;
		} else if ( this.queue.length > 0 ) {
			reuse = true;
			var first = this.queue.shift();
			first( null, ftp );
		} else {
			this.pushIdle( ftp );
		}
	},
	reload: function () {
		if ( this._mkdirp ) this._mkdirp.clear();
		if ( this._mlsd ) this._mlsd.clear();
		if ( this._list ) this._list.clear();
		return this;
	},
	pushIdle: function ( ftp ) {
		var self = this;
		// add connection to idle list
		this.idle.push( ftp );
		// reset any earlier timeout
		clearTimeout( this.idleTimer );
		// disconnect all after timeout
		this.idleTimer = setTimeout( function () {
			self.idle.forEach( function ( ftp ) {
				self.log( 'DISC ' );
				ftp.end();
				--self.connectionCount;
			} );
			self.idle = [];
		}, this.config.idleTimeout );
	}
};