UNPKG

cloudcmd

Version:

File manager for the web with console and editor

408 lines (306 loc) 9.81 kB
'use strict'; const path = require('node:path'); const _fs = require('node:fs'); const process = require('node:process'); const jaguar = require('jaguar'); const onezip = require('onezip'); const inly = require('inly'); const wraptile = require('wraptile'); const currify = require('currify'); const pullout = require('pullout'); const json = require('jonny'); const ponse = require('ponse'); const copymitter = require('copymitter'); const _moveFiles = require('@cloudcmd/move-files'); const root = require(`../root`); const CloudFunc = require(`../../common/cloudfunc`); const markdown = require(`../markdown/index.js`); const info = require('./info'); const isUndefined = (a) => typeof a === 'undefined'; const isRootAll = (root, names) => names.some(isRootWin32(root)); const isString = (a) => typeof a === 'string'; const isFn = (a) => typeof a === 'function'; const swap = wraptile((fn, a, b) => fn(b, a)); const isWin32 = process.platform === 'win32'; const {apiURL} = CloudFunc; const UserError = (msg) => { const error = Error(msg); error.code = 'EUSER'; return error; }; module.exports = currify(({config, fs = _fs, moveFiles = _moveFiles}, request, response, next) => { const name = ponse.getPathName(request); const regExp = RegExp(`^${apiURL}`); const is = regExp.test(name); if (!is) return next(); rest({fs, config, moveFiles}, request, response); }); function rest({fs, config, moveFiles}, request, response) { const name = ponse.getPathName(request); const params = { request, response, name: name.replace(apiURL, '') || '/', }; sendData(params, {fs, config, moveFiles}, (error, options, data) => { params.gzip = !error; if (!data) { data = options; options = {}; } if (options.name) params.name = options.name; if (!isUndefined(options.gzip)) params.gzip = options.gzip; if (options.query) params.query = options.query; if (error?.code) return ponse.sendError(error, params); if (error) return ponse.sendError(error.stack, params); ponse.send(data, params); }); } /** * getting data on method and command * * @param params {name, method, body, requrest, response} * @param config {} * @param callback */ function sendData(params, {fs, config, moveFiles}, callback) { const p = params; const isMD = p.name.startsWith('/markdown'); const rootDir = config('root'); if (isMD) return markdown(p.name, rootDir, p.request, callback); const {method} = p.request; switch(method) { case 'GET': return onGET(params, config, callback); case 'PUT': return pullout(p.request) .then((body) => { onPUT({ name: p.name, fs, moveFiles, config, body, }, callback); }) .catch(callback); } } function onGET(params, config, callback) { let cmd; const p = params; const packer = config('packer'); const prefix = config('prefix'); const rootDir = config('root'); if (p.name[0] === '/') cmd = p.name.replace('/', ''); if (cmd.startsWith('pack')) { cmd = cmd.replace(/^pack/, ''); streamPack(root(cmd, rootDir), p.response, packer); return; } switch(cmd) { case '': p.data = json.stringify(info(prefix)); callback(null, {name: 'api.json'}, p.data); break; default: callback({ message: 'Not Found', }); break; } } function getPackReg(packer) { if (packer === 'zip') return /\.zip$/; return /\.tar\.gz$/; } function streamPack(cmd, response, packer) { const noop = () => {}; const filename = cmd.replace(getPackReg(packer), ''); const dir = path.dirname(filename); const names = [ path.basename(filename), ]; operation('pack', packer, dir, response, names, noop); } function getCMD(cmd) { if (cmd[0] === '/') return cmd.slice(1); return cmd; } const getMoveMsg = (names) => formatMsg('move', names); const getRenameMsg = (from, to) => { const msg = formatMsg('rename', { from, to, }); return msg; }; module.exports._onPUT = onPUT; function onPUT({name, fs, moveFiles, config, body}, callback) { checkPut(name, body, callback); const cmd = getCMD(name); const files = json.parse(body); const rootDir = config('root'); switch(cmd) { case 'move': { const { from, to, names, } = files; if (!from) return callback(UserError('"from" should be filled')); if (!to) return callback(UserError('"to" should be filled')); if (isRootAll(rootDir, [to, from])) return callback(getWin32RootMsg()); const msg = getMoveMsg(names); const fn = swap(callback, msg); const fromRooted = root(from, rootDir); const toRooted = root(to, rootDir); return moveFiles(fromRooted, toRooted, names) .on('error', fn) .on('end', fn); } case 'rename': return rename(rootDir, files.from, files.to, fs, callback); case 'copy': if (!files.from || !files.names || !files.to) return callback(body); if (isRootAll(rootDir, [files.to, files.from])) return callback(getWin32RootMsg()); files.from = root(files.from, rootDir); files.to = root(files.to, rootDir); copy(files.from, files.to, files.names, (error) => { const msg = formatMsg('copy', files.names); callback(error, msg); }); break; case 'pack': if (!files.from) return callback(body); pack(files.from, files.to, files.names, config, callback); break; case 'extract': if (!files.from) return callback(body); extract(files.from, files.to, config, callback); break; default: callback(); break; } } function rename(rootDir, from, to, fs, callback) { if (!from) return callback(UserError('"from" should be filled')); if (!to) return callback(UserError('"to" should be filled')); const msg = getRenameMsg(from, to); const fn = swap(callback, msg); const fromRooted = root(from, rootDir); const toRooted = root(to, rootDir); return fs.rename(fromRooted, toRooted, fn); } function pack(from, to, names, config, fn) { const rootDir = config('root'); const packer = config('packer'); from = root(from, rootDir); to = root(to, rootDir); if (!names) { names = [ path.basename(from), ]; from = path.dirname(from); } operation('pack', packer, from, to, names, fn); } function extract(from, to, config, fn) { const rootDir = config('root'); from = root(from, rootDir); if (to) to = root(to, rootDir); else to = from.replace(/\.tar\.gz$/, ''); operation('extract', config('packer'), from, to, fn); } function getPacker(operation, packer) { if (operation === 'extract') return inly; if (packer === 'zip') return onezip.pack; return jaguar.pack; } function operation(op, packer, from, to, names, fn) { if (!fn) { fn = names; names = [ path.basename(from), ]; } const packerFn = getPacker(op, packer); const pack = packerFn(from, to, names); pack.on('error', fn); const [name] = names; pack.on('progress', (count) => { process.stdout.write(`\r${op} "${name}": ${count}%`); }); pack.on('end', () => { process.stdout.write('\n'); const name = path.basename(from); const msg = formatMsg(op, name); fn(null, msg); }); } function copy(from, to, names, fn) { copymitter(from, to, names) .on('error', fn) .on('progress', (count) => { process.stdout.write(`\r copy ${from} ${to} ${count}%`); }) .on('end', () => { process.stdout.write('\n'); fn(); }); } const isRootWin32 = currify((root, path) => { const isRoot = path === '/'; const isConfig = root === '/'; return isWin32 && isRoot && isConfig; }); module.exports._isRootWin32 = isRootWin32; module.exports._isRootAll = isRootAll; module.exports._getWin32RootMsg = getWin32RootMsg; function getWin32RootMsg() { const message = 'Could not copy from/to root on windows!'; return Error(message); } function parseData(data) { const isObj = typeof data === 'object'; if (!isObj) return data; return json.stringify(data); } module.exports._formatMsg = formatMsg; function formatMsg(msg, data, status) { const value = parseData(data); return CloudFunc.formatMsg(msg, value, status); } function checkPut(name, body, callback) { if (!isString(name)) throw Error('name should be a string!'); if (!isString(body)) throw Error('body should be a string!'); if (!isFn(callback)) throw Error('callback should be a function!'); }