/**
* Module to abstract storing files on hard drive or in a shared system between multiple countly instances, currently based on GridFS
* @module api/utils/countlyFs
*/
/** @lends module:api/utils/countlyFs */
var countlyFs = {};
var GridFSBucket = require("mongodb").GridFSBucket;
var Readable = require('stream').Readable;
var fs = require("fs");
var path = require("path");
var config = require("../config.js");
var db;
var log = require('./log.js')('core:fs');
/**
* Direct GridFS methods
*/
countlyFs.gridfs = {};
(function(ob) {
/**
* Generic save function for data in gridfs
* @param {string} category - collection where to store data
* @param {string} filename - filename
* @param {stream} readStream - stream where to get file content
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when we have result, providing error object as first param and id as second
**/
function save(category, filename, readStream, options, callback) {
log.d("saving file", filename);
var bucket = new GridFSBucket(db, { bucketName: category });
var uploadStream;
var id = options.id;
delete options.id;
delete options.writeMode;
if (typeof id === "string") {
uploadStream = bucket.openUploadStreamWithId(id, filename, options);
}
else {
uploadStream = bucket.openUploadStream(filename, options);
}
uploadStream.once('finish', function() {
log.d("file saved", filename);
if (callback) {
callback(null);
}
});
uploadStream.on('error', function(error) {
log.d("error saving file", filename, error);
if (callback) {
callback(error);
}
});
readStream.pipe(uploadStream);
}
/**
* Preprocessing hook before saving data
* @param {string} category - collection where to store data
* @param {string} filename - filename
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when we have result, providing error object as first param and id as second
* @param {function} done - function called hook is done
**/
function beforeSave(category, filename, options, callback, done) {
log.d("checking file", filename);
ob.getId(category, filename, async function(err, res) {
log.d("file state", filename, err, res);
if (options.forceClean) {
ob.clearFile(category, filename, done);
}
else if (!err) {
if (!res || options.writeMode === "version") {
done();
}
else if (options.writeMode === "overwrite") {
var bucket = new GridFSBucket(db, { bucketName: category });
log.d("deleting file", filename);
let errHandle = null;
try {
await bucket.delete(res);
}
catch (error) {
errHandle = error;
}
log.d("deleted", filename, errHandle);
if (!errHandle) {
setTimeout(done, 1);
}
else if (callback) {
callback(errHandle);
}
}
else {
if (callback) {
callback(new Error("File already exists"), res);
}
}
}
else {
if (callback) {
callback(err, res);
}
}
});
}
/**
* Get file's id
* @param {string} category - collection where to store data
* @param {string} filename - filename
* @param {function} callback - function called when we have result, providing error object as first param and id as second
* @example
* countlyFs.getId("test", "./CHANGELOG.md", function(err, exists){
* if(exists)
* console.log("File exists");
* });
*/
ob.getId = function(category, filename, callback) {
log.d("getId", category, filename);
db.collection(category + ".files").findOne({ filename: filename }, {_id: 1}, function(err, res) {
if (callback) {
callback(err, (res && res._id) ? res._id : false);
}
});
};
/**
* Check if file exists
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when we have result, providing error object as first param and boolean as second to indicate if file exists
* @example
* countlyFs.exists("test", "./CHANGELOG.md", function(err, exists){
* if(exists)
* console.log("File exists");
* });
*/
ob.exists = function(category, dest, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
log.d("exists", category, dest, options);
var query = {};
if (options.id) {
query._id = options.id;
}
else {
query.filename = dest.split(path.sep).pop();
}
db.collection(category + ".files").findOne(query, {_id: 1}, function(err, res) {
if (callback) {
callback(err, (res && res._id) ? true : false);
}
});
};
/**
* Save file in shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {string} source - source file
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {string} options.writeMode - write mode, by default errors on existing file, possible values "overwrite" deleting previous file, or "version", will not work with provided custom id
* @param {number} options.chunkSizeBytes - Optional overwrite this bucket's chunkSizeBytes for this file
* @param {object} options.metadata - Optional object to store in the file document's metadata field
* @param {string} options.contentType - Optional string to store in the file document's contentType field
* @param {array} options.aliases - Optional array of strings to store in the file document's aliases field
* @param {function} callback - function called when saving was completed or errored, providing error object as first param
* @example
* countlyFs.saveFile("test", "./CHANGELOG.md", function(err){
* console.log("Storing file finished", err);
* });
*/
ob.saveFile = function(category, dest, source, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
log.d("saveFile", category, dest, source, options);
var filename = dest.split(path.sep).pop();
beforeSave(category, filename, options, callback, function() {
save(category, filename, fs.createReadStream(source), options, callback);
});
};
/**
* Save string data in shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {string} data - data to save
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {string} options.writeMode - write mode, by default errors on existing file, possible values "overwrite" deleting previous file, or "version", will not work with provided custom id
* @param {number} options.chunkSizeBytes - Optional overwrite this bucket's chunkSizeBytes for this file
* @param {object} options.metadata - Optional object to store in the file document's metadata field
* @param {string} options.contentType - Optional string to store in the file document's contentType field
* @param {array} options.aliases - Optional array of strings to store in the file document's aliases field
* @param {function} callback - function called when saving was completed or errored, providing error object as first param
* @example
* countlyFs.saveData("test", "test.text", "some\nmultiline\ntest", function(err){
* console.log("Storing data finished", err);
* });
*/
ob.saveData = function(category, dest, data, options, callback) {
var filename = dest.split(path.sep).pop();
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
log.d("saveData", category, dest, typeof data, options);
beforeSave(category, filename, options, callback, function() {
var readStream = new Readable;
readStream.push(data);
readStream.push(null);
save(category, filename, readStream, options, callback);
});
};
/**
* Save file from stream in shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {stream} readStream - stream where to get file content
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {string} options.writeMode - write mode, by default errors on existing file, possible values "overwrite" deleting previous file, or "version", will not work with provided custom id
* @param {number} options.chunkSizeBytes - Optional overwrite this bucket's chunkSizeBytes for this file
* @param {object} options.metadata - Optional object to store in the file document's metadata field
* @param {string} options.contentType - Optional string to store in the file document's contentType field
* @param {array} options.aliases - Optional array of strings to store in the file document's aliases field
* @param {function} callback - function called when saving was completed or errored, providing error object as first param
* @example
* countlyFs.saveStream("test", "AGPLv3", fs.createReadStream("AGPLv3"), function(err){
* console.log("Storing stream finished", err);
* });
*/
ob.saveStream = function(category, dest, readStream, options, callback) {
var filename = dest.split(path.sep).pop();
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
log.d("saveStream", category, dest, typeof readStream, options);
beforeSave(category, filename, options, callback, function() {
save(category, filename, readStream, options, callback);
});
};
/**
* Rename existing file
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {string} source - source file
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when renaming was completed or errored, providing error object as first param
* @example
* countlyFs.rename("test", "AGPLv3", "LICENSE.md", function(err){
* console.log("Finished", err);
* });
*/
ob.rename = async function(category, dest, source, options, callback) {
var newname = dest.split(path.sep).pop();
var oldname = source.split(path.sep).pop();
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
log.d("rename", category, dest, source, options);
if (options.id) {
let bucket = new GridFSBucket(db, { bucketName: category });
let errHandle = null;
try {
await bucket.rename(options.id, newname);
}
catch (error) {
errHandle = error;
}
if (callback) {
callback(errHandle);
}
}
else {
db.collection(category + ".files").findOne({ filename: oldname }, {_id: 1}, async function(err, res) {
if (!err) {
if (res && res._id) {
let bucket = new GridFSBucket(db, { bucketName: category });
let errHandle = null;
try {
await bucket.rename(res._id, newname);
}
catch (error) {
errHandle = error;
}
if (callback) {
callback(errHandle);
}
}
else {
if (callback) {
callback(new Error("File does not exist"));
}
}
}
else {
if (callback) {
callback(err);
}
}
});
}
};
/**
* Update file fields by ID
* @param {string} category - collection where the file is stored
* @param {string} id - file id
* @param {object} updateFields - fields to update
* @param {function} callback - function called when updating was completed or errored, providing error object as first param
* @example
* countlyFs.gridfs.updateFileById("test", "66d6c2d770434130c03b7dae", { filename: "newname.png", "metadata.tags": "newtag" }, function(err){
* console.log("Update finished", err);
* });
*/
ob.updateFileById = function(category, id, updateFields, callback) {
if (!db) {
if (callback) {
callback(new Error("Database connection not available"));
}
return;
}
const collection = db.collection(category + ".files");
const setOps = {};
for (const [key, value] of Object.entries(updateFields)) {
setOps[key] = value;
}
collection.findOneAndUpdate(
{ _id: new db.ObjectID(id) },
{ $set: setOps },
{ returnOriginal: true },
function(err, result) {
if (err) {
log.e("Error updating file:", err);
if (callback) {
callback(err);
}
return;
}
if (!result.value) {
if (callback) {
callback(new Error("File not found"));
}
return;
}
log.d("File updated successfully");
if (callback) {
callback(null, result.value);
}
}
);
};
/**
* Delete file from shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when deleting was completed or errored, providing error object as first param
* @example
* countlyFs.deleteFile("test", "AGPLv3", function(err){
* console.log("Finished", err);
* });
*/
ob.deleteFile = function(category, dest, options, callback) {
var filename = dest.split(path.sep).pop();
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
log.d("deleteFile", category, dest, options);
if (options.id) {
ob.deleteFileById(category, options.id, callback);
}
else {
db.collection(category + ".files").findOne({ filename: filename }, {_id: 1}, function(err, res) {
if (!err) {
if (res && res._id) {
ob.deleteFileById(category, res._id, callback);
}
else {
if (callback) {
callback(new Error("File does not exist"));
}
}
}
else {
if (callback) {
callback(err);
}
}
});
}
};
/**
* Delete all files from collection/category
* @param {string} category - collection of files to delete
* @param {string} dest - directory destination
* @param {function} callback - function called when deleting was completed or errored, providing error object as first param
* @example
* countlyFs.deleteAll("test", function(err){
* console.log("Finished", err);
* });
*/
ob.deleteAll = async function(category, dest, callback) {
log.d("deleteAll", category, dest);
var bucket = new GridFSBucket(db, { bucketName: category });
let errHandle = null;
try {
await bucket.drop();
}
catch (error) {
errHandle = error;
}
if (callback) {
callback(errHandle);
}
};
/**
* Get stream for file
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when establishing stream was completed or errored, providing error object as first param and stream as second
* @example
* countlyFs.getStream("test", "CHANGELOG.md", function(err, stream){
* var writeStream = fs.createWriteStream('./CHANGELOG.md');
* stream.pipe(writeStream);
* });
*/
ob.getStream = function(category, dest, options, callback) {
var filename = dest.split(path.sep).pop();
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
log.d("getStream", category, dest, options);
if (callback) {
if (options.id) {
ob.getStreamById(category, options.id, callback);
}
else {
var bucket = new GridFSBucket(db, { bucketName: category });
callback(null, bucket.openDownloadStreamByName(filename));
}
}
};
/**
* Get file data
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when retrieving stream was completed or errored, providing error object as first param and filedata as second
* @example
* countlyFs.getData("test", "AGPLv3", function(err, data){
* console.log("Retrieved", err, data);
* });
*/
ob.getData = function(category, dest, options, callback) {
var filename = dest.split(path.sep).pop();
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
log.d("getData", category, dest, options);
if (options.id) {
ob.getDataById(category, options.id, callback);
}
else {
var bucket = new GridFSBucket(db, { bucketName: category });
var downloadStream = bucket.openDownloadStreamByName(filename);
downloadStream.on('error', function(error) {
if (callback) {
callback(error, null);
}
});
var str = '';
downloadStream.on('data', function(data) {
str += data.toString('utf8');
});
downloadStream.on('end', function() {
if (callback) {
callback(null, str);
}
});
}
};
/**
* Get file size
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when retrieving file size was completed or errored, providing error object as first param and file size as second
* @example
* countlyFs.getSize("test", "AGPLv3", function(err, size){
* console.log("Retrieved", err, size);
* });
*/
ob.getSize = function(category, dest, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
log.d("getSize", category, dest, options);
var query = {};
if (options.id) {
query._id = options.id;
}
else {
query.filename = dest.split(path.sep).pop();
}
db.collection(category + ".files").findOne(query, {length: 1}, function(err, res) {
if (callback) {
callback(err, (res && res.length) ? res.length : 0);
}
});
};
/**
* Get file stats
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when retrieving file size was completed or errored, providing error object as first param and file size as second
* @example
* countlyFs.getStats("test", "AGPLv3", function(err, stats){
* console.log("Retrieved", err, stats);
* });
*/
ob.getStats = function(category, dest, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
log.d("getStats", category, dest, options);
var query = {};
if (options.id) {
query._id = options.id;
}
else {
query.filename = dest.split(path.sep).pop();
}
db.collection(category + ".files").findOne(query, {}, function(err, res) {
if (callback) {
var stats = {};
stats.size = (res && res.length) ? res.length : 0;
stats.blksize = (res && res.chunkSize) ? res.chunkSize : 0;
stats.atimeMs = (res && res.uploadDate) ? res.uploadDate.getTime() : 0;
stats.mtimeMs = (res && res.uploadDate) ? res.uploadDate.getTime() : 0;
stats.ctimeMs = (res && res.uploadDate) ? res.uploadDate.getTime() : 0;
stats.birthtimeMs = (res && res.uploadDate) ? res.uploadDate.getTime() : 0;
stats.atime = (res && res.uploadDate) ? res.uploadDate : new Date();
stats.mtime = (res && res.uploadDate) ? res.uploadDate : new Date();
stats.ctime = (res && res.uploadDate) ? res.uploadDate : new Date();
stats.birthtime = (res && res.uploadDate) ? res.uploadDate : new Date();
callback(err, stats);
}
});
};
/**
* Get file data by file id
* @param {string} category - collection from where to read data
* @param {string} id - file id provided upon creation
* @param {function} callback - function called when retrieving stream was completed or errored, providing error object as first param and filedata as second
* @example
* countlyFs.getDataById("test", "AGPLv3", function(err, data){
* console.log("Retrieved", err, data);
* });
*/
ob.getDataById = function(category, id, callback) {
log.d("getDataById", category, id);
var bucket = new GridFSBucket(db, { bucketName: category });
var downloadStream = bucket.openDownloadStream(id);
downloadStream.on('error', function(error) {
if (callback) {
callback(error, null);
}
});
var str = '';
downloadStream.on('data', function(data) {
str += data.toString('utf8');
});
downloadStream.on('end', function() {
if (callback) {
callback(null, str);
}
});
};
/**
* Get file stream by file id
* @param {string} category - collection from where to read data
* @param {string} id - file id provided upon creation
* @param {function} callback - function called when retrieving stream was completed or errored, providing error object as first param and filedata as second
* @example
* countlyFs.getStreamById("test", "AGPLv3", function(err, data){
* console.log("Retrieved", err, data);
* });
*/
ob.getStreamById = function(category, id, callback) {
log.d("getStreamById", category, id);
if (callback) {
var bucket = new GridFSBucket(db, { bucketName: category });
callback(null, bucket.openDownloadStream(id));
}
};
/**
* Delete file by id from shared system
* @param {string} category - collection where to store data
* @param {string} id - file id provided upon creation
* @param {function} callback - function called when deleting was completed or errored, providing error object as first param
* @example
* countlyFs.deleteFileById("test", "AGPLv3", function(err){
* console.log("Finished", err);
* });
*/
ob.deleteFileById = async function(category, id, callback) {
log.d("deleteFileById", category, id);
var bucket = new GridFSBucket(db, { bucketName: category });
let errHandle = null;
try {
await bucket.delete(id);
}
catch (error) {
errHandle = error;
}
if (callback) {
callback(errHandle);
}
};
/**
* Force clean file if there were errors inserting or deleting previously
* @param {string} category - collection where to store data
* @param {string} filename - filename
* @param {function} callback - function called when deleting was completed or errored, providing error object as first param
* @example
* countlyFs.clearFile("test", "AGPLv3", function(err){
* console.log("Finished", err);
* });
*/
ob.clearFile = function(category, filename, callback) {
log.d("clearFile", category, filename);
db.collection(category + ".files").deleteMany({ filename: filename }, function(err1, res1) {
log.d("deleting files", category, { filename: filename }, err1, res1 && res1.result);
db.collection(category + ".chunks").deleteMany({ files_id: filename }, function(err2, res2) {
log.d("deleting chunks", category, { files_id: filename }, err1, res2 && res2.result);
if (callback) {
callback(err1 || err2);
}
});
});
};
/**
* List files inside the category (collection/directory)
* @param {string} category - collection to list files in
* @param {function} callback - function called when files found or query errored, providing error object as first param and a list of filename, creation date and size as secondas second
*/
ob.listFiles = function(category, callback) {
log.d("listFiles", category);
const bucket = new GridFSBucket(db, { bucketName: category });
bucket.find().toArray()
.then((records) => callback(
null,
records.map(({ _id, filename, uploadDate, length, metadata }) => ({
_id,
filename,
createdOn: uploadDate,
size: length,
metadata
}))
))
.catch((error) => callback(error, null));
};
/**
* Get handler for filesystem, which in case of GridFS is database connection
* @returns {object} databse connection
* @example
* var db = countlyFs.getHandler();
* db.close();
*/
ob.getHandler = function() {
return db;
};
/**
* Set handler for filesystem, which in case of GridFS is database connection
* @param {object} dbCon - database connection
*/
ob.setHandler = function(dbCon) {
db = dbCon;
};
}(countlyFs.gridfs));
/**
* Direct FS methods
*/
countlyFs.fs = {};
(function(ob) {
/**
* Check if file exists
* @param {string} category - collection where to store data
* @param {string} dest - destination of file
* @param {object=} options - additional options for saving file
* @param {function} callback - function called when we have result, providing error object as first param and boolean as second to indicate if file exists
* @example
* countlyFs.exists("test", "./CHANGELOG.md", function(err, exists){
* if(exists)
* console.log("File exists");
* });
*/
ob.exists = function(category, dest, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
fs.exists(dest, function(exists) {
if (callback) {
callback(null, exists);
}
});
};
/**
* Save file in shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {string} source - source file
* @param {object=} options - additional options for saving file
* @param {function} callback - function called when saving was completed or errored, providing error object as first param
* @example
* countlyFs.saveFile("test", "./CHANGELOG.md", function(err){
* console.log("Storing file finished", err);
* });
*/
ob.saveFile = function(category, dest, source, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
var is = fs.createReadStream(source);
var os = fs.createWriteStream(dest);
is.pipe(os);
is.on('end', function() {});
if (callback) {
os.on('finish', callback);
}
};
/**
* Save string data in shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {string} data - data to save
* @param {object=} options - additional options for saving file
* @param {function} callback - function called when saving was completed or errored, providing error object as first param
* @example
* countlyFs.saveData("test", "test.text", "some\nmultiline\ntest", function(err){
* console.log("Storing data finished", err);
* });
*/
ob.saveData = function(category, dest, data, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
fs.writeFile(dest, data, function(err) {
if (callback) {
callback(err);
}
});
};
/**
* Save file from stream in shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {stream} is - stream where to get file content
* @param {object=} options - additional options for saving file
* @param {function} callback - function called when saving was completed or errored, providing error object as first param
* @example
* countlyFs.saveStream("test", "AGPLv3", fs.createReadStream("AGPLv3"), function(err){
* console.log("Storing stream finished", err);
* });
*/
ob.saveStream = function(category, dest, is, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
var os = fs.createWriteStream(dest);
is.pipe(os);
is.on('end', function() {});
os.on('finish', function() {
if (callback) {
callback();
}
});
};
/**
* Rename existing file
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {string} source - source file
* @param {object=} options - additional options for saving file
* @param {function} callback - function called when renaming was completed or errored, providing error object as first param
* @example
* countlyFs.rename("test", "AGPLv3", "LICENSE.md", function(err){
* console.log("Finished", err);
* });
*/
ob.rename = function(category, dest, source, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
fs.rename(source, dest, function(err) {
if (callback) {
callback(err);
}
});
};
/**
* Delete file from shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {function} callback - function called when deleting was completed or errored, providing error object as first param
* @example
* countlyFs.deleteFile("test", "AGPLv3", function(err){
* console.log("Finished", err);
* });
*/
ob.deleteFile = function(category, dest, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
fs.unlink(dest, function(err) {
if (callback) {
callback(err);
}
});
};
/**
* Get stream for file
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {function} callback - function called when establishing stream was completed or errored, providing error object as first param and stream as second
* @example
* countlyFs.getStream("test", "CHANGELOG.md", function(err, stream){
* var writeStream = fs.createWriteStream('./CHANGELOG.md');
* stream.pipe(writeStream);
* });
*/
ob.getStream = function(category, dest, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
var rstream = fs.createReadStream(dest);
if (callback) {
callback(null, rstream);
}
};
/**
* Get file data
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {function} callback - function called when retrieving stream was completed or errored, providing error object as first param and filedata as second
* @example
* countlyFs.getData("test", "AGPLv3", function(err, data){
* console.log("Retrieved", err, data);
* });
*/
ob.getData = function(category, dest, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
fs.readFile(dest, 'utf8', function(err, data) {
if (callback) {
callback(err, data);
}
});
};
/**
* Get file size
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when retrieving file size was completed or errored, providing error object as first param and file size as second
* @example
* countlyFs.getSize("test", "AGPLv3", function(err, size){
* console.log("Retrieved", err, size);
* });
*/
ob.getSize = function(category, dest, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
fs.stat(dest, function(err, stats) {
if (callback) {
callback(err, stats.size);
}
});
};
/**
* Get file stats
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when retrieving file size was completed or errored, providing error object as first param and file size as second
* @example
* countlyFs.getStats("test", "AGPLv3", function(err, stats){
* console.log("Retrieved", err, stats);
* });
*/
ob.getStats = function(category, dest, options, callback) {
if (typeof options === "function") {
callback = options;
options = null;
}
if (!options) {
options = {};
}
fs.stat(dest, function(err, stats) {
if (callback) {
callback(err, stats);
}
});
};
/**
* List files inside the category (directory)
* @param {string} category - directory to list files in
* @param {function} callback - function called when files found, providing error object as first param and a list of filename, creation date and size as second
*/
ob.listFiles = function(category, callback) {
fs.readdir(category, function(err, files) {
if (err) {
return callback(err);
}
callback(
null,
files.map(filename => {
const stats = fs.statSync(category + '/' + filename);
return {
filename,
createdOn: stats.mtime,
size: stats.size
};
})
);
});
};
/**
* Get handler for filesystem, which in case of GridFS is database connection
* @returns {object} databse connection
* @example
* var db = countlyFs.getHandler();
* db.close();
*/
ob.getHandler = function() {
return db;
};
/**
* Set handler for filesystem, which in case of GridFS is database connection
* @param {object} dbCon - database connection
*/
ob.setHandler = function(dbCon) {
db = dbCon;
};
}(countlyFs.fs));
/**
* Check if file exists
* @param {string} category - collection where to store data for gridfs
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when we have result, providing error object as first param and boolean as second to indicate if file exists
* @example
* countlyFs.exists("test", "./CHANGELOG.md", function(err, exists){
* if(exists)
* console.log("File exists");
* });
*/
countlyFs.exists = function() {
var handler = this[config.fileStorage] || this.fs;
handler.exists.apply(handler, arguments);
};
/**
* Save file in shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {string} source - source file
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {string} options.writeMode - write mode, by default errors on existing file, possible values "overwrite" deleting previous file, or "version", will not work with provided custom id
* @param {number} options.chunkSizeBytes - Optional overwrite this bucket's chunkSizeBytes for this file
* @param {object} options.metadata - Optional object to store in the file document's metadata field
* @param {string} options.contentType - Optional string to store in the file document's contentType field
* @param {array} options.aliases - Optional array of strings to store in the file document's aliases field
* @param {function} callback - function called when saving was completed or errored, providing error object as first param
* @example
* countlyFs.saveFile("test", "./CHANGELOG", "./CHANGELOG.md", function(err){
* console.log("Storing file finished", err);
* });
*/
countlyFs.saveFile = function() {
var handler = this[config.fileStorage] || this.fs;
handler.saveFile.apply(handler, arguments);
};
/**
* Save string data in shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {string} data - data to save
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {string} options.writeMode - write mode, by default errors on existing file, possible values "overwrite" deleting previous file, or "version", will not work with provided custom id
* @param {number} options.chunkSizeBytes - Optional overwrite this bucket's chunkSizeBytes for this file
* @param {object} options.metadata - Optional object to store in the file document's metadata field
* @param {string} options.contentType - Optional string to store in the file document's contentType field
* @param {array} options.aliases - Optional array of strings to store in the file document's aliases field
* @param {function} callback - function called when saving was completed or errored, providing error object as first param
* @example
* countlyFs.saveData("test", "test.text", "some\nmultiline\ntest", function(err){
* console.log("Storing data finished", err);
* });
*/
countlyFs.saveData = function() {
var handler = this[config.fileStorage] || this.fs;
handler.saveData.apply(handler, arguments);
};
/**
* Save file from stream in shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {stream} readStream - stream where to get file content
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {string} options.writeMode - write mode, by default errors on existing file, possible values "overwrite" deleting previous file, or "version", will not work with provided custom id
* @param {number} options.chunkSizeBytes - Optional overwrite this bucket's chunkSizeBytes for this file
* @param {object} options.metadata - Optional object to store in the file document's metadata field
* @param {string} options.contentType - Optional string to store in the file document's contentType field
* @param {array} options.aliases - Optional array of strings to store in the file document's aliases field
* @param {function} callback - function called when saving was completed or errored, providing error object as first param
* @example
* countlyFs.saveStream("test", "AGPLv3", fs.createReadStream("AGPLv3"), function(err){
* console.log("Storing stream finished", err);
* });
*/
countlyFs.saveStream = function() {
var handler = this[config.fileStorage] || this.fs;
handler.saveStream.apply(handler, arguments);
};
/**
* Move or Rename existing file
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {string} source - source file
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when renaming was completed or errored, providing error object as first param
* @example
* countlyFs.rename("test", "AGPLv3", "LICENSE.md", function(err){
* console.log("Finished", err);
* });
*/
countlyFs.rename = function() {
var handler = this[config.fileStorage] || this.fs;
handler.rename.apply(handler, arguments);
};
/**
* Delete file from shared system
* @param {string} category - collection where to store data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when deleting was completed or errored, providing error object as first param
* @example
* countlyFs.deleteFile("test", "AGPLv3", function(err){
* console.log("Finished", err);
* });
*/
countlyFs.deleteFile = function() {
var handler = this[config.fileStorage] || this.fs;
handler.deleteFile.apply(handler, arguments);
};
/**
* Get stream for file
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when establishing stream was completed or errored, providing error object as first param and stream as second
* @example
* countlyFs.getStream("test", "CHANGELOG.md", function(err, stream){
* var writeStream = fs.createWriteStream('./CHANGELOG.md');
* stream.pipe(writeStream);
* });
*/
countlyFs.getStream = function() {
var handler = this[config.fileStorage] || this.fs;
handler.getStream.apply(handler, arguments);
};
/**
* Get file data
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when retrieving file was completed or errored, providing error object as first param and filedata as second
* @example
* countlyFs.getData("test", "AGPLv3", function(err, data){
* console.log("Retrieved", err, data);
* });
*/
countlyFs.getData = function() {
var handler = this[config.fileStorage] || this.fs;
handler.getData.apply(handler, arguments);
};
/**
* Get file size
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when retrieving file size was completed or errored, providing error object as first param and file size as second
* @example
* countlyFs.getSize("test", "AGPLv3", function(err, size){
* console.log("Retrieved", err, size);
* });
*/
countlyFs.getSize = function() {
var handler = this[config.fileStorage] || this.fs;
handler.getSize.apply(handler, arguments);
};
/**
* Get file stats
* @param {string} category - collection from where to read data
* @param {string} dest - file's destination
* @param {object=} options - additional options for saving file
* @param {string} options.id - custom id for the file
* @param {function} callback - function called when retrieving file size was completed or errored, providing error object as first param and file size as second
* @example
* countlyFs.getStats("test", "AGPLv3", function(err, stats){
* //similar to fs.stat object
* console.log("Retrieved", err, stats);
* });
*/
countlyFs.getStats = function() {
var handler = this[config.fileStorage] || this.fs;
handler.getStats.apply(handler, arguments);
};
/**
* Get handler for connection to close it, for stopping separate scripts
* @returns {object} databse connection
* @example
* var db = countlyFs.getHandler();
* db.close();
*/
countlyFs.getHandler = function() {
var handler = this[config.fileStorage] || this.fs;
return handler.getHandler();
};
/**
* Set handler for connection
* @param {object} dbCon - database connection
*/
countlyFs.setHandler = function(dbCon) {
var handler = this[config.fileStorage] || this.fs;
handler.setHandler(dbCon);
};
/**
* Currently used file storage type
**/
countlyFs.type = config.fileStorage || "fs";
module.exports = countlyFs;