"use strict";
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const objects = require("./objects");
const ChinoAPIBase = require("./chinoBase");
/** Commit the upload action and return the blob information
*
* @param uploadId {string} The upload id representing an upload action
* @return {Promise.<objects.Blob, objects.ChinoException>}
* A promise that return a Blob object if resolved,
* otherwise throw an ChinoException object if rejected
* or was not retrieved a success status
*/
function commit(uploadId) {
const params = {
upload_id : uploadId
};
return this.call.post(`/blobs/commit`, params)
.then((result) => objects.checkResult(result, "Blob"))
.catch((error) => { throw new objects.ChinoException(error); });
}
/** Create a new blob
*
* @param info {object} The parameter for the creation of a blob
* @return {Promise}
*/
function create(info = {}) {
return this.call.post(`/blobs`, info);
}
class ChinoAPIBlobs extends ChinoAPIBase {
/** Create a caller for Groups Chino APIs
*
* @param baseUrl {string} The url endpoint for APIs
* @param customerId {string} The Chino customer id or bearer token
* @param customerKey {string | null} The Chino customer key or null (not provided)
*/
constructor(baseUrl, customerId, customerKey = null) {
super(baseUrl, customerId, customerKey);
}
/** Upload a blob file
*
* @param docId {string}
* @param field {string}
* @param fileName {string}
* @return {Promise.<objects.Blob, objects.ChinoException>}
* A promise that return a BlobUncommitted object if resolved,
* otherwise throw an ChinoException object if rejected
* or was not retrieved a success status
*/
upload(docId = "", field = "", fileName = "") {
const info = {
document_id : docId,
field : field,
file_name : fileName
}
let uploadId = "";
function doUpload(resolve, reject) {
if (!fileName) {
const error = {
message : "Missing file name: impossible to upload undefined file",
result_code : 400,
result : "error",
data : null
};
reject(new objects.ChinoException(error));
}
else {
create.call(this, info)
.then((result) => {
if (result.result_code === 200) {
// get an id where upload blob data
uploadId = result.data.blob.upload_id;
// prepare to read file
const options = {
flags : "r",
autoClose : true,
highWaterMark : 16 * 1024
};
const readStream = fs.createReadStream(fileName, options);
// hash for verifying blob integrity
const hash = crypto.createHash('sha1');
let chunks = [];
let offset = 0;
// read each chunk and upload it
readStream.on('data', (chunk) => {
let params = {
blob_offset : offset,
blob_length : chunk.length
}
// create an array of Promises upload
chunks.push(this.call.chunk(`/blobs/${uploadId}`, chunk, params))
hash.update(chunk);
offset += chunk.length;
});
readStream.on('error', (error) => {
throw new Error(`Error reading file:\n${error}`);
});
readStream.on('end', () =>
// wait upload of all chunks is completed
Promise.all(chunks)
.then((result) => {
if (result.every((res) => res.result_code === 200)) {
return commit.call(this, uploadId)
}
else {
throw new objects.ChinoException(result);
}
})
.then((blob) => {
// attention: digest method can be called one for hash
if (blob.sha1 === hash.digest("hex")) {
resolve(blob);
}
else {
reject("Digest mismatch.");
}
})
.catch((error) => { throw new objects.ChinoException(error); })
)
}
else {
throw new objects.ChinoException(result);
}
})
.catch((error) => { throw new objects.ChinoException(error); });
}
}
return new Promise(doUpload.bind(this))
}
/** Retrieve selected blob data and save it in the specified file
*
* @param blobId {string}
* @param newFileName {string}
* @return {Promise.<objects.Success, Error>}
* A promise that return Blob object as Octet stream if resolved,
* otherwise throw an ChinoException object if rejected
*/
download(blobId, newFileName = "") {
function doDownload(resolve, reject) {
if (!newFileName || newFileName === "") {
const error = {
message : "Missing file name for creating file from downloaded blob data.",
result_code : 400,
result : "error",
data : null
};
reject(new objects.ChinoException(error));
}
else {
const options = {
flags : "w",
autoClose : true,
highWaterMark : 16 * 1024
};
const writer = fs.createWriteStream(newFileName, options);
writer.on("finish", function () {
const ok = {
result_code: 200,
result: "success",
data : null,
message : null
};
resolve(new objects.Success(ok));
});
writer.on("error", (error) => {
reject(new Error("Writing blob raise an error:\n" + error));
});
this.call.getBlob(`/blobs/${blobId}`).pipe(writer);
}
}
return new Promise(doDownload.bind(this));
}
/** Delete blob selected by its id
*
* @param blobId {string}
* @return {Promise.<objects.Success, objects.ChinoException>}
* A promise that return a Success object if resolved,
* otherwise throw an ChinoException object if rejected
* or was not retrieved a success status
*/
delete(blobId) {
const params = {};
return this.call.del(`/blobs/${blobId}`, params)
.then((result) => objects.checkResult(result, "Success"))
.catch((error) => { throw new objects.ChinoException(error); });
}
}
module.exports = ChinoAPIBlobs;