diff --git a/agent/apps/ztm/file/cli.js b/agent/apps/ztm/file/cli.js index 51b20873..2276a359 100644 --- a/agent/apps/ztm/file/cli.js +++ b/agent/apps/ztm/file/cli.js @@ -1,9 +1,9 @@ -export default function ({ api, utils }) { +export default function ({ app, mesh, api, utils }) { return pipeline($=>$ .onStart(ctx => main(ctx)) ) - function main({ argv, endpoint }) { + function main({ argv, cwd, endpoint }) { var buffer = new Data function output(str) { @@ -24,32 +24,85 @@ export default function ({ api, utils }) { return utils.parseArgv(argv, { help: text => Promise.resolve(output(text + '\n')), commands: [ - { - title: 'Edit the access control list', - usage: 'acl ', - options: ` - --set-all Set access for all users - --set-writable Add writable access for users - --set-readonly Add readonly access for users - --set-block Block access for users - --set-default Reset access to default for users - `, - action: (args) => api.getACL(endpoint.id).then(acl => { - }) - }, { title: 'List all files in a directory', - usage: 'ls []', + usage: 'ls ', action: (args) => { + var pathname = os.path.normalize(args['']) + return api.statFile(endpoint.id, pathname, app.username).then( + stat => { + if (!stat) throw `No such file or directory on ${endpoint.name}: ${pathname}` + if (!stat.dir) return [{ name: os.path.basename(pathname), size: stat.size, time: stat.mtime }] + return Promise.all( + stat.dir.sort().map( + name => api.statFile(endpoint.id, os.path.join(pathname, name), app.username).then( + stat => stat && { name, size: stat.size, time: stat.mtime } + ) + ) + ).then( + stats => stats.filter(s=>s) + ) + } + ).then(list => { + output(printTable(list, { + 'NAME': s => s.name, + 'SIZE': s => s.size, + 'DATE': s => new Date(s.time * 1000).toString(), + })) + }) } }, { title: 'Copy files or directories', - usage: 'cp ', - options: ` - -R, -r, --recursive Copy directories recursively - `, - action: (args) => {} + usage: 'cp <[src-ep:]pathname> <[dst-ep:]pathname>', + action: (args) => { + var src = args['<[src-ep:]pathname>'] + var dst = args['<[dst-ep:]pathname>'] + return mesh.discover().then(endpoints => { + function resolvePathname(pathname) { + var i = pathname.indexOf(':') + var epID = pathname.substring(0,i) + pathname = pathname.substring(i+1) + if (epID) { + var list = endpoints.filter(ep => ep.name === epID) + if (list.length === 0) list = endpoints.filter(ep => ep.id === epID) + if (list.length > 1) throw 'Ambiguous endpoint name: ' + epID + if (list.length < 1) throw 'Endpoint not found: ' + epID + epID = list[0].id + } else { + epID = app.endpoint.id + pathname = os.path.resolve(cwd, pathname) + } + return { ep: epID, pathname } + } + src = resolvePathname(src) + dst = resolvePathname(dst) + }).then(() => { + return api.startTransfer( + dst.ep, dst.pathname, + src.ep, src.pathname, + app.username + ).then(ret => { + if (!ret) throw 'Cannot copy files' + function wait() { + return new Timeout(1).wait().then( + () => api.getTransfer(dst.ep, dst.pathname, app.username) + ).then(transfer => { + var state = transfer?.state + if (state === 'working') return wait() + api.abortTransfer(dst.ep, dst.pathname, app.username) + if (state === 'error') throw 'Failed to copy files' + transfer?.copied?.forEach?.(filename => output(filename + '\n')) + switch (state) { + case 'done': output('Done\n'); break + case 'abort': output('Aborted\n'); break + } + }) + } + return wait() + }) + }) + } }, ] diff --git a/agent/apps/ztm/file/main.js b/agent/apps/ztm/file/main.js index f1b4e01f..38e88dfb 100644 --- a/agent/apps/ztm/file/main.js +++ b/agent/apps/ztm/file/main.js @@ -11,9 +11,6 @@ export default function ({ app, mesh, utils }) { var gui = new http.Directory(os.path.join(app.root, 'gui')) var response = utils.createResponse var responder = utils.createResponder - var responderOwnerOnly = (f) => responder((params, req) => ( - $ctx.peer.username === app.username ? f(params, req) : Promise.resolve(response(403)) - )) var serveUser = utils.createServer({ '/cli': { @@ -88,24 +85,6 @@ export default function ({ app, mesh, utils }) { ), }, - '/api/endpoints/{ep}/acl/*': { - 'GET': responder((params) => { - var ep = params.ep - var pathname = URL.decodeComponent(params['*']) - return api.getACL(ep, pathname).then( - ret => ret ? response(200, ret) : response(404) - ) - }), - - 'POST': responder((params, req) => { - var ep = params.ep - var pathname = URL.decodeComponent(params['*']) - return api.setACL(ep, pathname, JSON.decode(req.body)).then( - ret => response(ret ? 201 : 404) - ) - }), - }, - '/api/endpoints/{ep}/transfers': { 'GET': responder((params) => { var ep = params.ep @@ -201,22 +180,6 @@ export default function ({ app, mesh, utils }) { ), }, - '/api/acl/*': { - 'GET': responderOwnerOnly((params) => { - var pathname = URL.decodeComponent(params['*']) - return api.getACL(app.endpoint.id, pathname).then( - ret => ret ? response(200, ret) : response(404) - ) - }), - - 'POST': responderOwnerOnly((params, req) => { - var pathname = URL.decodeComponent(params['*']) - return api.setACL(app.endpoint.id, pathname, JSON.decode(req.body)).then( - ret => response(ret ? 201 : 404) - ) - }), - }, - '/api/transfers': { 'GET': responder(() => { return api.allTransfers(app.endpoint.id, $ctx.peer.username).then(