// Copyright 2018 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under
// the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT> or
// the Modified BSD license <LICENSE-BSD or https://opensource.org/licenses/BSD-3-Clause>,
// at your option.
//
// This file may not be copied, modified, or distributed except according to those terms.
//
// Please review the Licences for the specific language governing permissions and limitations
// relating to use of the SAFE Network Software.
const helpers = require('../helpers');
const lib = require('../native/lib');
const multihash = require('multihashes');
const CID = require('cids');
const consts = require('../consts');
const { EXPOSE_AS_EXPERIMENTAL_API } = require('../helpers');
/**
* {@link ImmutableDataInterface} reader
* @hideconstructor
*/
class Reader extends helpers.NetworkObject {
/**
* Read the given amount of bytes from the network
* @param {Object=} options
* @param {Number} [options.offset=0] start position
* @param {Number} [options.end=size] end position or end of data
* @returns {Promise<Buffer>}
* @example
* // Assumes {@link initialiseApp|SAFEApp} interface has been obtained
* const asyncFn = async () => {
* const readOptions =
* {
* offset: 0, // starts reading from this byte position
* end: null // ends reading at this byte position
* };
* try {
* const iDataReader = await app.immutableData.fetch(iDataAddress)
* const data = await iDataReader.read(readOptions)
* } catch(err) {
* throw err;
* }
* };
*/
read(options) {
const opts = Object.assign({}, options);
let prms;
if (opts.end) {
prms = Promise.resolve(opts.end);
} else {
prms = this.size();
}
return prms.then((end) =>
lib.idata_read_from_self_encryptor(this.app.connection,
this.ref,
opts.offset || 0,
end));
}
/**
* The size of the immutable data on the network
* @returns {Promise<Number>} length in bytes
* @example
* // Assumes {@link initialiseApp|SAFEApp} interface has been obtained
* const asyncFn = async () => {
* try {
const size = await iDataReader.size()
* } catch(err) {
* throw err;
* }
* };
*/
size() {
return lib.idata_size(this.app.connection, this.ref);
}
/**
* @private
* free the reference of reader of the app on the native side
* used by the autoref feature
* @param {SAFEApp} app - the app the reference belongs to
* @param {handle} ref - the reference to free
*/
static free(app, ref) {
lib.idata_self_encryptor_reader_free(app.connection, ref);
}
}
/**
* {@link ImmutableDataInterface} writer
* @hideconstructor
*/
class Writer extends helpers.NetworkObject {
/**
* Append the given data to {@link ImmutableDataInterface}. This does not commit data to network.
*
* @param {String|Buffer} data The string or buffer to write
* @returns {Promise}
* @example
* // Assumes {@link initialiseApp|SAFEApp} interface has been obtained
* const asyncFn = async () => {
* try {
* const iDataWriter = await app.immutableData.create()
* const data = 'Most proteins are glycosylated.
* Mass spectrometry methods are used for mapping glycoprotein.';
* await iDataWriter.write(data);
* } catch(err) {
* throw err;
* }
* };
*/
write(data) {
return lib.idata_write_to_self_encryptor(this.app.connection, this.ref, data);
}
/**
* Close and commit the {@link ImmutableDataInterface} to the network.
*
* @param {CipherOpt} cipherOpt The cipher method with which to encrypt data
* @param {Boolean} getXorUrl (experimental) if the XOR-URL shall also
* be returned along with the xor address
* @param {String} mimeType (experimental) the MIME type to encode in
* the XOR-URL as the codec of the content
* @returns {Promise<Buffer|{ name: Buffer, xorUrl: String }>}
* The XOR address to the data once written to the network,
* or an object that contains both the XOR address and XOR URL.
* @example
* // Assumes {@link initialiseApp|SAFEApp} interface has been obtained
* const asyncFn = async () => {
* try {
* const cipherOpt = await app.cipherOpt.newPlainText();
* const iDataWriter = await app.immutableData.create()
* const data = 'Most proteins are glycosylated.
* Mass spectrometry methods are used for mapping glycoprotein.';
* await iDataWriter.write(data);
* const iDataAddress = await iDataWriter.close(cipherOpt);
*
* // Alternatively:
* // const getXorUrl = true;
* // const mimeType = 'text/plain';
* // const iDataMeta = await iDataWriter.close(cipherOpt, getXorUrl, mimeType);
* } catch(err) {
* throw err;
* }
* };
*/
async close(cipherOpt, getXorUrl, mimeType) {
const name = await lib.idata_close_self_encryptor(this.app.connection,
this.ref, cipherOpt.ref);
if (!getXorUrl) {
return name;
}
// Let's either generate the XOR-URL, or generate an error if the
// experimental apis are not enabled
/* eslint-disable camelcase, prefer-arrow-callback */
const xorUrl = EXPOSE_AS_EXPERIMENTAL_API.call(this.app, function XOR_URLs() {
const address = Buffer.from(name);
const encodedHash = multihash.encode(address, consts.CID_HASH_FN);
const codec = mimeType ? `${consts.CID_MIME_CODEC_PREFIX}${mimeType}` : consts.CID_DEFAULT_CODEC;
const newCid = new CID(consts.CID_VERSION, codec, encodedHash);
const cidStr = newCid.toBaseEncodedString(consts.CID_BASE_ENCODING);
return `safe://${cidStr}`;
});
return { name, xorUrl };
}
/**
* @private
* free the reference of writer of the app on the native side.
* used by the autoref feature
* @param {SAFEApp} app the app the reference belongs to
* @param {handle} ref the reference to free
*/
static free(app, ref) {
lib.idata_self_encryptor_writer_free(app.connection, ref);
}
}
class ImmutableDataInterface {
/**
* @hideconstructor
* @param {SAFEApp} app
*/
constructor(app) {
this.app = app;
}
/**
* Create a new {@link ImmutableDataInterface} writer
* @returns {Promise<Writer>}
* @example
* // Assumes {@link initialiseApp|SAFEApp} interface has been obtained
* const asyncFn = async () => {
* try {
* const iDataWriter = await app.immutableData.create()
* } catch(err) {
* throw err;
* }
* };
*/
create() {
return lib.idata_new_self_encryptor(this.app.connection)
.then((ref) => helpers.autoref(new Writer(this.app, ref)));
}
/**
* Look up an existing {@link ImmutableDataInterface} for the given address
* @param {Buffer} Network XOR address
* @returns {Promise<Reader>}
* @example
* // Assumes {@link initialiseApp|SAFEApp} interface has been obtained
* const asyncFn = async () => {
* try {
* const iDataReader = await app.immutableData.fetch(iDataAddress);
* } catch(err) {
* throw err;
* }
* };
*/
fetch(address) {
return lib.idata_fetch_self_encryptor(this.app.connection, address)
.then((ref) => helpers.autoref(new Reader(this.app, ref)));
}
}
module.exports = ImmutableDataInterface;