diff --git a/.gitignore b/.gitignore index 8ea322c438..33a78f6cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ lib/ # Mac DS_Store files .DS_Store + +# Folder created by FileSystemAdapter +/files diff --git a/spec/FilesController.spec.js b/spec/FilesController.spec.js index 3b2108e71e..c3a281dceb 100644 --- a/spec/FilesController.spec.js +++ b/spec/FilesController.spec.js @@ -2,6 +2,7 @@ var FilesController = require('../src/Controllers/FilesController').FilesControl var GridStoreAdapter = require("../src/Adapters/Files/GridStoreAdapter").GridStoreAdapter; var S3Adapter = require("../src/Adapters/Files/S3Adapter").S3Adapter; var GCSAdapter = require("../src/Adapters/Files/GCSAdapter").GCSAdapter; +var FileSystemAdapter = require("../src/Adapters/Files/FileSystemAdapter").FileSystemAdapter; var Config = require("../src/Config"); var FCTestFactory = require("./FilesControllerTestFactory"); @@ -49,4 +50,15 @@ describe("FilesController",()=>{ } else if (!process.env.TRAVIS) { console.log("set GCP_PROJECT_ID, GCP_KEYFILE_PATH, and GCS_BUCKET to test GCSAdapter") } + + try { + // Test the file system adapter + var fsAdapter = new FileSystemAdapter({ + filesSubDirectory: 'sub1/sub2' + }); + + FCTestFactory.testAdapter("FileSystemAdapter", fsAdapter); + } catch (e) { + console.log("Give write access to the file system to test the FileSystemAdapter. Error: " + e); + } }); diff --git a/src/Adapters/Files/FileSystemAdapter.js b/src/Adapters/Files/FileSystemAdapter.js new file mode 100644 index 0000000000..bd6fc37005 --- /dev/null +++ b/src/Adapters/Files/FileSystemAdapter.js @@ -0,0 +1,120 @@ +// FileSystemAdapter +// +// Stores files in local file system +// Requires write access to the server's file system. + +import { FilesAdapter } from './FilesAdapter'; +import colors from 'colors'; +var fs = require('fs'); +var path = require('path'); +var pathSep = require('path').sep; + +export class FileSystemAdapter extends FilesAdapter { + + constructor({filesSubDirectory = ''} = {}) { + super(); + + this._filesDir = filesSubDirectory; + this._mkdir(this._getApplicationDir()); + if (!this._applicationDirExist()) { + throw "Files directory doesn't exist."; + } + } + + // For a given config object, filename, and data, store a file + // Returns a promise + createFile(config, filename, data) { + return new Promise((resolve, reject) => { + let filepath = this._getLocalFilePath(filename); + fs.writeFile(filepath, data, (err) => { + if(err !== null) { + return reject(err); + } + resolve(data); + }); + }); + } + + deleteFile(config, filename) { + return new Promise((resolve, reject) => { + let filepath = this._getLocalFilePath(filename); + fs.readFile( filepath , function (err, data) { + if(err !== null) { + return reject(err); + } + fs.unlink(filepath, (unlinkErr) => { + if(err !== null) { + return reject(unlinkErr); + } + resolve(data); + }); + }); + + }); + } + + getFileData(config, filename) { + return new Promise((resolve, reject) => { + let filepath = this._getLocalFilePath(filename); + fs.readFile( filepath , function (err, data) { + if(err !== null) { + return reject(err); + } + resolve(data); + }); + }); + } + + getFileLocation(config, filename) { + return (config.mount + '/' + this._getLocalFilePath(filename)); + } + + /* + Helpers + --------------- */ + _getApplicationDir() { + if (this._filesDir) { + return path.join('files', this._filesDir); + } else { + return 'files'; + } + } + + _applicationDirExist() { + return fs.existsSync(this._getApplicationDir()); + } + + _getLocalFilePath(filename) { + let applicationDir = this._getApplicationDir(); + if (!fs.existsSync(applicationDir)) { + this._mkdir(applicationDir); + } + return path.join(applicationDir, encodeURIComponent(filename)); + } + + _mkdir(dirPath) { + // snippet found on -> https://gist.github.com/danherbert-epam/3960169 + let dirs = dirPath.split(pathSep); + var root = ""; + + while (dirs.length > 0) { + var dir = dirs.shift(); + if (dir === "") { // If directory starts with a /, the first path will be an empty string. + root = pathSep; + } + if (!fs.existsSync(path.join(root, dir))) { + try { + fs.mkdirSync(path.join(root, dir)); + } + catch (e) { + if ( e.code == 'EACCES' ) { + throw new Error("PERMISSION ERROR: In order to use the FileSystemAdapter, write access to the server's file system is required."); + } + } + } + root = path.join(root, dir, pathSep); + } + } +} + +export default FileSystemAdapter; diff --git a/src/index.js b/src/index.js index 01a1d48ab4..c425948d58 100644 --- a/src/index.js +++ b/src/index.js @@ -48,6 +48,7 @@ import { SessionsRouter } from './Routers/SessionsRouter'; import { setFeature } from './features'; import { UserController } from './Controllers/UserController'; import { UsersRouter } from './Routers/UsersRouter'; +import { FileSystemAdapter } from './Adapters/Files/FileSystemAdapter'; // Mutate the Parse object to add the Cloud Code handlers addParseCloud(); @@ -274,5 +275,6 @@ ParseServer.createLiveQueryServer = function(httpServer, config) { module.exports = { ParseServer: ParseServer, S3Adapter: S3Adapter, - GCSAdapter: GCSAdapter + GCSAdapter: GCSAdapter, + FileSystemAdapter: FileSystemAdapter };