cli.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. #!/usr/bin/env node
  2. /*
  3. * This file is part of the storage node for the Joystream project.
  4. * Copyright (C) 2019 Joystream Contributors
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. 'use strict';
  20. const path = require('path');
  21. const fs = require('fs');
  22. const assert = require('assert');
  23. const { RuntimeApi } = require('@joystream/runtime-api');
  24. const meow = require('meow');
  25. const chalk = require('chalk');
  26. const _ = require('lodash');
  27. const debug = require('debug')('joystream:cli');
  28. // Project root
  29. const project_root = path.resolve(__dirname, '..');
  30. // Configuration (default)
  31. const pkg = require(path.resolve(project_root, 'package.json'));
  32. // Parse CLI
  33. const FLAG_DEFINITIONS = {
  34. // TODO
  35. };
  36. const cli = meow(`
  37. Usage:
  38. $ joystream key_file command [options]
  39. All commands require a key file holding the identity for interacting with the
  40. runtime API.
  41. Commands:
  42. upload Upload a file to a Colossus storage node. Requires a
  43. storage node URL, and a local file name to upload. As
  44. an optional third parameter, you can provide a Data
  45. Object Type ID - this defaults to "1" if not provided.
  46. download Retrieve a file. Requires a storage node URL and a content
  47. ID, as well as an output filename.
  48. head Send a HEAD request for a file, and print headers.
  49. Requires a storage node URL and a content ID.
  50. `,
  51. { flags: FLAG_DEFINITIONS });
  52. function assert_file(name, filename)
  53. {
  54. assert(filename, `Need a ${name} parameter to proceed!`);
  55. assert(fs.statSync(filename).isFile(), `Path "${filename}" is not a file, aborting!`);
  56. }
  57. const commands = {
  58. 'upload': async (runtime_api, url, filename, do_type_id) => {
  59. // Check parameters
  60. assert_file('file', filename);
  61. const size = fs.statSync(filename).size;
  62. console.log(`File "${filename}" is ` + chalk.green(size) + ' Bytes.');
  63. if (!do_type_id) {
  64. do_type_id = 1;
  65. }
  66. console.log('Data Object Type ID is: ' + chalk.green(do_type_id));
  67. // Generate content ID
  68. // FIXME this require path is like this because of
  69. // https://github.com/Joystream/apps/issues/207
  70. const { ContentId } = require('@joystream/types/lib/media');
  71. var cid = ContentId.generate();
  72. cid = cid.encode().toString();
  73. console.log('Generated content ID: ' + chalk.green(cid));
  74. // Create Data Object
  75. const data_object = await runtime_api.assets.createDataObject(
  76. runtime_api.identities.key.address, cid, do_type_id, size);
  77. console.log('Data object created.');
  78. // TODO in future, optionally contact liaison here?
  79. const request = require('request');
  80. url = `${url}asset/v0/${cid}`;
  81. console.log('Uploading to URL', chalk.green(url));
  82. const f = fs.createReadStream(filename);
  83. const opts = {
  84. url: url,
  85. headers: {
  86. 'content-type': '',
  87. 'content-length': `${size}`,
  88. },
  89. json: true,
  90. };
  91. return new Promise((resolve, reject) => {
  92. const r = request.put(opts, (error, response, body) => {
  93. if (error) {
  94. reject(error);
  95. return;
  96. }
  97. if (response.statusCode / 100 != 2) {
  98. reject(new Error(`${response.statusCode}: ${body.message || 'unknown reason'}`));
  99. return;
  100. }
  101. console.log('Upload successful:', body.message);
  102. resolve();
  103. });
  104. f.pipe(r);
  105. });
  106. },
  107. 'download': async (runtime_api, url, content_id, filename) => {
  108. const request = require('request');
  109. url = `${url}asset/v0/${content_id}`;
  110. console.log('Downloading URL', chalk.green(url), 'to', chalk.green(filename));
  111. const f = fs.createWriteStream(filename);
  112. const opts = {
  113. url: url,
  114. json: true,
  115. };
  116. return new Promise((resolve, reject) => {
  117. const r = request.get(opts, (error, response, body) => {
  118. if (error) {
  119. reject(error);
  120. return;
  121. }
  122. console.log('Downloading', chalk.green(response.headers['content-type']), 'of size', chalk.green(response.headers['content-length']), '...');
  123. f.on('error', (err) => {
  124. reject(err);
  125. });
  126. f.on('finish', () => {
  127. if (response.statusCode / 100 != 2) {
  128. reject(new Error(`${response.statusCode}: ${body.message || 'unknown reason'}`));
  129. return;
  130. }
  131. console.log('Download completed.');
  132. resolve();
  133. });
  134. });
  135. r.pipe(f);
  136. });
  137. },
  138. 'head': async (runtime_api, url, content_id) => {
  139. const request = require('request');
  140. url = `${url}asset/v0/${content_id}`;
  141. console.log('Checking URL', chalk.green(url), '...');
  142. const opts = {
  143. url: url,
  144. json: true,
  145. };
  146. return new Promise((resolve, reject) => {
  147. const r = request.head(opts, (error, response, body) => {
  148. if (error) {
  149. reject(error);
  150. return;
  151. }
  152. if (response.statusCode / 100 != 2) {
  153. reject(new Error(`${response.statusCode}: ${body.message || 'unknown reason'}`));
  154. return;
  155. }
  156. for (var propname in response.headers) {
  157. console.log(` ${chalk.yellow(propname)}: ${response.headers[propname]}`);
  158. }
  159. resolve();
  160. });
  161. });
  162. },
  163. };
  164. async function main()
  165. {
  166. // Key file is at the first instance.
  167. const key_file = cli.input[0];
  168. assert_file('key file', key_file);
  169. // Create runtime API.
  170. const runtime_api = await RuntimeApi.create({ account_file: key_file });
  171. // Simple CLI commands
  172. const command = cli.input[1];
  173. if (!command) {
  174. throw new Error('Need a command to run!');
  175. }
  176. if (commands.hasOwnProperty(command)) {
  177. // Command recognized
  178. const args = _.clone(cli.input).slice(2);
  179. await commands[command](runtime_api, ...args);
  180. }
  181. else {
  182. throw new Error(`Command "${command}" not recognized, aborting!`);
  183. }
  184. }
  185. main()
  186. .then(() => {
  187. console.log('Process exiting gracefully.');
  188. process.exit(0);
  189. })
  190. .catch((err) => {
  191. console.error(chalk.red(err.stack));
  192. process.exit(-1);
  193. });