Przeglądaj źródła

storage-node: cli: Update ‘upload’ command.

Shamil Gadelshin 4 lat temu
rodzic
commit
7de98d4b97

+ 4 - 39
storage-node/packages/cli/src/cli.ts

@@ -19,16 +19,11 @@
 
 'use strict'
 
-import axios from "axios";
 import fs from "fs"
-import path from "path"
 import assert from "assert"
 import { RuntimeApi } from "@joystream/storage-runtime-api"
 import meow from "meow"
-import chalk from "chalk"
 import _ from "lodash"
-import ipfsHash from 'ipfs-only-hash';
-import FormData from "form-data"
 import Debug from "debug";
 const debug = Debug('joystream:storage-cli');
 
@@ -156,44 +151,14 @@ const commands = {
     //   f.pipe(r)
     // })
   },
-  // upload2: async (api, url, contentId, filePath___) => {
-  //   const filePath = './local.mp4';
-  //   // requesetType: 'stream' - same as download??
-  //   // Create file read stream and set error handler.
-  //   const file = fs.createReadStream(filePath)
-  //       .on('error', (err) => {
-  //         const message = `File read failed: ${err}`;
-  //         console.log(chalk.red(message));
-  //         process.exit(1);
-  //       });
-  //
-  //   try {
-  //     const formData = new FormData();
-  //     formData.append("file", file);
-  //
-  //     const config = {
-  //       headers: {
-  //         // TODO uncomment this once the issue fixed:
-  //         // https://github.com/Joystream/storage-node-joystream/issues/16
-  //         // 'Content-Type': file.type
-  //         'Content-Type': '' // <-- this is a temporary hack
-  //       },
-  //       url
-  //     };
-  //
-  //     await axios(config);
-  //   } catch (err) {
-  //     console.log(chalk.red(err));
-  //     process.exit(1);
-  //   }
-  // },
-  upload: async (api) => {
-    await uploadCommand.run(api)
+
+  upload: async (api: any, filePath: string) => {
+    await uploadCommand.run(api, filePath)
   },
   // needs to be updated to take a content id and resolve it a potential set
   // of providers that has it, and select one (possibly try more than one provider)
   // to fetch it from the get api url of a provider..
-  download: async (api, url, contentId, filePath) => {
+  download: async (api: any, url: string, contentId: string, filePath: string) => {
     await downloadCommand.run(api, url, contentId, filePath);
   },
   // Shows asset information derived from request headers.

+ 114 - 11
storage-node/packages/cli/src/commands/upload.ts

@@ -1,30 +1,133 @@
-import axios from "axios";
+import axios, {AxiosRequestConfig} from "axios";
 import chalk from "chalk"
 import fs from "fs";
 import ipfsHash from "ipfs-only-hash";
 import { ContentId, DataObject } from '@joystream/types/media';
+import BN from "bn.js";
+import { Option } from '@polkadot/types/codec';
+import Debug from "debug";
+const debug = Debug('joystream:storage-cli:upload');
 
-// requesetType: 'stream' - same as download??
 
+// Defines maximum content length for the assets (files). Limits the upload.
+const MAX_CONTENT_LENGTH = 500 * 1024 * 1024; // 500Mb
 
+function fail(message: string) {
+    console.log(chalk.red(message));
+    process.exit(1);
+}
 
 async function computeIpfsHash(filePath: string): Promise<string> {
     const file = fs.createReadStream(filePath)
         .on('error', (err) => {
-            const message = `File read failed: ${err}`;
-            console.log(chalk.red(message));
-            process.exit(1);
+            fail(`File read failed: ${err}`);
         });
 
     return await ipfsHash.of(file);
 }
 
-export async function run(api: any){
-    const filePath = './local.mp4';
+function getFileSize(filePath: string): number {
+    const stats = fs.statSync(filePath);
+    return stats.size;
+}
+
+//
+interface AddContentParams {
+    accountId: string,
+    ipfsCid: string,
+    contentId: string,
+    fileSize: BN,
+    dataObjectTypeId: number,
+    memberId: number
+}
+
+// Composes an asset URL and logs it to console.
+function createAndLogAssetUrl(url: string, contentId: string) : string {
+    const assetUrl = `${url}/asset/v0/${contentId}`;
+    console.log(chalk.yellow('Generated asset URL:', assetUrl));
+
+    return assetUrl;
+}
+
+function getAccountId(api: any) : string {
+    const ALICE_URI = '//Alice'
+
+    return api.identities.keyring.addFromUri(ALICE_URI, null, 'sr25519').address
+}
+
+// Creates parameters for the AddContent runtime tx.
+async function getAddContentParams(api: any, filePath: string): Promise<AddContentParams> {
+    const dataObjectTypeId = 1;
+    const memberId = 0; // alice
+    const accountId = getAccountId(api)
+
+    return {
+        accountId,
+        ipfsCid: await computeIpfsHash(filePath),
+        contentId: ContentId.generate().encode(),
+        fileSize: new BN(getFileSize(filePath)),
+        dataObjectTypeId,
+        memberId
+    };
+}
+
+// Creates the DataObject in the runtime.
+async function createContent(api: any, p: AddContentParams) : Promise<DataObject> {
+    try {
+        const dataObject: Option<DataObject> = await api.assets.createDataObject(
+            p.accountId,
+            p.memberId,
+            p.contentId,
+            p.dataObjectTypeId,
+            p.fileSize,
+            p.ipfsCid
+        );
+
+        if (dataObject.isNone) {
+            fail("Cannot create data object: got None object");
+        }
+
+        return dataObject.unwrap();
+    } catch (err) {
+        fail(`Cannot create data object: ${err}`);
+    }
+}
+
+// Command executor.
+export async function run(api: any, filePath: string){
+    let addContentParams = await getAddContentParams(api, filePath);
+    debug(`AddContent Tx params: ${addContentParams}`); // debug
+    debug(`Decoded CID: ${ContentId.decode(addContentParams.contentId).toString()}`);
+
+    let dataObject = await createContent(api, addContentParams);
+    debug(`Received data object: ${dataObject.toString()}`); // debug
+
+    let assetUrl = createAndLogAssetUrl("http://localhost:3001", addContentParams.contentId);
+    await uploadFile(assetUrl, filePath);
+}
+
+// Uploads file to given asset URL.
+async function uploadFile(assetUrl: string, filePath: string) {
+    // Create file read stream and set error handler.
+    const file = fs.createReadStream(filePath)
+        .on('error', (err) => {
+            fail(`File read failed: ${err}`);
+        });
 
-    const ipfsHashValue = await computeIpfsHash(filePath);
-    const contentId = ContentId.generate();
+    // Upload file from the stream.
+    try {
+        const fileSize = getFileSize(filePath);
+        const config: AxiosRequestConfig = {
+            headers: {
+                'Content-Type': '', // https://github.com/Joystream/storage-node-joystream/issues/16
+                'Content-Length': fileSize.toString()
+            },
+            'maxContentLength': MAX_CONTENT_LENGTH,
+        };
+        await axios.put(assetUrl, file, config);
 
-    console.log(ipfsHashValue);
-    console.log(contentId.toString());
+        console.log("File uploaded.");
+    } catch (err) {
+        fail(err.toString());
+    }
 }