Introduce a concept of 'storage backend'; add fs backend

parent 03e99b2a
const Promise = require('bluebird');
const fs = require('fs-extra');
const crypto = require('crypto');
const path = require('path');
const logging = require('../logging').getWrapperForModule('filesystem');
const HASH_ALGORITHM = 'sha256';
class FilesystemStorage {
/**
* @param {string} basePath - A root directory for all stored files
*/
constructor (basePath) {
this.basePath = basePath;
}
/**
* Uploads attachment to file storage.
* @param {File} file Multipart/form-data file.
* @return {Promise<string>} Hash of uploaded attachment.
*/
add (file) {
logging.verbose(`Uploading '${file.originalFilename}' to filesystem`);
const hash = this.computeHash(file);
const filePath = this.getFilePath(hash);
return fs.outputFile(filePath, file).then(() => hash);
}
/**
* Deletes a file.
*
* @param {string} hash - Hash of file to delete
* @return {Promise<void>}
*/
delete (hash) {
logging.verbose(`Deleting file ${hash} from filesystem`);
const filePath = this.getFilePath(hash);
return fs.remove(filePath);
}
/**
* Gets file from IPFS.
* @param {String} hash IPFS hash.
* @return {Promise<ReadableStream>} Readable Stream of attachment.
*/
get (hash) {
logging.verbose(`Serving attachment for ${hash} from filesystem`);
const filePath = this.getFilePath(hash);
return fs.readFile(filePath);
}
computeHash (file) {
const hash = crypto.createHash(HASH_ALGORITHM);
hash.update(file);
return hash.digest('hex');
}
getFilePath (hash) {
const dirName = hash.slice(0, 2);
const fileName = hash.slice(2);
return path.join(this.basePath, dirName, fileName);
}
}
\ No newline at end of file
'use strict';
const fs = require('fs'); const fs = require('fs');
const ipfsAPI = require('ipfs-api'); const ipfsAPI = require('ipfs-api');
const path = require('path'); const path = require('path');
const FileNotFoundError = require('./errors').FileNotFoundError; const FileNotFoundError = require('../errors').FileNotFoundError;
const logging = require('./logging').getWrapperForModule('ipfs'); const logging = require('../logging').getWrapperForModule('ipfs');
const settings = require('./settings'); const settings = require('../settings');
/** /**
* Represents an interface to interact with IPFS daemon to download and upload * Represents an interface to interact with IPFS daemon to download and upload
...@@ -16,67 +14,69 @@ class Ipfs { ...@@ -16,67 +14,69 @@ class Ipfs {
/** /**
* constructor for Ipfs class. * constructor for Ipfs class.
*/ */
constructor() { constructor (url) {
this.ipfs = ipfsAPI(settings.ipfs.url); this.ipfs = ipfsAPI(url);
} }
/** /**
* Stores an IPFS object from a given path locally to disk. * Uploads attachment to IPFS.
* @param {String} hash IPFS hash. * @param {File} file Multipart/form-data file.
* @return {Promise} Pinned object. * @return {Promise<string>} IPFS hash of uploaded attachment.
*/ */
pin(hash) { add (file) {
logging.verbose(`Pinning object ${hash} to local storage`); logging.verbose(`Uploading '${file.originalFilename}' to IPFS`);
return this.ipfs.pin.add(hash);
let files = [{
path: path.basename(file.path),
content: fs.createReadStream(file.path),
}];
let hash;
return this.ipfs.files.add(files).then((res) => {
hash = res;
return this.pin(hash);
}).then(() => {
return Promise.resolve(hash);
});
}
/**
* Unpins a file from IPFS - it will be deleted during next `ipfs repo gc`
*
* @param {string} hash - Hash of file to delete
* @return {Promise<void>}
*/
delete (hash) {
logging.verbose(`Unpinning object ${hash} from local storage`);
return this.ipfs.pin.rm(hash);
} }
/** /**
* Serves attachment to the client. * Gets file from IPFS.
* @param {String} hash IPFS hash. * @param {String} hash IPFS hash.
* @return {Promise} Readable Stream of attachment. * @return {Promise<ReadableStream>} Readable Stream of attachment.
*/ */
serve(hash) { get (hash) {
logging.verbose(`Serving attachment for ${hash}`); logging.verbose(`Serving attachment for ${hash}`);
return this.ipfs.files.cat(hash).then((stream) => { return this.ipfs.files.cat(hash).then((stream) => {
if (!(stream && stream.pipe)) if (!(stream && stream.pipe)) {
throw new FileNotFoundError('No file or stream provided'); throw new FileNotFoundError('No file or stream provided');
}
return stream; return stream;
}); });
} }
/**
* Removes the pin from the given object allowing it to be garbage
* collected if needed.
* @param {String} hash IPFS hash.
* @return {Promise} Unpinned object.
*/
unpin(hash) {
logging.verbose(`Unpinning object ${hash} from local storage`);
return this.ipfs.pin.rm(hash);
}
/** /**
* Uploads attachment to IPFS. * Pins a file by hash.
* @param {File} file Multipart/form-data file. * @param {String} hash IPFS hash.
* @return {Promise} IPFS hash of uploaded attachment. * @return {Promise<void>}
*/ */
upload(file) { pin (hash) {
logging.verbose(`Uploading '${file.originalFilename}' to IPFS`); logging.verbose(`Pinning object ${hash} to local storage`);
return this.ipfs.pin.add(hash);
let files = [{
path: path.basename(file.path),
content: fs.createReadStream(file.path),
}];
let hash;
return this.ipfs.files.add(files).then((res) => {
hash = res;
return this.pin(hash);
}).then(() => {
return Promise.resolve(hash);
});
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment