diff --git a/app/controllers/install.js b/app/controllers/install.js
index ae958c25..7cd32fe2 100644
--- a/app/controllers/install.js
+++ b/app/controllers/install.js
@@ -7,6 +7,9 @@ const cache = require('../utils/cache')
module.exports = {
+ /**
+ * Install index handler
+ */
async home(ctx , next){
if(config.installed() ){
ctx.redirect('/')
@@ -16,6 +19,9 @@ module.exports = {
}
}
,
+ /**
+ * Save config handler
+ */
async save(ctx){
let { token , name , path , vendor , title = 'ShareList'} = ctx.request.body
let cfg = {token , title}
diff --git a/app/controllers/manage.js b/app/controllers/manage.js
index 1868d8bc..c484bf61 100644
--- a/app/controllers/manage.js
+++ b/app/controllers/manage.js
@@ -5,6 +5,14 @@ const cache = require('../utils/cache')
const { getVendors , reload } = require('../services/plugin')
const service = require('../services/sharelist')
+/**
+ * Hanlders hub
+ *
+ * @param {string} [a] action
+ * @param {object} [body] formdata
+ * @param {object} [ctx] ctx
+ * @return {object}
+ */
const handlers = async (a, body , ctx) => {
let result = { status: 0, message: 'Success', data: '', a }
@@ -144,6 +152,9 @@ const handlers = async (a, body , ctx) => {
module.exports = {
+ /**
+ * Manage page index handler
+ */
async home(ctx, next) {
let token = ctx.request.body.token
@@ -162,6 +173,9 @@ module.exports = {
},
+ /**
+ * API router handler
+ */
async api(ctx) {
let body = ctx.request.body
@@ -210,6 +224,9 @@ module.exports = {
},
+ /**
+ * Shell page handler
+ */
async shell(ctx){
let access = !!ctx.session.admin
if(access){
@@ -219,6 +236,12 @@ module.exports = {
}
},
+ /**
+ * Shell exection
+ *
+ * @param {object} [ctx]
+ * @return {void}
+ */
async shell_exec(ctx){
let body = ctx.request.body
let { command , path = '/' } = body
diff --git a/app/controllers/sharelist.js b/app/controllers/sharelist.js
index 3f108457..733fc59f 100644
--- a/app/controllers/sharelist.js
+++ b/app/controllers/sharelist.js
@@ -4,8 +4,13 @@ const qs = require('querystring')
const { sendRedirect } = require('../utils/sendfile')
const { parsePath , pathNormalize , enablePreview, enableRange , isRelativePath , markdownParse , md5 } = require('../utils/base')
-const requireAuth = (data) => !!(data.children && data.children.find(i=>(i.name == '.passwd')))
-
+/**
+ * Check path
+ *
+ * @param {string} [path] current path
+ * @param {array} [paths] allow proxy paths
+ * @return [boolean]
+ */
const isProxyPath = (path , paths) => {
return (
path == '' || path == '/' ||
@@ -14,6 +19,13 @@ const isProxyPath = (path , paths) => {
) ? true : false
}
+/**
+ * Check download condition
+ *
+ * @param {object} [ctx]
+ * @param {object} [data] folder/file data
+ * @return [boolean]
+ */
const enableDownload = (ctx, data) => {
if(ctx.runtime.isAdmin) return true
let result = true
@@ -29,6 +41,12 @@ const enableDownload = (ctx, data) => {
return result
}
+/**
+ * Output handler
+ *
+ * @param {object} [ctx]
+ * @param {object} [data] folder/file data
+ */
const output = async (ctx , data)=>{
const download = enableDownload(ctx, data)
@@ -131,6 +149,9 @@ const output = async (ctx , data)=>{
}
module.exports = {
+ /**
+ * Index handler
+ */
async index(ctx){
let downloadLinkAge = config.getConfig('max_age_download')
let cursign = md5(config.getConfig('max_age_download_sign') + Math.floor(Date.now() / downloadLinkAge))
@@ -261,6 +282,9 @@ module.exports = {
},
+ /**
+ * API handler
+ */
async api(ctx){
let ignoreexts = (config.getConfig('ignore_file_extensions') || '').split(',')
let ignorefiles = (config.getConfig('ignore_files') || '').split(',')
diff --git a/app/controllers/webdav.js b/app/controllers/webdav.js
index d13a5c27..9d92dbdb 100644
--- a/app/controllers/webdav.js
+++ b/app/controllers/webdav.js
@@ -9,6 +9,9 @@ var virtualFile = {
}
+/**
+ * Webdav props default options
+ */
const default_options = {
ns:{
name:'D',
@@ -24,6 +27,12 @@ const default_options = {
}
}
+/**
+ * Conv date to GMT
+ *
+ * @param {string} [d]
+ * @return {mixed}
+ */
const dateFormat = (d) => {
let nd = new Date(d)
if (nd instanceof Date && !isNaN(nd)) {
@@ -33,6 +42,17 @@ const dateFormat = (d) => {
}
}
+/**
+ * Create webdav xml response
+ *
+ * @param {object} [data]
+ * @param {object} [options]
+ * @param {object} [optiosn.props]
+ * @param {object} [optiosn.ns]
+ * @param {string} [optiosn.ns.name]
+ * @param {string} [optiosn.ns.value]
+ * @return {string} XML string
+ */
const propsCreate = (data, options) => {
let out = ''
let { props, ns: { name, value } } = options
@@ -71,6 +91,12 @@ const propsCreate = (data, options) => {
return out
}
+/**
+ * Parse prop from webdab request
+ *
+ * @param {object} [data]
+ * @return {object}
+ */
const propfindParse = (data, ns) => {
if(!data){
return default_options
@@ -98,6 +124,12 @@ const propfindParse = (data, ns) => {
}
}
+/**
+ * Parse props from webdav request
+ *
+ * @param {object} [data]
+ * @return {object|boolean}
+ */
const nsParse = (data) => {
if(!data) return false
@@ -116,8 +148,16 @@ const nsParse = (data) => {
return false
}
-
-// http://www.domain.example.com/public/ 0 James Smith Infinite opaquelocktoken:f81de2ad-7f3d-a1b3-4f3c-00a0c91a9d76 HTTP/1.1 200 OK
+/**
+ * Create webdav responese xml by data and props options
+ *
+ * @param {object} [data] file data
+ * @param {object} [options]
+ * @param {object} [options.props] Available props
+ * @param {object} [options.path] Current folder path
+ * @param {object} [options.ns]
+ * @return {string} XML string
+ */
const respCreate = (data, options) => {
let { props, path, ns: { name, value } } = options
@@ -136,44 +176,15 @@ const respCreate = (data, options) => {
body += `${xmlns}multistatus>`
body = body.replace(/^\s+/g,'').replace(/[\r\n]/g,'')
- // console.log(body)
- /*return `
-
-
- /%E6%BC%94%E7%A4%BA%E7%9B%AE%E5%BD%95/example/filesystem_windows_disk_c/Users/abcdef
-
- HTTP/1.1 200 OK
-
- Mon, 25 Feb 2019 12:20:01 GMT
- 100
- Mon, 25 Feb 2019 12:20:01 GMT
-
-
-
- 你好
-
-
-
-
- /%E6%BC%94%E7%A4%BA%E7%9B%AE%E5%BD%95/example/filesystem_windows_disk_c/Users/abcdef2
-
- HTTP/1.1 200 OK
-
- Mon, 25 Feb 2019 12:20:01 GMT
- 100
- Mon, 25 Feb 2019 12:20:01 GMT
-
-
-
- 你好2
-
-
-
-`*/
return body
}
class Request {
+ /**
+ * Initialize a new Request for WebDAV
+ *
+ * @param {Object} ctx
+ */
constructor(ctx) {
this.ctx = ctx
this.davPoweredBy = null
@@ -181,6 +192,11 @@ class Request {
this.allows = ['GET', 'PUT', 'HEAD', 'OPTIONS', 'PROPFIND']
}
+ /**
+ * Execute handler
+ *
+ * @api private
+ */
async exec(){
let { ctx } = this
@@ -205,16 +221,36 @@ class Request {
return false
}
}
+
+ /**
+ * Set header
+ *
+ * @param {string} [k] key
+ * @param {string} [v] value
+ * @return void
+ */
setHeader(k, v) {
this.ctx.set(k, v)
}
+ /**
+ * Set body
+ *
+ * @param {mixed} [body]
+ * @return void
+ */
setBody(body) {
this.ctx.type = 'text/xml; charset="utf-8"'
this.setHeader('Content-Length', body.length);
this.ctx.body = body
}
+ /**
+ * Set body status
+ *
+ * @param {string|boolean} [status]
+ * @return void
+ */
setStatus(status) {
if (status === true) {
status = "200 OK"
@@ -225,6 +261,12 @@ class Request {
this.setHeader('X-WebDAV-Status', status)
}
+ /**
+ * OPTIONS method
+ *
+ * @param void
+ * @return void
+ */
async http_options() {
const allows = this.allows
@@ -308,35 +350,6 @@ class Request {
async http_get() {
await api(this.ctx)
}
- /*
- //create
- async http_put() {
- let ret = await api(this.ctx)
- this.setStatus("200 Success")
- }
-
- // put时 webdav 将lock文件 此方法没有实现
- async http_lock(){
- if( !virtualFile[this.ctx.path] ){
- virtualFile[this.ctx.path] = {}
- }
-
- virtualFile[this.ctx.path]['locked'] = true
- this.setStatus("200 Success")
- this.setBody(` ${this.ctx.path} 0 ShareList Infinite opaquelocktoken:f81de2ad-7f3d-a1b3-4f3c-00a0c91a9d76 HTTP/1.1 200 OK `)
-
- }
-
- async http_unlock(){
- if( !virtualFile[this.ctx.path] ){
- virtualFile[this.ctx.path] = {}
- }
- virtualFile[this.ctx.path]['locked'] = false
-
- this.setStatus("200 Success")
- }
- */
-
/*
http_head() {}
diff --git a/app/plugins/drive.fs.js b/app/plugins/drive.fs.js
index 33e10fd1..95798aa6 100644
--- a/app/plugins/drive.fs.js
+++ b/app/plugins/drive.fs.js
@@ -1,113 +1,138 @@
-/*
- * 提供对本地文件系统的支持
- * file:linux风格路径
+/**
+ * Mount file system
*/
+const path = require('path')
+const fs = require('fs')
+const os = require('os')
-const name = 'FileSystem'
+const isWinOS = os.platform() == 'win32'
-const version = '1.0'
+/**
+ * Convert posix style path to windows style
+ *
+ * @param {string} [p]
+ * @return {string}
+ */
+const winStyle = (p) => p.replace(/^\/([^\/]+?)/, '$1:\\').replace(/\//g, '\\').replace(/(? p.split('\\').join('/').replace(/^([a-z])\:/i,'/$1')
-const defaultProtocol = 'fs'
+/**
+ * normalize path(posix style) and replace current path
+ *
+ * @param {string} [p]
+ * @return {string}
+ */
+const normalize = (p) => path.posix.normalize(p.replace(/^\.\//, slpath(process.cwd()) + '/'))
-const path = require('path')
-const fs = require('fs')
-const os = require('os')
+const slpath = (p) => (isWinOS ? posixStyle(p) : p)
+
+const realpath = (p) => (isWinOS ? winStyle(p) : p)
-const isWinOS = os.platform() == 'win32'
-const l2w = (p) => p.replace(/^\/([^\/]+?)/,'$1:\\').replace(/\//g,'\\').replace(/(? (isWinOS ? l2w(p) : p)
+ this.version = '1.0'
+ this.protocol = 'fs'
+ }
+
+ mkdir(p) {
+ if (fs.existsSync(p) == false) {
+ this.mkdir(path.dirname(p));
+ fs.mkdirSync(p);
+ }
+ }
-module.exports = ({datetime , extname , pathNormalize}) => {
+ path(id) {
+ let { datetime, extname } = this.helper
- const normalize = (p) => pathNormalize(p).replace(/^\.\//,process.cwd()+'/')
+ let { protocol } = this
- const folder = async(id , {_path=[]} = {}) => {
let dir = normalize(id)
- if( _path.length > 0) {
- dir = _path.length.join('/')
- }
- let resp = { id : dir , type:'folder', protocol:defaultProtocol}
let realdir = realpath(dir)
- if( fs.existsSync(realdir) ){
- let children = []
- fs.readdirSync(realdir).forEach(function(filename){
- let path = normalize(dir) + '/' + filename
+ let stat = fs.statSync(realdir)
+
+ if (stat.isDirectory()) {
+ let children = []
+ fs.readdirSync(realdir).forEach((filename) => {
+ let path = normalize(dir + '/' + filename)
let stat
- try{
+ try {
stat = fs.statSync(realpath(path))
- }catch(e){}
+ } catch (e) {}
let obj = {
- id:path ,
- name:filename,
- protocol:defaultProtocol,
- type:'other'
+ id: path,
+ name: filename,
+ protocol: this.protocol,
+ type: 'other'
}
- if(stat){
+ if (stat) {
obj.created_at = datetime(stat.ctime)
obj.updated_at = datetime(stat.mtime)
- if(stat.isDirectory()){
+ if (stat.isDirectory()) {
obj.type = 'folder'
- }
- else if(stat.isFile()){
+ } else if (stat.isFile()) {
obj.ext = extname(filename)
obj.size = stat.size
}
}
children.push(obj)
})
- resp.children = children
- return resp
- }else{
+
+ return { id: dir, type: 'folder', protocol: this.protocol , children }
+
+ } else if (stat.isFile()) {
+ return {
+ id,
+ name: path.basename(id),
+ protocol: this.protocol,
+ ext: extname(id),
+ url: realpath(id),
+ size: stat.size,
+ outputType: 'file',
+ proxy: true
+ }
+ } else {
return false
}
- }
- const file = async(id)=>{
- let realdir = realpath(normalize(id))
- let stat = {}
- try{
- stat = fs.statSync(realpath(realdir))
- }catch(e){}
-
- return {
- id,
- name: path.basename(id),
- ext: extname(id),
- url: realpath(id),
- size:stat.size,
- protocol:defaultProtocol,
- outputType:'file',
- proxy:true
- }
}
- const createReadStream = ({id , options = {}} = {}) => {
- return fs.createReadStream(realpath(id) , {...options,highWaterMark:64*1024})
+ folder(id) {
+ return this.path(id)
}
- const mkdir = (p) => {
- if (fs.existsSync(p) == false) {
- mkdir(path.dirname(p));
- fs.mkdirSync(p);
- }
- };
+ file(id) {
+ return this.path(id)
+ }
+ async createReadStream({ id, options = {} } = {}) {
+ return fs.createReadStream(realpath(id), { ...options, highWaterMark: 64 * 1024 })
+ }
- const createWriteStream = async ({ id , options = {} , target = ''} = {}) => {
- let fullpath = pathNormalize(id +'/' + target)
- let parent = (fullpath.split('/').slice(0,-1).join('/') + '/').replace(/\/+$/g,'/')
- mkdir(parent)
- return fs.createWriteStream(realpath(fullpath) , options)
+ async createWriteStream({ id, options = {}, target = '' } = {}) {
+ let fullpath = path.join(id , target)
+ let parent = (fullpath.split('/').slice(0, -1).join('/') + '/').replace(/\/+$/g, '/')
+ this.mkdir(parent)
+ return fs.createWriteStream(realpath(fullpath), options)
}
+}
+
- return { name , label:'本地文件',version , drive:{ protocols , folder , file , cache:false , createReadStream , createWriteStream } }
-}
\ No newline at end of file
+module.exports = FileSystem
\ No newline at end of file
diff --git a/app/services/plugin.js b/app/services/plugin.js
index 8d36f30a..c6c46aa7 100644
--- a/app/services/plugin.js
+++ b/app/services/plugin.js
@@ -45,6 +45,8 @@ const whenReady = (handler) => {
}
}
+const isClass = fn => typeof fn == 'function' && /^\s*class/.test(fn.toString());
+
var ready = false
var resourcesCount = 0
@@ -300,56 +302,75 @@ const load = (options) => {
const type = name.split('.')[0]
const id = 'plugin_' + pluginName.replace(/\./g,'_')
const helpers = getHelpers(id)
- const resource = require(filepath).call(helpers,helpers)
-
+ let ins = require(filepath)
console.log('Load Plugins: ',pluginName)
- resources[id] = resource
-
- if( resource.auth ){
- for(let key in resource.auth){
- authMap.set(key , id)
+ let resource
+ if( isClass(ins) ){
+ let driver = new ins()
+ let { protocol , mountable , createReadStream , createWriteStream } = driver
+ driver.helper = helpers
+ resources[id] = {
+ label:driver.label,
+ mountable,protocol,
+ drive:driver,
+ name:driver.name
}
- }
+ driveMap.set(protocol , id)
- if( resource.drive ){
- let protocols = [].concat(resource.drive.protocols || [])
- let mountable = resource.drive.mountable !== false
- protocols.forEach( protocol => {
- driveMap.set(protocol,id)
- if(mountable) driveMountableMap.set(protocol , id)
- })
+ if(mountable) driveMountableMap.set(protocol , id)
+ if(createReadStream) readstreamMap.set(protocol , driver.createReadStream)
+ }else{
+ resource = ins.call(helpers,helpers)
- if( resource.drive.createReadStream ){
- protocols.forEach( protocol => {
- readstreamMap.set(protocol , resource.drive.createReadStream)
- })
+ resources[id] = resource
+
+ if( resource.auth ){
+ for(let key in resource.auth){
+ authMap.set(key , id)
+ }
}
- if( resource.drive.createWriteStream ){
+ if( resource.drive ){
+ let protocols = [].concat(resource.drive.protocols || [])
+ let mountable = resource.drive.mountable !== false
protocols.forEach( protocol => {
- writestreamMap.set(protocol , resource.drive.createWriteStream)
+ driveMap.set(protocol,id)
+ if(mountable) driveMountableMap.set(protocol , id)
})
+
+ if( resource.drive.createReadStream ){
+ protocols.forEach( protocol => {
+ readstreamMap.set(protocol , resource.drive.createReadStream)
+ })
+ }
+
+ if( resource.drive.createWriteStream ){
+ protocols.forEach( protocol => {
+ writestreamMap.set(protocol , resource.drive.createWriteStream)
+ })
+ }
}
- }
-
- if(resource.format){
- for(let key in resource.format){
- formatMap.set(key , id)
+
+ if(resource.format){
+ for(let key in resource.format){
+ formatMap.set(key , id)
+ }
}
- }
- if(resource.preview){
- for(let key in resource.preview){
- previewMap.set(key , id)
+ if(resource.preview){
+ for(let key in resource.preview){
+ previewMap.set(key , id)
+ }
}
- }
- if(resource.cmd){
- for(let key in resource.cmd){
- cmdMap.set(key , id)
+ if(resource.cmd){
+ for(let key in resource.cmd){
+ cmdMap.set(key , id)
+ }
}
}
+
}
}
}
@@ -365,7 +386,7 @@ const reload = () => {
load(loadOptions)
}
/**
- * 根据扩展名获取可处理的驱动
+ * 根据协议获取可处理的驱动
*/
const getDrive = (protocol) => {
if( driveMap.has(protocol)){
@@ -512,7 +533,7 @@ const getVendors = () => [...new Set(driveMountableMap.values())].map(id => {
return {
name : resources[id].name,
label: resources[id].label || resources[id].name,
- protocol : resources[id].drive.protocols[0]
+ protocol : resources[id].protocol || resources[id].drive.protocols[0]
}
})
@@ -548,7 +569,11 @@ const createReadStream = async (options) => {
const createWriteStream = async (options) => {
let { id , protocol } = options
- if( writestreamMap.has(protocol) ){
+
+ let drive = getDrive(protocol)
+ if(drive.createWriteStream){
+ return await drive.createWriteStream(options)
+ }else if( writestreamMap.has(protocol) ){
return await writestreamMap.get(protocol)(options)
}
}
diff --git a/app/services/sharelist.js b/app/services/sharelist.js
index 0b648523..35e418ae 100644
--- a/app/services/sharelist.js
+++ b/app/services/sharelist.js
@@ -14,6 +14,14 @@ class ShareList {
this.passwdPaths = new Set()
}
+ /**
+ * Path diff
+ *
+ * @param {string} [a] path_a
+ * @param {string} [b] path_b
+ * @return {boolean}
+ * @api private
+ */
async diff(a,b){
let ret = []
b.forEach((v, i) => {
@@ -24,10 +32,24 @@ class ShareList {
return ret
}
+ /**
+ * Check folder contain passwd file
+ *
+ * @param {object} [data]
+ * @return {boolean}
+ * @api private
+ */
hasPasswdFile(data){
return !!(data.children && data.children.find(i=>(i.name == '.passwd')))
}
+ /**
+ * Search the first serect path
+ *
+ * @param {object} [req]
+ * @return {mixed}
+ * @api private
+ */
searchPasswdPath(req){
for(let i = 1 ; i <= req.paths.length ; i++){
let path = '/'+req.paths.slice(0,i).join('/')
@@ -39,12 +61,19 @@ class ShareList {
return null
}
+ /**
+ * Find the folder(file) data by path
+ *
+ * @param {object} [req]
+ * @return {object}
+ * @api public
+ */
async path(req) {
if( req.body && req.body.act == 'auth' ){
let ra = await this.auth(req)
return { type:'auth_response' , result: ra }
}
- //上传
+ // upload request
else if(req.upload){
if(!req.upload.enable){
return {
@@ -113,7 +142,13 @@ class ShareList {
}
}
-
+ /**
+ * Verify auth by path
+ *
+ * @param {object} [req]
+ * @return {boolean}
+ * @api private
+ */
async auth(req) {
let data = await command('ls' , req.paths.join('/'))
let hit = data.children.find(i => i.name == '.passwd')
@@ -129,31 +164,64 @@ class ShareList {
}
return false
}
- /*
- * 获取文件预览页面
+
+ /**
+ * Get previewable data
+ *
+ * @param {object}
+ * @return {object}
+ * @api public
*/
async preview(data){
return await getPreview(data)
}
- /*
- * 根据文件ID和协议获取可读流
+
+ /**
+ * Get readable stream by file id
+ *
+ * @param {object} [ctx] * required
+ * @param {string} [id] * required file id
+ * @param {type} [type] * required stream type
+ * @return {stream}
+ * @api public
*/
async stream(ctx , id , type , protocol , data){
return await getStream(ctx , id , type , protocol , data)
}
+ /**
+ * Get file content by file id
+ *
+ * @param {string} [id] * required file id
+ * @param {type} [protocol] * required stream type
+ * @param {object} [data] file data
+ * @return {stream}
+ * @api public
+ */
async source(id , protocol , data){
return await getSource(id , protocol , data)
}
+ /**
+ * Execute core command
+ *
+ * @param {string} [v] command and args
+ * @param {string} [path] command execution path
+ * @return {mixed}
+ * @api public
+ */
async exec(v , path){
// TODO yargs
let [cmd , ...options] = v.split(/\s+/)
return await command(cmd , path , options , true)
}
- /*
- * 检测文件是否支持预览
+ /**
+ * Check the file support preview
+ *
+ * @param {object} [data] file data
+ * @return {boolean}
+ * @api public
*/
async isPreviewable(data){
return await isPreviewable(data)
@@ -161,4 +229,4 @@ class ShareList {
}
-module.exports = new ShareList()
+module.exports = new ShareList()
\ No newline at end of file