[
  {
    "path": ".gitignore",
    "content": "node_modules/\n.idea"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2016 \n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Screen Shot\n![screenshot](https://raw.githubusercontent.com/efeiefei/node-file-manager/master/example/screenshot.png)\n\n# Usage\n\n```sh\n  npm install -g node-file-manager\n  node-file-manager -p 8080 -d /path/to/\n```\n\nOr\n\n```sh\n  git clone https://github.com/efeiefei/node-file-manager.git\n  cd node-file-manager\n  npm i\n  cd lib\n  node --harmony index.js -p 8080 -d /path/to\n```\n\nWe can run node-file-manager in terminal directly. We can specify prot add data root dir by `-p` and `-d`, default with 5000 and scripts directory.\n\nThen, we can view localhost:8080/ in our browr.\n"
  },
  {
    "path": "bin/node-file-manager.sh",
    "content": "#!/usr/bin/env sh\n\nSHELL_PATH=`dirname $0`\ncd $SHELL_PATH/../lib/node_modules/node-file-manager/lib\n\nnode --harmony index.js $*\n"
  },
  {
    "path": "lib/fileManager.js",
    "content": "var fs = require('co-fs');\nvar co = require('co');\nvar fse = require('co-fs-extra');\nvar path = require('path');\nvar JSZip = require('jszip');\n\nvar FileManager = {};\n\nFileManager.getStats = function *(p) {\n  var stats = yield fs.stat(p);\n  return {\n    folder: stats.isDirectory(),\n    size: stats.size,\n    mtime: stats.mtime.getTime()\n  }\n};\n\nFileManager.list = function *(dirPath) {\n  var files = yield fs.readdir(dirPath);\n  var stats = [];\n  for (var i=0; i<files.length; ++i) {\n    var fPath = path.join(dirPath, files[i]);\n    var stat = yield FileManager.getStats(fPath);\n    stat.name = files[i];\n    stats.push(stat);\n  }\n  return stats;\n};\n\nFileManager.remove = function *(p) {\n  yield fse.remove(p);\n};\n\nFileManager.mkdirs = function *(dirPath) {\n  yield fse.mkdirs(dirPath);\n};\n\nFileManager.move = function *(srcs, dest) {\n  for (var i=0; i<srcs.length; ++i) {\n    var basename = path.basename(srcs[i]);\n    yield fse.move(srcs[i], path.join(dest, basename));\n  }\n};\n\nFileManager.rename = function *(src, dest) {\n  yield fse.move(src, dest);\n};\n\nFileManager.archive = function *(src, archive, dirPath, embedDirs) {\n  var zip = new JSZip();\n  var baseName = path.basename(archive, '.zip');\n\n  function* addFile(file) {\n    var data = yield fs.readFile(file);\n    var name;\n    if (embedDirs) {\n      name = file;\n      if (name.indexOf(dirPath) === 0) {\n        name = name.substring(dirPath.length);\n      }\n    } else {\n      name = path.basename(file);\n    }\n    zip.file(name, data);\n    C.logger.info('Added ' + name + ' ' + data.length + ' bytes to archive ' + archive);\n  }\n\n  function* addDir(dir) {\n    var contents = yield fs.readdir(dir);\n    for (var file of contents) {\n      yield * process(path.join(dir, file));\n    }\n  }\n\n  function* process(fp) {\n    var stat = yield fs.stat(fp);\n    if (stat.isDirectory()) {\n      yield * addDir(fp);\n    } else {\n      yield addFile(fp);\n    }\n  }\n\n  // Add each src.  For directories, do the entire recursive dir.\n  for (var file of src) {\n    yield * process(file);\n  }\n\n  // Generate the zip and store the final.\n  var data = yield zip.generateAsync({type:'nodebuffer',compression:'DEFLATE'});\n  yield fs.writeFile(archive, data, 'binary');\n};\n\nmodule.exports = FileManager;\n"
  },
  {
    "path": "lib/fileMap.js",
    "content": "var path = require('path');\n\nvar DATA_ROOT = C.data.root;\n\nexports.filePath = function (relPath, decodeURI) {\n  if (decodeURI) relPath = decodeURIComponent(relPath);\n  if (relPath.indexOf('..') >= 0){\n    var e = new Error('Do Not Contain .. in relPath!');\n    e.status = 400;\n    throw e;\n  }\n  else {\n    return path.join(DATA_ROOT, relPath);\n  }\n};\n"
  },
  {
    "path": "lib/index.js",
    "content": "#!/usr/bin/env node\n\nvar koa =require('koa');\nvar path = require('path');\nvar tracer = require('tracer');\nvar mount = require('koa-mount');\nvar morgan = require('koa-morgan');\nvar koaStatic = require('koa-static');\n\n// Config\nvar argv = require('optimist')\n  .usage([\n    'USAGE: $0 [-p <port>] [-d <directory>]']\n  )\n  .option('port', {\n    alias: 'p',\n    'default': 5000,\n    description: 'Server Port'\n  })\n  .option('directory', {\n    alias: 'd',\n    description: 'Root Files Directory'\n  })\n  .option('version', {\n    alias: 'v',\n    description: 'Server　Version'\n  })\n  .option('help', {\n    alias: 'h',\n    description: \"Display This Help Message\"\n  })\n  .argv;\n\nif (argv.help) {\n  require('optimist').showHelp(console.log);\n  process.exit(0);\n}\n\nif (argv.version) {\n  console.log('FileManager', require('./package.json').version);\n  process.exit(0);\n}\n\nglobal.C = {\n  data: {\n    root: argv.directory || path.dirname('.')\n  },\n  logger: require('tracer').console({level: 'info'}),\n  morganFormat: ':date[iso] :remote-addr :method :url :status :res[content-length] :response-time ms'\n};\n\n// Start Server\nvar Tools = require('./tools');\n\nvar startServer = function (app, port) {\n  app.listen(port);\n  C.logger.info('listening on *.' + port);\n};\n\nvar app = koa();\napp.proxy = true;\napp.use(Tools.handelError);\napp.use(Tools.realIp);\napp.use(morgan.middleware(C.morganFormat));\n\nvar IndexRouter = require('./routes');\napp.use(mount('/', IndexRouter));\napp.use(koaStatic(path.join(__dirname,'./public/')));\n\nstartServer(app, +argv.port);\n\n"
  },
  {
    "path": "lib/public/js/angular-file.js",
    "content": "/**\n * ur.file: Native HTML5-based file input bindings for AngularJS\n *\n * @version 0.9a\n * @copyright (c) 2013 Union of RAD, LLC http://union-of-rad.com/\n * @license: BSD\n */\n\n\n/**\n * The ur.file module implements native support for file uploads in AngularJS.\n */\nangular.module('ur.file', []).config(['$provide', function($provide) {\n\n  /**\n   * XHR initialization, copied from Angular core, because it's buried inside $HttpProvider.\n   */\n  var XHR = window.XMLHttpRequest || function() {\n    try { return new ActiveXObject(\"Msxml2.XMLHTTP.6.0\"); } catch (e1) {}\n    try { return new ActiveXObject(\"Msxml2.XMLHTTP.3.0\"); } catch (e2) {}\n    try { return new ActiveXObject(\"Msxml2.XMLHTTP\"); } catch (e3) {}\n    throw new Error(\"This browser does not support XMLHttpRequest.\");\n  };\n\n  /**\n   * Initializes XHR object with parameters from $httpBackend.\n   */\n  function prepXHR(method, url, headers, callback, withCredentials, type, manager) {\n    var xhr = new XHR();\n    var status;\n\n    xhr.open(method, url, true);\n\n    if (type) {\n      xhr.type = type;\n      headers['Content-Type'] = type;\n    }\n\n    angular.forEach(headers, function(value, key) {\n      (value) ? xhr.setRequestHeader(key, value) : null;\n    });\n\n    manager.register(xhr);\n\n    xhr.onreadystatechange = function() {\n      if (xhr.readyState == 4) {\n        manager.unregister(xhr);\n        var response = xhr.response || xhr.responseText;\n        callback(status = status || xhr.status, response, xhr.getAllResponseHeaders());\n      }\n    };\n\n    if (withCredentials) {\n      xhr.withCredentials = true;\n    }\n    return xhr;\n  }\n\n  /**\n   * Hook into $httpBackend to intercept requests containing files.\n   */\n  $provide.decorator('$httpBackend', ['$delegate', '$window', 'uploadManager', function($delegate, $window, uploadManager) {\n    return function(method, url, post, callback, headers, timeout, wc) {\n      var containsFile = false, result = null, manager = uploadManager;\n\n      if (post && angular.isObject(post)) {\n        containsFile = hasFile(post);\n      }\n\n      if (angular.isObject(post)) {\n        if (post && post.name && !headers['X-File-Name']) {\n          headers['X-File-Name'] = encodeURI(post.name);\n        }\n\n        angular.forEach({\n          size: 'X-File-Size',\n          lastModifiedDate: 'X-File-Last-Modified'\n        }, function(header, key) {\n          if (post && post[key]) {\n            if (!headers[header]) headers[header] = post[key];\n          }\n        });\n      }\n\n      if (post && post instanceof Blob) {\n        return prepXHR(method, url, headers, callback, wc, post.type, manager).send(post);\n      }\n      $delegate(method, url, post, callback, headers, timeout, wc);\n    };\n  }]);\n\n  /**\n   * Checks an object hash to see if it contains a File object, or, if legacy is true, checks to\n   * see if an object hash contains an <input type=\"file\" /> element.\n   */\n  var hasFile = function(data) {\n    for (var n in data) {\n      if (data[n] instanceof Blob) {\n        return true;\n      }\n      if ((angular.isObject(data[n]) || angular.isArray(data[n])) && hasFile(data[n])) {\n        return true;\n      }\n    }\n    return false;\n  };\n\n  /**\n   * Prevents $http from executing its default transformation behavior if the data to be\n   * transformed contains file data.\n   */\n  $provide.decorator('$http', ['$delegate', function($delegate) {\n    var transformer = $delegate.defaults.transformRequest[0];\n\n    $delegate.defaults.transformRequest = [function(data) {\n      return data instanceof Blob ? data : transformer(data);\n    }];\n    return $delegate;\n  }]);\n\n}]).service('fileHandler', ['$q', '$rootScope', function($q, $rootScope) {\n\n  return {\n\n    /**\n     * Loads a file as a data URL and returns a promise representing the file's value.\n     */\n    load: function(file) {\n      var deferred = $q.defer();\n\n      var reader = angular.extend(new FileReader(), {\n        onload: function(e) {\n          deferred.resolve(e.target.result);\n          if (!$rootScope.$$phase) $rootScope.$apply();\n        },\n        onerror: function(e) {\n          deferred.reject(e);\n          if (!$rootScope.$$phase) $rootScope.$apply();\n        },\n        onabort: function(e) {\n          deferred.reject(e);\n          if (!$rootScope.$$phase) $rootScope.$apply();\n        }\n        // onprogress: Gee, it'd be great to get some progress support from $q...\n      });\n      reader.readAsDataURL(file);\n\n      return angular.extend(deferred.promise, {\n        abort: function() { reader.abort(); }\n      });\n    },\n\n    /**\n     * Returns the metadata from a File object, including the name, size and last modified date.\n     */\n    meta: function(file) {\n      return {\n        name: file.name,\n        size: file.size,\n        lastModifiedDate: file.lastModifiedDate\n      };\n    },\n\n    /**\n     * Converts a File object or data URL to a Blob.\n     */\n    toBlob: function(data) {\n      var extras = {};\n\n      if (data instanceof File) {\n        extras = this.meta(data);\n        data = data.toDataURL();\n      }\n      var parts = data.split(\",\"), headers = parts[0].split(\":\"), body;\n\n      if (parts.length !== 2 || headers.length !== 2 || headers[0] !== \"data\") {\n        throw new Error(\"Invalid data URI.\");\n      }\n      headers = headers[1].split(\";\");\n      body = (headers[1] === \"base64\") ? atob(parts[1]) : decodeURI(parts[1]);\n      var length = body.length, buffer = new ArrayBuffer(length), bytes = new Uint8Array(buffer);\n\n      for (var i = 0; i < length; i++) {\n        bytes[i] = body.charCodeAt(i);\n      }\n      return angular.extend(new Blob([bytes], { type: headers[0] }), extras);\n    }\n  };\n\n}]).service('uploadManager', ['$rootScope', function($rootScope) {\n\n  angular.extend(this, {\n    id : null,\n    uploads: {},\n    capture: function(id) {\n      this.id = id;\n      this.uploads[id] = {\n        loaded: 0,\n        total: 0,\n        percent: 0,\n        object: null\n      };\n    },\n    register: function(xhr) {\n      if (this.id === null) {\n        return false;\n      }\n      xhr._idXhr = this.id;\n      this.id = null;\n      this.uploads[xhr._idXhr]['object'] = xhr;\n      var self = this;\n\n      xhr.upload.onprogress = function(e) {\n        if (e.lengthComputable) {\n          self.uploads[xhr._idXhr]['loaded'] = e.loaded;\n          self.uploads[xhr._idXhr]['total'] = e.total;\n          self.uploads[xhr._idXhr]['percent'] = Math.round(e.loaded / e.total * 100);\n          $rootScope.$apply();\n        }\n      };\n      return true;\n    },\n    unregister: function(xhr) {\n      delete this.uploads[xhr._idXhr];\n    },\n    get: function(id) {\n      if (this.uploads[id]) {\n        return this.uploads[id];\n      }\n      return false;\n    },\n    abort: function(id) {\n      if (this.uploads[id]) {\n        return this.uploads[id]['object'].abort();\n      }\n      return false;\n    }\n  });\n\n}]).directive('type', ['$parse', function urModelFileFactory($parse) {\n\n  /**\n   * Binding for file input elements\n   */\n  return {\n    scope: false,\n    priority: 1,\n    require: \"?ngModel\",\n    link: function urFilePostLink(scope, element, attrs, ngModel) {\n\n      if (attrs.type.toLowerCase() !== 'file' || !ngModel) {\n        return;\n      }\n\n      element.bind('change', function(e) {\n        if (!e.target.files || !e.target.files.length || !e.target.files[0]) {\n          return true;\n        }\n        var index, fileData = attrs.multiple ? e.target.files : e.target.files[0];\n        ngModel.$render = function() {};\n\n        scope.$apply(function(scope) {\n          index = scope.$index;\n          $parse(attrs.ngModel).assign(scope, fileData);\n        });\n        scope.$index = index;\n\n        // @todo Make sure this can be replaced by ngChange.\n        // For that to work, this event handler must have a higher priority than the one\n        // defined by ngChange\n        attrs.change ? scope.$eval(attrs.change) : null;\n      });\n    }\n  };\n\n}]).directive('dropTarget', ['$parse', 'fileHandler', function urDropTargetFactory($parse, fileHandler) {\n\n  return {\n    scope: false,\n    restrict: \"EAC\",\n    require: \"?ngModel\",\n    link: function urDropTargetLink(scope, element, attrs, ngModel) {\n      var multiple  = attrs.multiple,\n          dropExpr  = attrs.drop ? $parse(attrs.drop) : null,\n          modelExpr = attrs.ngModel ? $parse(attrs.ngModel) : null;\n\n      if (ngModel) ngModel.$render = function() {};\n\n      function stop(e) {\n        e.stopPropagation();\n        e.preventDefault();\n      }\n\n      var toIgnore = [], isOver = false;\n\n      element.bind(\"dragenter\", function dragEnter(e) {\n        stop(e);\n        if (e.target === this && !isOver) {\n          if (attrs.overClass) element.addClass(attrs.overClass);\n          isOver = true;\n          return;\n        }\n        toIgnore.push(e.target);\n      });\n\n      element.bind(\"dragleave\", function dragExit(e) {\n        stop(e);\n        if (toIgnore.length === 0 && isOver) {\n          if (attrs.overClass) element.removeClass(attrs.overClass);\n          isOver = false;\n          return;\n        }\n        toIgnore.pop();\n      });\n\n      element.bind(\"dragover\", function(e) {\n        stop(e);\n      });\n\n      element.bind(\"drop\", function(e) {\n        stop(e);\n        if (attrs.overClass) element.removeClass(attrs.overClass);\n        isOver = false;\n        e = e.originalEvent || e;\n        var files = e.dataTransfer.files;\n\n        if (!files.length) return;\n        files = multiple ? files : files[0];\n\n        if (modelExpr) modelExpr.assign(scope, files);\n        if (!dropExpr) return (scope.$$phase) ? null : scope.$apply();\n\n        var local = { $event: e };\n        local['$file' + (multiple ? 's' : '')] = files;\n        var result = function() { dropExpr(scope, local); };\n        (scope.$$phase) ? result() : scope.$apply(result);\n      });\n    }\n  };\n\n}]);\n"
  },
  {
    "path": "lib/public/js/app.js",
    "content": "var FMApp = angular.module('FMApp', ['ur.file']);\n\nFMApp.controller('FileManagerCtr', ['$scope', '$http', '$location',\n  function ($scope, $http, $location) {\n    var FM = this;\n    FM.curHashPath = '#/';          // hash in browser url\n    FM.curFolderPath = '/';         // current relative folder path\n    FM.curBreadCrumbPaths = [];     // items in breadcrumb list, for each level folder\n    FM.curFiles = [];               // files in current folder\n\n    FM.selecteAll = false;          // if select all files\n    FM.selection = [];              // selected files\n    FM.renameName = '';             // new name for rename action\n    FM.uploadFile = null;           // will upload file\n    FM.newFolderName = '';\n    FM.successData = '__init__';\n    FM.errorData = '__init__';\n    FM.updateArchiveName = function() {\n      FM.archiveTarget = 'files_' + FM.curFolderPath.substring(1).replace(/\\//g, '_') + new Date().toISOString().replace(/:/g, '.') + '.zip';\n    };\n\n    var hash2paths = function (relPath) {\n      var paths = [];\n      var names = relPath.split('/');\n      var path = '#/';\n      paths.push({name: 'Home', path: path});\n      for (var i=0; i<names.length; ++i) {\n        var name = names[i];\n        if (name) {\n          path = path + name + '/';\n          paths.push({name: name, path: path});\n        }\n      }\n      return paths;\n    };\n\n    var humanSize = function (size) {\n      var hz;\n      if (size < 1024) hz = size + ' B';\n      else if (size < 1024*1024) hz = (size/1024).toFixed(2) + ' KB';\n      else if (size < 1024*1024*1024) hz = (size/1024/1024).toFixed(2) + ' MB';\n      else hz = (size/1024/1024/1024).toFixed(2) + ' GB';\n      return hz;\n    };\n\n    var humanTime = function (timestamp) {\n      var t = new Date(timestamp);\n      return t.toLocaleDateString() + ' ' + t.toLocaleTimeString();\n    };\n\n    var setCurFiles = function (relPath) {\n      $http.get('api' + relPath)\n        .success(function (data) {\n          var files = data;\n          files.forEach(function (file) {\n            file.relPath = relPath + encodeURIComponent(file.name);\n            if (file.folder) file.relPath += '/';\n            file.selected = false;\n            file.humanSize = humanSize(file.size);\n            file.humanTime = humanTime(file.mtime);\n          });\n          FM.curFiles = files;\n          console.log('Current Files:');\n          console.log(FM.curFiles);\n        })\n        .error(function (data, status) {\n          alert('Error: ' + status + data);\n        });\n    };\n\n    var handleHashChange = function (hash) {\n      if (!hash) {\n        return $location.path('/');\n      }\n      console.log('Hash change: ' + hash);\n      var relPath = hash.slice(1);\n      FM.curHashPath = hash;\n      FM.curFolderPath = relPath;\n      FM.curBreadCrumbPaths = hash2paths(relPath);\n      setCurFiles(relPath);\n    };\n\n    $scope.$watch(function () {\n      return location.hash;\n    }, function (val) {\n      handleHashChange(val);\n    });\n\n    // listening on file checkbox\n    $scope.$watch('FM.curFiles|filter:{selected:true}', function (nv) {\n      FM.selection = nv.map(function (file) {\n        return file;\n      });\n    }, true);\n\n    $scope.$watch('FM.selectAll', function (nv) {\n      FM.curFiles.forEach(function (file) {\n        file.selected = nv;\n      });\n    });\n\n    $scope.$watch('FM.successData', function () {\n      if (FM.successData === '__init__') return;\n      $('#successAlert').show();\n      $('#successAlert').fadeIn(3000);\n      $('#successAlert').fadeOut(3000);\n    });\n\n    $scope.$watch('FM.errorData', function () {\n      if (FM.errorData === '__init__') return;\n      $('#errorAlert').show();\n    });\n\n    var httpRequest = function (method, url, params, data, config) {\n      var conf = {\n        method: method,\n        url: url,\n        params: params,\n        data: data,\n        timeout: 10000\n      };\n      for (var k in config) {\n        if (config.hasOwnProperty(k)) {\n          conf[k] = config[k];\n        }\n      }\n      console.log('request url', url);\n      $http(conf)\n        .success(function (data) {\n          FM.successData = data;\n          handleHashChange(FM.curHashPath);\n        })\n        .error(function (data, status) {\n          FM.errorData = ' ' + status + ': ' + data;\n        });\n    };\n\n    var downloadFile = function (file) {\n      window.open('api' + file.relPath);\n    };\n\n    FM.clickFile = function (file) {\n      if (file.folder) {\n        // open folder by setting url hash\n        $location.path(decodeURIComponent(file.relPath));\n      }\n      else {\n        // download file\n        downloadFile(file);\n      }\n    };\n\n    FM.download = function () {\n\n      // Technique for downloading multiple files adapted from here:\n      //  [1] http://stackoverflow.com/questions/2339440/download-multiple-files-with-a-single-action\n\n      var link = document.createElement('a');\n      link.style.display = 'none';\n      document.body.appendChild(link);\n\n      for (var i in FM.selection) {\n        link.setAttribute('href', 'api' + FM.selection[i].relPath);\n        link.setAttribute('download', FM.selection[i].name);\n        link.click();\n      }\n\n      document.body.removeChild(link);\n    };\n\n    FM.delete = function () {\n      for (var i in FM.selection) {\n        var relPath = FM.selection[i].relPath;\n        var url = 'api' + relPath;\n        httpRequest('DELETE', url, null, null);\n      }\n    };\n\n    FM.move = function (target) {\n      var url = 'api' + encodeURI(target);\n      var src = FM.selection.map(function (file) {\n        return file.relPath;\n      });\n      httpRequest('PUT', url, {type: 'MOVE'}, {src: src});\n    };\n\n    FM.archive = function (archive) {\n      if (!archive.match(/\\.zip$/)) {\n        archive += '.zip';\n      }\n      var url = 'api' + FM.curFolderPath + encodeURI(archive);\n      var src = FM.selection.map(function (file) {\n        return file.relPath;\n      });\n      httpRequest('POST', url, {type: 'CREATE_ARCHIVE'}, {src: src, embedDirs: FM.archiveEmbedDirs});\n    };\n\n    FM.rename = function (newName) {\n      var url = 'api' + FM.selection[0].relPath;\n      var target = FM.curFolderPath + encodeURI(newName);\n      console.log('rename target', target);\n      httpRequest('PUT', url, {type: 'RENAME'}, {target: target});\n    };\n\n    FM.createFolder = function (folderName) {\n      var url = 'api' + FM.curFolderPath + encodeURI(folderName);\n      httpRequest('POST', url, {type: 'CREATE_FOLDER'}, null);\n    };\n\n    FM.upload = function () {\n      console.log('Upload File:', FM.uploadFile);\n      var formData = new FormData();\n      formData.append('upload', FM.uploadFile);\n      var url = 'api' + FM.curFolderPath + encodeURI(FM.uploadFile.name);\n      httpRequest('POST', url, {type: 'UPLOAD_FILE'}, formData, {\n        transformRequest: angular.identity,\n        headers: {'Content-Type': undefined}\n      });\n    };\n\n    FM.btnDisabled = function (btnName) {\n      switch (btnName) {\n        case 'download':\n          if (FM.selection.length === 0) return true;\n          else {\n            for (var i in FM.selection) {\n              if (FM.selection[i].folder) return true;\n            }\n            return false;\n          }\n        case 'delete':\n        case 'move':\n        case 'archive':\n          return FM.selection.length === 0;\n        case 'rename':\n          return FM.selection.length !== 1;\n        case 'upload_file':\n        case 'create_folder':\n          return false;\n        default:\n          return true;\n      }\n    }\n  }\n]);\n"
  },
  {
    "path": "lib/routes.js",
    "content": "var fs = require('co-fs');\nvar path = require('path');\nvar views = require('co-views');\nvar origFs = require('fs');\nvar koaRouter = require('koa-router');\nvar bodyParser = require('koa-bodyparser');\nvar formParser = require('co-busboy');\n\nvar Tools = require('./tools');\nvar FilePath = require('./fileMap').filePath;\nvar FileManager = require('./fileManager');\n\nvar router = new koaRouter();\nvar render = views(path.join(__dirname, './views'), {map: {html: 'ejs'}});\n\nrouter.get('/', function *() {\n  this.redirect('files');\n});\n\nrouter.get('/files', function *() {\n  this.body = yield render('files');\n});\n\nrouter.get('/api/(.*)', Tools.loadRealPath, Tools.checkPathExists, function *() {\n  var p = this.request.fPath;\n  var stats = yield fs.stat(p);\n  if (stats.isDirectory()) {\n    this.body = yield * FileManager.list(p);\n  }\n  else {\n    //this.body = yield fs.createReadStream(p);\n    this.body = origFs.createReadStream(p);\n  }\n});\n\nrouter.del('/api/(.*)', Tools.loadRealPath, Tools.checkPathExists, function *() {\n  var p = this.request.fPath;\n  yield * FileManager.remove(p);\n  this.body = 'Delete Succeed!';\n});\n\nrouter.put('/api/(.*)', Tools.loadRealPath, Tools.checkPathExists, bodyParser(), function* () {\n  var type = this.query.type;\n  var p = this.request.fPath;\n  if (!type) {\n    this.status = 400;\n    this.body = 'Lack Arg Type'\n  }\n  else if (type === 'MOVE') {\n    var src = this.request.body.src;\n    if (!src || ! (src instanceof Array)) return this.status = 400;\n    var src = src.map(function (relPath) {\n      return FilePath(relPath, true);\n    });\n    yield * FileManager.move(src, p);\n    this.body = 'Move Succeed!';\n  }\n  else if (type === 'RENAME') {\n    var target = this.request.body.target;\n    if (!target) return this.status = 400;\n    yield * FileManager.rename(p, FilePath(target, true));\n    this.body = 'Rename Succeed!';\n  }\n  else {\n    this.status = 400;\n    this.body = 'Arg Type Error!';\n  }\n});\n\nrouter.post('/api/(.*)', Tools.loadRealPath, Tools.checkPathNotExists, bodyParser(), function *() {\n  var type = this.query.type;\n  var p = this.request.fPath;\n  if (!type) {\n    this.status = 400;\n    this.body = 'Lack Arg Type!';\n  }\n  else if (type === 'CREATE_FOLDER') {\n    yield * FileManager.mkdirs(p);\n    this.body = 'Create Folder Succeed!';\n  }\n  else if (type === 'UPLOAD_FILE') {\n    var formData = yield formParser(this.req);\n    if (formData.fieldname === 'upload'){\n      var writeStream = origFs.createWriteStream(p);\n      formData.pipe(writeStream);\n      this.body = 'Upload File Succeed!';\n    }\n    else {\n      this.status = 400;\n      this.body = 'Lack Upload File!';\n    }\n  }\n  else if (type === 'CREATE_ARCHIVE') {\n    var src = this.request.body.src;\n    if (!src) return this.status = 400;\n    src = src.map(function(file) {\n      return FilePath(file, true);\n    })\n    var archive = p;\n    yield * FileManager.archive(src, archive, C.data.root, !!this.request.body.embedDirs);\n    this.body = 'Create Archive Succeed!';\n  }\n  else {\n    this.status = 400;\n    this.body = 'Arg Type Error!';\n  }\n});\n\nmodule.exports = router.middleware();\n"
  },
  {
    "path": "lib/tools.js",
    "content": "var fs = require('co-fs');\nvar FilePath = require('./fileMap').filePath;\n\nmodule.exports = {\n  realIp: function * (next) {\n      this.req.ip = this.headers['x-forwarded-for'] || this.ip;\n      yield *next;\n  },\n\n  handelError: function * (next) {\n    try {\n      yield * next;\n    } catch (err) {\n      this.status = err.status || 500;\n      this.body = err.message;\n      C.logger.error(err.stack);\n      this.app.emit('error', err, this);\n    }\n  },\n\n  loadRealPath: function *(next) {\n    // router url format must be /api/(.*)\n    this.request.fPath = FilePath(this.params[0]);\n    C.logger.info(this.request.fPath);\n    yield * next;\n  },\n\n  checkPathExists: function *(next) {\n    // Must after loadRealPath\n    if (!(yield fs.exists(this.request.fPath))) {\n      this.status = 404;\n      this.body = 'Path Not Exists!';\n    }\n    else {\n      yield * next;\n    }\n  },\n\n  checkPathNotExists: function *(next) {\n    // Must after loadRealPath\n    if (yield fs.exists(this.request.fPath)) {\n      this.status = 400;\n      this.body = 'Path Has Exists!';\n    }\n    else {\n      yield * next;\n    }\n  }\n\n};\n"
  },
  {
    "path": "lib/views/files.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" ng-app=\"FMApp\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <title>File Manager</title>\n\n  <link href=\"//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css\" rel=\"stylesheet\">\n  <script src=\"//cdn.bootcss.com/jquery/2.1.4/jquery.min.js\"></script>\n  <script src=\"//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js\"></script>\n  <script src=\"//cdn.bootcss.com/angular.js/1.4.5/angular.min.js\"></script>\n  <script src=\"js/angular-file.js\"></script>\n  <script src=\"js/app.js\"></script>\n\n  <style type=\"text/css\">\n    .sortable {\n      cursor: pointer;\n    }\n  </style>\n</head>\n\n<body ng-controller=\"FileManagerCtr as FM\">\n  <div class=\"modal fade\" id=\"createFolderModal\">\n    <div class=\"modal-dialog\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <h4>New Folder</h4>\n        </div>\n        <div class=\"modal-body\">\n          <p>Complete Path　{{FM.curFolderPath + FM.newFolderName + '/'}}</p>\n          <label class=\"control-label\">Folder Name</label><input class=\"form-control\" autofocus ng-model=\"FM.newFolderName\" />\n        </div>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Cancel</button>\n          <button type=\"button\" class=\"btn btn-primary\" data-dismiss=\"modal\" ng-disabled=\"!FM.newFolderName\" ng-click=\"FM.createFolder(FM.newFolderName)\">OK</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal fade\" id=\"uploadFileModal\">\n    <div class=\"modal-dialog\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <h4>Upload File - Just for small file now</h4>\n        </div>\n        <div class=\"modal-body\">\n          <p>Upload to {{FM.curFolderPath + FM.uploadFile.name}}</p>\n          <div class=\"form-inline\">\n              <input type=\"file\" class=\"form-control\" autofocus ng-model=\"FM.uploadFile\">\n          </div>\n        </div>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Cancel</button>\n          <button type=\"button\" class=\"btn btn-primary\" data-dismiss=\"modal\" ng-disabled=\"!FM.uploadFile\" ng-click=\"FM.upload()\">OK</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal fade\" id=\"renameModal\">\n    <div class=\"modal-dialog\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <h4>Rename</h4>\n        </div>\n        <div class=\"modal-body\">\n          <label class=\"control-label\">New Name</label><input class=\"form-control\" autofocus ng-model=\"FM.newName\" placeholder={{FM.selection[0].name}} />\n        </div>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Cancel</button>\n          <button type=\"button\" class=\"btn btn-primary\" data-dismiss=\"modal\" ng-disabled=\"!FM.newName\" ng-click=\"FM.rename(FM.newName)\">OK</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal fade\" id=\"moveModal\">\n    <div class=\"modal-dialog\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <h4>Move</h4>\n        </div>\n        <div class=\"modal-body\">\n          <label class=\"control-label\">Target</label><input class=\"form-control\" autofocus ng-model=\"FM.moveTarget\" placeholder=\"{{FM.curFolderPath}}\" />\n        </div>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Cancel</button>\n          <button type=\"button\" class=\"btn btn-primary\" data-dismiss=\"modal\" ng-disabled=\"!FM.moveTarget\" ng-click=\"FM.move(FM.moveTarget)\">OK</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"modal fade\" id=\"archiveModal\">\n    <div class=\"modal-dialog\">\n      <div class=\"modal-content\">\n        <div class=\"modal-header\">\n          <h4>Create Archive</h4>\n        </div>\n        <div class=\"modal-body\">\n          <label class=\"control-label\">Name</label><input class=\"form-control\" autofocus ng-model=\"FM.archiveTarget\" placeholder=\"Enter name of archive\" />\n          <div class=\"checkbox\">\n            <label><input type=\"checkbox\" ng-init=\"FM.archiveEmbedDirs = true\" ng-model=\"FM.archiveEmbedDirs\"> Embed directories in archive</label>\n          </div>\n        </div>\n        <div class=\"modal-footer\">\n          <button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Cancel</button>\n          <button type=\"button\" class=\"btn btn-primary\" data-dismiss=\"modal\" ng-disabled=\"!FM.archiveTarget\" ng-click=\"FM.archive(FM.archiveTarget)\">Create</button>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <nav class=\"navbar navbar-default navbar-static-top\">\n    <div class=\"container\">\n      <div class=\"navbar-header\">\n        <a class=\"navbar-brand\" href=\"#\">File Manager</a>\n      </div>\n    </div>\n  </nav>\n\n  <div class=\"container\">\n    <div class=\"btn-toolbar\">\n      <button type=\"button\" class=\"btn btn-default\" ng-disabled=\"FM.btnDisabled('download')\" ng-click=\"FM.download()\"><span class=\"glyphicon glyphicon-download\"></span> Download</button>\n      <button type=\"button\" class=\"btn btn-default\" ng-disabled=\"FM.btnDisabled('delete')\" ng-click=\"FM.delete()\"><span class=\"glyphicon glyphicon-remove\"></span> Delete</button>\n      <button type=\"button\" class=\"btn btn-default\" ng-disabled=\"FM.btnDisabled('move')\" data-toggle=\"modal\" data-target=\"#moveModal\"><span class=\"glyphicon glyphicon-random\"></span> Move</button>\n      <button type=\"button\" class=\"btn btn-default\" ng-disabled=\"FM.btnDisabled('rename')\" data-toggle=\"modal\" data-target=\"#renameModal\"><span class=\"glyphicon glyphicon-font\"></span> Rename</button>\n      <button type=\"button\" class=\"btn btn-default\" ng-disabled=\"FM.btnDisabled('archive')\" data-toggle=\"modal\" data-target=\"#archiveModal\" ng-click=\"FM.updateArchiveName()\"><span class=\"glyphicon glyphicon-download-alt\"></span> Archive</button>\n      <button type=\"button\" class=\"btn btn-default pull-right\" ng-disabled=\"FM.btnDisabled('create_folder')\" data-toggle=\"modal\" data-target=\"#createFolderModal\"><span class=\"glyphicon glyphicon-plus-sign\"></span> New Folder</button>\n      <button type=\"button\" class=\"btn btn-default pull-right\" ng-disabled=\"FM.btnDisabled('upload_file')\" data-toggle=\"modal\" data-target=\"#uploadFileModal\"><span class=\"glyphicon glyphicon-upload\"></span> Upload</button>\n    </div>\n\n    <ol class=\"breadcrumb\">\n      <span class=\"glyphicon glyphicon-home\"></span>\n      <li ng-repeat=\"p in FM.curBreadCrumbPaths\"><a href={{p.path}}>{{p.name}}</a></li>\n    </ol>\n\n    <table class=\"table table-hover table-striped\">\n      <thead>\n        <tr class=\"sortable\">\n          <th><input type=\"checkbox\" value=\"\" ng-model=\"FM.selectAll\"></th>\n          <th>Type</th>\n          <th>Name</th>\n          <th>Size</th>\n          <th>Time</th>\n        </tr>\n      </thead>\n      <tbody>\n      <tr class=\"sortable\" ng-repeat=\"file in FM.curFiles | orderBy:'folder'\">\n        <td><input type=\"checkbox\" value=\"{{file.name}}\" ng-model=\"file.selected\"></td>\n        <td ng-click=\"FM.clickFile(file)\"><span class=\"{{file.folder ? 'glyphicon glyphicon-folder-open' : 'glyphicon glyphicon-file'}}\"></span></td>\n        <td ng-click=\"FM.clickFile(file)\"><a>{{file.name}}</a></td>\n        <td>{{file.humanSize}}</td>\n        <td>{{file.humanTime}}</td>\n      </tr>\n      </tbody>\n    </table>\n\n    <div id=\"successAlert\" class=\"alert alert-success\" role=\"alert\" style=\"display: none\">\n      <span>{{FM.successData}}</span>\n    </div>\n\n    <div id=\"errorAlert\" class=\"alert alert-danger\" role=\"alert\" style=\"display: none\">\n      <button type=\"button\" class=\"close\" data-dismiss=\"alert\" alert-label=\"Close\">\n        <span aria-hidden=\"true\">&times</span>\n      </button>\n      <strong>Error!</strong>\n      {{FM.errorData}}\n    </div>\n\n  </div>\n\n</body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"node-file-manager\",\n  \"version\": \"0.4.6\",\n  \"description\": \"File manager web based on Koa and Angular.js\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"node --harmony ./lib/index.js\"\n  },\n  \"bin\": {\n    \"node-file-manager\": \"../node-file-manager/bin/node-file-manager.sh\"\n  },\n  \"dependencies\": {\n    \"co\": \"^4.6.0\",\n    \"co-busboy\": \"1.3.1\",\n    \"co-from-stream\": \"0.0.0\",\n    \"co-fs\": \"^1.2.0\",\n    \"co-fs-extra\": \"^1.1.0\",\n    \"co-views\": \"^2.1.0\",\n    \"ejs\": \"^2.3.4\",\n    \"jszip\": \"^3.1.3\",\n    \"koa\": \"^1.0.0\",\n    \"koa-bodyparser\": \"^2.0.1\",\n    \"koa-morgan\": \"^0.3.0\",\n    \"koa-mount\": \"^1.3.0\",\n    \"koa-router\": \"^5.1.2\",\n    \"koa-static\": \"^1.4.9\",\n    \"optimist\": \"^0.6.1\",\n    \"tracer\": \"^0.8.0\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/efeiefei/node-file-manager.git\"\n  },\n  \"keywords\": [\n    \"files\",\n    \"manager\",\n    \"koa\",\n    \"angular\",\n    \"web\",\n    \"server\"\n  ],\n  \"author\": {\n    \"name\": \"efei\",\n    \"email\": \"efeigm@gmail.com\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/efeiefei/node-file-manager/issues\"\n  },\n  \"homepage\": \"https://github.com/efeiefei/node-file-manager#readme\"\n}\n"
  }
]