/**
* MOST Web Framework
* A JavaScript Web Framework
* http://themost.io
*
* Copyright (c) 2014, Kyriakos Barbounakis k.barbounakis@gmail.com, Anthi Oikonomou anthioikonomou@gmail.com
*
* Released under the BSD3-Clause license
* Date: 2015-01-05
*/
/**
* @private
*/
var util = require('util'),
path=require('path'),
fs = require('fs'),
common = require('./common');
/**
* @classdesc An abstract class that describes a file storage.
* @class
* @constructor
* @property {string} root - Gets or sets a string that represents the physical root path of this file storage
* @property {string} virtualPath - Gets or sets a string that represents the virtual path of this file storage
* @memberOf module:most-web.files
*/
function FileStorage() {
//
}
/**
* @param {HttpContext} context
* @param {string} src
* @param {*} attrs
* @param {Function} callback
*/
FileStorage.prototype.copyFrom = function(context, src, attrs, callback) {
callback = callback || function() {};
callback();
};
/**
* @param {HttpContext} context
* @param {*} item
* @param {string} dest
* @param {Function} callback
*/
FileStorage.prototype.copyTo = function(context, item, dest, callback) {
callback = callback || function() {};
callback();
};
/**
* @param {HttpContext} context
* @param {*} item
* @param {Function} callback
*/
FileStorage.prototype.resolvePhysicalPath = function(context, item, callback) {
callback = callback || function() {};
callback();
};
/**
* @param {HttpContext} context
* @param {*} item
* @param {Function} callback
*/
FileStorage.prototype.resolveUrl = function(context, item, callback) {
callback = callback || function() {};
callback();
};
/**
* @param {HttpContext} context
* @param {*} item
* @param {Function} callback
*/
FileStorage.prototype.createReadStream = function(context, item, callback) {
callback = callback || function() {};
callback();
};
/**
* @param {Function} callback
*/
FileStorage.prototype.init = function(callback) {
callback = callback || function() {};
callback();
};
/**
* @param {HttpContext} context
* @param {*} query
* @param {Function} callback
*/
FileStorage.prototype.find = function(context, query, callback) {
callback = callback || function() {};
callback();
};
/**
* @param {HttpContext} context
* @param {*} query
* @param {Function} callback
*/
FileStorage.prototype.findOne = function(context, query, callback) {
callback = callback || function() {};
callback();
};
/**
* @param {HttpContext} context
* @param {*} item
* @param {function(Error=,*=)} callback
*/
FileStorage.prototype.remove = function(context, item, callback) {
callback = callback || function() {};
callback();
};
/**
* @param {HttpContext} context
* @param {*} item
* @param {Function} callback
*/
FileStorage.prototype.exists = function(context, item, callback) {
callback = callback || function() {};
callback(false);
};
/**
* @classdesc FileSystemStorage class describes a file storage on local file system.
* @class FileSystemStorage
* @constructor
* @augments FileStorage
* @param {string} physicalPath The root directory of this storage
* @memberOf module:most-web.files
* @deprecated
* @ignore
*/
function FileSystemStorage(physicalPath) {
this.root = physicalPath;
this.virtualPath = null;
this.ensure = function(callback) {
var self = this;
callback = callback || function() {};
if (self._initialized) {
callback();
return;
}
if (typeof self.root === 'undefined' || self.root==null) {
callback(new Error('The file system storage root directory cannot be empty at this context.'));
}
else {
//check directory existence
fs.exists(self.root, function(exists) {
if (exists) {
self._initialized = true;
callback();
}
else {
fs.mkdir(self.root,function(err) {
if (err) {
console.log(err);
callback(new Error('An error occured while trying to initialize file system storage.'));
}
else {
var Db = require('tingodb')().Db,
db = new Db(self.root, {nativeObjectID:true});
//Fetch a collection to insert document into
var collection = db.collection("fs");
db.close(function() {
//initialization was completed, so exit with no error
self._initialized = true;
callback();
});
}
});
}
});
}
};
this.execute = function(fn, callback) {
fn = fn || function() {};
try {
var Db = require('tingodb')().Db,
db = new Db(physicalPath, { nativeObjectID:true });
fn(db, function(err, result) {
db.close(function() {
callback(err, result);
});
});
}
catch (e) {
callback(e);
}
}
}
util.inherits(FileSystemStorage, FileStorage);
/**
* @param {HttpContext} context
* @param {*} item
* @param {function} callback
*/
FileSystemStorage.prototype.save = function(context, item, callback) {
var self = this;
if (common.isNullOrUndefined(item)) {
callback();
return;
}
self.execute(function(db, cb) {
//file default version
item.version = item.version || 1;
//file status (0) temporary
item.status = item.status || 0;
//file date created (on storage)
item.dateCreated = new Date();
//file date modified (on storage)
item.dateModified = new Date();
//get collection
var collection = db.collection('fs');
//save
var state = 2;
if (common.isNullOrUndefined(item._id)) {
state=1;
item.oid = common.randomChars(12);
}
collection.save(item, function(err) {
cb(err);
});
}, function(err) {
callback(err);
});
};
/**
* @param {HttpContext} context
* @param {*} query
* @param {function(Error=,*=)} callback
*/
FileSystemStorage.prototype.findOne = function(context, query, callback) {
var self = this;
if (common.isNullOrUndefined(query)) {
callback();
return;
}
self.ensure(function() {
self.execute(function(db, cb) {
//get collection
var collection = db.collection('fs');
if (query._id) {
collection.findOne({_id:query._id}, function(err, result) {
cb(err, result);
});
}
else {
collection.findOne(query, function(err, result) {
cb(err, result);
});
}
}, function(err, result) {
callback(err, result);
});
});
};
/**
* @param {HttpContext} context
* @param {*} item
* @param {function(Error=,*=)} callback
*/
FileSystemStorage.prototype.resolvePhysicalPath = function(context, item, callback) {
var _id = item._id, self = this, file_id;
if (_id) {
file_id = common.convertToBase26(_id);
callback(null, path.join(self.root, file_id.substr(0,1), file_id));
}
else {
self.findOne(context, item, function(err, result) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(result)) {
callback(new Error('Item cannot be found'));
}
else {
file_id = common.convertToBase26(result._id);
callback(null, path.join(self.root, file_id.substr(0,1), file_id));
}
}
});
}
};
/**
* @param {HttpContext} context
* @param {*} item
* @param {function(Error=,*=)} callback
*/
FileSystemStorage.prototype.resolveUrl = function(context, item, callback) {
var oid = item.oid, self = this;
if (oid) {
callback(null, util.format(self.virtualPath, oid));
}
else {
self.findOne(context, item, function(err, result) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(result)) {
callback(new Error('Item cannot be found'));
}
else {
callback(null, util.format(self.virtualPath, result.oid));
}
}
});
}
};
/**
* @param {HttpContext} context
* @param {*} item
* @param {Function} callback
*/
FileSystemStorage.prototype.createReadStream = function(context, item, callback) {
var self = this, filePath;
self.findOne(context, item, function(err, result) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(result)) {
callback(new Error('Item cannot be found'));
}
else {
//get file id
var file_id = common.convertToBase26(result._id);
//create file path
filePath = path.join(self.root, file_id.substr(0,1), file_id);
//check file
fs.exists(filePath, function(exists) {
if (!exists) {
callback(new common.FileNotFoundException());
}
else {
callback(null, fs.createReadStream(filePath));
}
});
}
}
});
};
/***
* @param {HttpContext} context
* @param {*} item
* @param {function(Boolean)} callback
*/
FileSystemStorage.prototype.exists = function(context, item, callback) {
callback = callback || function() {};
this.findOne(context, item, function(err, result) {
if (err) {
common.log(err);
callback(false);
}
else {
callback(!common.isNullOrUndefined(result));
}
});
};
/**
* @param {HttpContext} context
* @param {*} query
* @param {function(Error=,*=)} callback
*/
FileSystemStorage.prototype.find = function(context, query, callback) {
var self = this;
if (common.isNullOrUndefined(query)) {
callback();
return;
}
self.ensure(function() {
self.execute(function(db, cb) {
//get collection
var collection = db.collection('fs');
collection.find(query, function(err, result) {
cb(err, result);
});
}, function(err, result) {
callback(err, result);
});
});
};
/**
* @param {function(Error=)} callback
*/
FileSystemStorage.prototype.init = function(callback) {
this.ensure(function(err) {
callback(err);
});
};
/**
* @param {HttpContext} context
* @param {string} src
* @param {*} attrs
* @param {function(Error=,*=)} callback
*/
FileSystemStorage.prototype.copyFrom = function(context, src, attrs, callback) {
var self = this;
callback = callback || function() {};
self.ensure(function(err) {
if (err) {
callback(err);
}
else {
var filename = path.basename(src);
attrs = attrs || {};
//set file composition name
attrs.filename = attrs.filename || filename;
//check source file
fs.exists(src, function(exists) {
if (!exists) {
callback(new Error('The source file cannot be found'));
}
else {
//save attributes
//insert item attributes
self.save(context, attrs, function(err) {
if (err) {
callback(err);
}
else {
//file operation (save to folder)
var file = common.convertToBase26(attrs._id);
fs.exists(path.join(self.root, file.substr(0,1)), function(exists) {
if (exists) {
copyFile(src,path.join(self.root, file.substr(0,1), file), function(err) {
callback(err);
});
}
else {
fs.mkdir(path.join(self.root, file.substr(0,1)), function(err) {
if (err) {
callback(err);
}
else {
copyFile(src,path.join(self.root, file.substr(0,1), file), function(err) {
callback(err);
});
}
});
}
});
}
});
}
});
}
});
};
/**
* @param {HttpContext} context
* @param {string|*} item
* @param {string} dest
* @param {function(Error=,*=)} callback
*/
FileSystemStorage.prototype.copyTo = function(context, item, dest, callback) {
var self = this;
callback = callback || function() {};
if (common.isNullOrUndefined(item)) {
callback(new Error('The source item cannot be empty at this context'));
self.findOne(context, item, function(err, result) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(result)) {
callback(new Error('The source item cannot be found.'));
}
else {
var file = common.convertToBase26(result._id), src = path.join(self.root, file.substr(0,1), file);
fs.exists(src, function(exists) {
if (!exists) {
callback(new Error('The source file cannot be found.'));
}
else {
var destFile = path.join(dest, result.filename);
copyFile(src, destFile, function(err) {
callback(err, destFile);
});
}
});
}
}
});
}
};
/**
* @classdesc AttachmentFileSystemStorage class describes a file storage for attachments' management on local file system.
* @class
* @constructor
* @augments FileStorage
* @param {string} physicalPath The root directory of this storage
* @memberOf module:most-web.files
*/
function AttachmentFileSystemStorage(physicalPath) {
this.root = physicalPath;
this.virtualPath = null;
this.ensure = function(callback) {
var self = this;
callback = callback || function() {};
if (self._initialized) {
callback();
return;
}
if (common.isNullOrUndefined(self.root)) {
callback(new Error('The file system storage root directory cannot be empty at this context.'));
}
else {
//check directory existence
fs.exists(self.root, function(exists) {
if (exists) {
self._initialized = true;
callback();
}
else {
fs.mkdir(self.root,function(err) {
if (err) {
console.log(err);
callback(new Error('An error occured while trying to initialize file system storage.'));
}
else {
self._initialized = true;
callback();
}
});
}
});
}
};
}
util.inherits(AttachmentFileSystemStorage, FileStorage);
/**
* @param {HttpContext} context
* @param {*} item
* @param {function} callback
*/
AttachmentFileSystemStorage.prototype.save = function(context, item, callback) {
var self = this;
self.ensure(function(err) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(item)) {
callback();
return;
}
var attachments = context.model('Attachment');
if (common.isNullOrUndefined(attachments)) {
callback(new Error('Attachment model cannot be found.'));
}
//file default version
item.version = item.version || 1;
//file status (false) not published
item.published = item.published || false;
//set oid explicitly
item.oid = common.randomChars(12);
//set url
item.url = util.format(self.virtualPath, item.oid);
//save attachment
attachments.save(item, function(err) {
callback(err);
});
}
});
};
/**
* @param {HttpContext} context
* @param {*} query
* @param {Function} callback
*/
AttachmentFileSystemStorage.prototype.findOne = function(context, query, callback) {
var self = this;
self.ensure(function(err) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(query)) {
callback();
return;
}
var attachments = context.model('Attachment');
if (common.isNullOrUndefined(attachments)) {
callback(new Error('Attachment model cannot be found.'));
}
attachments.find(query).first(callback);
}
});
};
/**
*
* @param {HttpContext} context
* @param {*} item
* @param {Function} callback
*/
AttachmentFileSystemStorage.prototype.resolvePhysicalPath = function(context, item, callback) {
var id = item.id, self = this, file_id;
if (id) {
file_id = common.convertToBase26(id);
callback(null, path.join(self.root, file_id.substr(0,1), file_id));
}
else {
self.findOne(context, item, function(err, result) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(result)) {
callback(new Error('Item cannot be found'));
}
else {
file_id = common.convertToBase26(result.id);
callback(null, path.join(self.root, file_id.substr(0,1), file_id));
}
}
});
}
};
/**
*
* @param {HttpContext} context
* @param {*} item
* @param {function(Error=,string=)} callback
*/
AttachmentFileSystemStorage.prototype.resolveUrl = function(context, item, callback) {
var oid = item.oid, self = this;
if (oid) {
callback(null, util.format(self.virtualPath, oid));
}
else {
self.findOne(context, item, function(err, result) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(result)) {
callback(new Error('Item cannot be found'));
}
else {
callback(null, util.format(self.virtualPath, item.oid));
}
}
});
}
};
/**
* @param {HttpContext} context
* @param {*} item
* @param {Function} callback
*/
AttachmentFileSystemStorage.prototype.createReadStream = function(context, item, callback) {
var self = this, filePath;
self.findOne(context, item, function(err, result) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(result)) {
callback(new Error('Item cannot be found'));
}
else {
//get file id
var file_id = common.convertToBase26(result.id);
//create file path
filePath = path.join(self.root, file_id.substr(0,1), file_id);
//check file
fs.exists(filePath, function(exists) {
if (!exists) {
callback(new common.FileNotFoundException());
}
else {
callback(null, fs.createReadStream(filePath));
}
});
}
}
});
};
/**
* @param {HttpContext} context
* @param {*} query
* @param {Function} callback
*/
AttachmentFileSystemStorage.prototype.exists = function(context, query, callback) {
callback = callback || function() {};
this.findOne(context, query, function(err, result) {
if (err) {
common.log(err);
callback(false);
}
else {
callback(!common.isNullOrUndefined(result));
}
});
};
/**
* @param {HttpContext} context
* @param {*} query
* @param {function(Error=,*=)} callback
*/
AttachmentFileSystemStorage.prototype.find = function(context, query, callback) {
var self = this;
self.ensure(function(err) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(query)) {
callback();
}
else {
var attachments = context.model('Attachment');
if (common.isNullOrUndefined(attachments)) {
callback(new Error('Attachment model cannot be found.'));
}
attachments.find(query).all(callback)
}
}
});
};
/**
* @param {function(Error=)} callback
*/
AttachmentFileSystemStorage.prototype.init = function(callback) {
this.ensure(callback);
};
/**
* @param {HttpContext} context
* @param {string} src
* @param {*} attrs
* @param {Function} callback
*/
AttachmentFileSystemStorage.prototype.copyFrom = function(context, src, attrs, callback) {
var self = this;
callback = callback || function() {};
self.ensure(function(err) {
if (err) {
callback(err);
}
else {
var filename = path.basename(src);
attrs = attrs || {};
//set file composition name
attrs.filename = attrs.filename || filename;
//check source file
fs.exists(src, function(exists) {
if (!exists) {
callback(new Error('The source file cannot be found'));
}
else {
//save attributes
//insert item attributes
self.save(context, attrs, function(err) {
if (err) {
callback(err);
}
else {
//file operation (save to folder)
var file = common.convertToBase26(attrs.id);
fs.exists(path.join(self.root, file.substr(0,1)), function(exists) {
if (exists) {
copyFile(src,path.join(self.root, file.substr(0,1), file), function(err) {
callback(err);
});
}
else {
fs.mkdir(path.join(self.root, file.substr(0,1)), function(err) {
if (err) {
callback(err);
}
else {
copyFile(src,path.join(self.root, file.substr(0,1), file), function(err) {
callback(err);
});
}
});
}
});
}
});
}
});
}
});
};
/**
* @param {HttpContext} context
* @param {string|*} item
* @param {string} dest
* @param {Function} callback
*/
AttachmentFileSystemStorage.prototype.copyTo = function(context, item, dest, callback) {
var self = this;
callback = callback || function() {};
self.ensure(function(err) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(item)) {
callback(new Error('The source item cannot be empty at this context'));
self.findOne(context, item, function(err, result) {
if (err) {
callback(err);
}
else {
if (common.isNullOrUndefined(result)) {
callback(new Error('The source item cannot be found.'));
}
else {
var file = common.convertToBase26(result.id), src = path.join(self.root, file.substr(0,1), file);
fs.exists(src, function(exists) {
if (!exists) {
callback(new Error('The source file cannot be found.'));
}
else {
var destFile = path.join(dest, result.filename);
copyFile(src, destFile, function(err) {
callback(err, destFile);
});
}
});
}
}
});
}
}
});
};
/**
* @param {string} src
* @param {string} dest
* @param {Function} callback
* @private
*/
function copyFile(src, dest, callback) {
//create read stream
var source = fs.createReadStream(src);
//create write stream
var dest = fs.createWriteStream(dest);
//copy file
source.pipe(dest);
source.on('end', function() {
callback();
});
source.on('error', function(err) {
callback(err);
});
}
/**
* @namespace
* @memberOf module:most-web
*/
var files = {
FileStorage:FileStorage,
FileSystemStorage:FileSystemStorage,
AttachmentFileSystemStorage:AttachmentFileSystemStorage,
/**
* @param {string} physicalPath
* @return {FileStorage}
*/
createFileSystemStorage:function(physicalPath) {
return new FileSystemStorage(physicalPath);
}
};
if (typeof exports !== 'undefined') {
/**
* @see common
*/
module.exports = files;
}