//Resume and Pause directory scan using FileWalker class also genrates file hash

//Back to tutorial
//Copy following code and paste into walker.js file (CTRL + A to select all then CTRL + C to copy):

//walker.js
const fs = require('fs'),
crypto = require('crypto'),
path = require('path'),
{EventEmitter} = require('events');

module.exports = class FileWalker extends EventEmitter {

constructor (debug){
 super();
 this.debug = debug ? true : false;
 this.reset(debug);
}
reset(){
 this.isPaused = false;
 this.queue = [];
 this.filter_dir = () => false;
 this.filter_file = () => false;
}
addToQueue(entry){
 Array.isArray(entry) 
  ? Array.prototype.push.apply (this.queue, entry)
  : this.queue.push (entry)
}
filterDir (fn){
 this.filter_dir = fn;
}
filterFile (fn){
 this.filter_file = fn;
}
start (entry) {
 fs.lstat(entry, (err,stat) => {
  if (err){
   this.debug&&console.log('Error stat: '+entry);
   this.emit('error',err,entry,stat);
   return this.next();
  }

  if (stat.isFile()){
   if (this.filter_file(entry,stat)){
    this.debug&&console.log('filterFile: '+entry);
    return this.next();
   }
   this.debug&&console.log('File: '+entry);
   this.emit('file',entry,stat);
   this.generateHash(entry,stat);/*Read file header*/
   this.next();
  }
  else if (stat.isDirectory()){
   if (this.filter_dir(entry,stat)){
    this.debug&&console.log('filterDir: '+entry);
    return this.next();
   }
   this.debug&&console.log('Dir: '+entry);
   this.emit('dir',entry,stat);
   fs.readdir(entry, (err,files) => {
    if (err){
     this.debug&&console.log('Error readdir: '+entry);
     this.emit('error',err,entry,stat);
     return this.next();
    }
    Array.prototype.push.apply(this.queue, files.map( file => {
     return path.join(entry,file);
    }));
	this.next();
   });
  }
  else {
   this.debug&&console.log('unknown  or inaccessilbe: '+entry);
   this.emit('unknown',entry,stat);
   this.next()
 }

 });
}

next(){
 if (this.isPaused) {
  this.emit('pause');
  this.debug&&console.log('isPaused, remaining files in queue:'+this.queue.length);
  return this;
 }
 let nextEntry = this.queue.shift();
 if (!nextEntry){
  this.emit('done');
  this.debug&&console.log('Done');
  return this;
 }
 this.start(nextEntry);
}

pause(){
 if (this.isPaused === true){
  debug&&console.log('isPaused failed. App was already paused');
  return this;	 
 }
 this.isPaused = true;
}

resume(){
 if (this.isPaused === false){
  debug&&console.log('Resume failed. App was already resumed');
  return;	 
 }
 this.isPaused = false;
 this.debug&&console.log('Resume');
 this.emit('resume');
 this.next();
}

generateHash(file,stat){
 const defaultLength = 4200,
 len = stat.size < defaultLength ? stat.size : defaultLength,
 pos = 0, offset =0;
 
 fs.open(file, 'r', (err, fd) => {
  if (err) {
   this.emit('error',err,file,stat);
   this.debug&&console.log(err.message);
   return;
  }

  const buffer = Buffer.alloc(len);
  fs.read(fd, buffer, offset, len, pos, (err, bytesRead, buffer) => {
   if (err){ 
    this.emit('error',err,file,stat);
    this.debug&&console.log(err);
    return;
   }
   fs.close(fd, (err) => {
    if (err){
     this.emit('error',err,file,stat);
     this.debug&&console.log(err);
     return;
    }
   });
   
   const hash = crypto
             .createHash('whirlpool')
             .update(buffer)
             .digest('hex');
   
   this.emit('hash',file,stat,buffer,hash);
   this.debug&&console.log('hash emitted');
   return;
  });
 })
}

}