Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FileSystemAdapter for saving files to the server's file system #716

Closed
wants to merge 12 commits into from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ lib/

# Mac DS_Store files
.DS_Store

# Folder created by FileSystemAdapter
/files
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove '/'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without '/' git ignores 'src/Adapters/Files/' folder too. I added this so git ignores only the first-level 'files' folder

12 changes: 12 additions & 0 deletions spec/FilesController.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
}
});
122 changes: 122 additions & 0 deletions src/Adapters/Files/FileSystemAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please allow options in the constructor to provide a path by the developer


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(path) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use var path as it's defined at the top-level

// snippet found on -> https://gist.github.com/danherbert-epam/3960169
var dirs = path.split(pathSep);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use const, let not vars

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(root + dir)) {
try {
fs.mkdirSync(root + dir);
}
catch (e) {
if ( e.code == 'EACCES' ) {
console.error("");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you may wanna throw an error here as there is no fallback, and this will render the File management on the server completely unusable

console.error(colors.red("ERROR: In order to use the FileSystemAdapter, write access to the server's file system is required"));
console.error("");
}
}
}
root += dir + pathSep;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use path.join

}
}
}

export default FileSystemAdapter;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add line break after that line

6 changes: 3 additions & 3 deletions src/cli/parse-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ if (program.args.length > 0 ) {
jsonPath = path.resolve(jsonPath);
options = require(jsonPath);
console.log(`Configuation loaded from ${jsonPath}`)
}
}

options = Object.keys(definitions).reduce(function (options, key) {
if (program[key]) {
Expand All @@ -53,7 +53,7 @@ if (!options.serverURL) {
options.serverURL = `http://localhost:${options.port}${options.mountPath}`;
}

if (!options.appId || !options.masterKey || !options.serverURL) {
if (!program.appId || !program.masterKey || !program.serverURL) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please revert those changes

program.outputHelp();
console.error("");
console.error(colors.red("ERROR: appId, masterKey and serverURL are required"));
Expand All @@ -65,7 +65,7 @@ const app = express();
const api = new ParseServer(options);
app.use(options.mountPath, api);

var server = app.listen(options.port, function() {
app.listen(options.port, function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this change too


for (let key in options) {
let value = options[key];
Expand Down
6 changes: 4 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { AnalyticsRouter } from './Routers/AnalyticsRouter';
import { ClassesRouter } from './Routers/ClassesRouter';
import { FeaturesRouter } from './Routers/FeaturesRouter';
import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
import { FilesController } from './Controllers/FilesController';
import { FilesRouter } from './Routers/FilesRouter';
import { FunctionsRouter } from './Routers/FunctionsRouter';
import { GCSAdapter } from './Adapters/Files/GCSAdapter';
Expand All @@ -46,6 +45,8 @@ import { SessionsRouter } from './Routers/SessionsRouter';
import { setFeature } from './features';
import { UserController } from './Controllers/UserController';
import { UsersRouter } from './Routers/UsersRouter';
import { FilesController } from './Controllers/FilesController';
import { FileSystemAdapter } from './Adapters/Files/FileSystemAdapter';

// Mutate the Parse object to add the Cloud Code handlers
addParseCloud();
Expand Down Expand Up @@ -265,5 +266,6 @@ function addParseCloud() {
module.exports = {
ParseServer: ParseServer,
S3Adapter: S3Adapter,
GCSAdapter: GCSAdapter
GCSAdapter: GCSAdapter,
FileSystemAdapter: FileSystemAdapter
};