Add better volume error reporting and volume mounting
This commit is contained in:
+26
-7
@@ -2,7 +2,8 @@
|
||||
|
||||
var HttpError = require('../httperror'),
|
||||
user = require('../user.js'),
|
||||
volume = require('../volume.js');
|
||||
volume = require('../volume.js'),
|
||||
VolumeError = volume.VolumeError;
|
||||
|
||||
exports = module.exports = {
|
||||
initialize: initialize,
|
||||
@@ -51,7 +52,7 @@ function createVolume(req, res, next) {
|
||||
}
|
||||
|
||||
if (!req.user.password) {
|
||||
return next(new HttpError(400, 'Password not specified'));
|
||||
return next(new HttpError(400, 'No password provided'));
|
||||
}
|
||||
|
||||
user.verify(req.user.username, req.user.password, function (error, result) {
|
||||
@@ -74,14 +75,16 @@ function createVolume(req, res, next) {
|
||||
}
|
||||
|
||||
function listFiles(req, res, next) {
|
||||
if (!req.volume) return next(new HttpError(404, 'No such volume'));
|
||||
|
||||
// TODO this is unsafe params index might change - Johannes
|
||||
var directory = req.params[0] ? req.params[0] : '.';
|
||||
|
||||
req.volume.listFiles(directory, function (error, files) {
|
||||
if (error) {
|
||||
if (error && error.reason === VolumeError.READ_ERROR) {
|
||||
return next(new HttpError(404, 'Unable to read folder'));
|
||||
} else if (error && error.reason === VolumeError.NOT_MOUNTED) {
|
||||
return next(new HttpError(401, 'Volume not mounted'));
|
||||
} else if (error) {
|
||||
return next(new HttpError(500, 'Internal server error'));
|
||||
}
|
||||
|
||||
res.send(200, files);
|
||||
@@ -89,11 +92,27 @@ function listFiles(req, res, next) {
|
||||
}
|
||||
|
||||
function mount(req, res, next) {
|
||||
// TODO
|
||||
if (!req.user.password) {
|
||||
return next(new HttpError(401, 'No password provided'));
|
||||
}
|
||||
|
||||
req.volume.open(req.user.password, function (error) {
|
||||
if (error) {
|
||||
return next(new HttpError(402, 'Unable to open volume'));
|
||||
}
|
||||
|
||||
res.send(200);
|
||||
});
|
||||
}
|
||||
|
||||
function unmount(req, res, next) {
|
||||
// TODO
|
||||
req.volume.close(function (error) {
|
||||
if (error) {
|
||||
return next(new HttpError(500, 'Unable to close volume'));
|
||||
}
|
||||
|
||||
res.send(200);
|
||||
});
|
||||
}
|
||||
|
||||
function attachVolume(req, res, next, volumeId) {
|
||||
|
||||
+77
-37
@@ -8,17 +8,38 @@ var fs = require('fs'),
|
||||
path = require('path'),
|
||||
assert = require('assert'),
|
||||
crypto = require('crypto'),
|
||||
util = require('util'),
|
||||
HttpError = require('./httperror.js'),
|
||||
Repo = require('./repo.js');
|
||||
|
||||
exports = module.exports = {
|
||||
Volume: Volume,
|
||||
VolumeError: VolumeError,
|
||||
list: listVolumes,
|
||||
create: createVolume,
|
||||
destroy: destroyVolume,
|
||||
get: getVolume
|
||||
};
|
||||
|
||||
// http://dustinsenos.com/articles/customErrorsInNode
|
||||
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
|
||||
function VolumeError(err, reason) {
|
||||
Error.call(this);
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
|
||||
this.name = this.constructor.name;
|
||||
this.message = JSON.stringify(err);
|
||||
this.code = err ? err.code : null;
|
||||
this.reason = reason || VolumeError.INTERNAL_ERROR;
|
||||
this.statusCode = 500; // any db error is a server error
|
||||
}
|
||||
util.inherits(VolumeError, Error);
|
||||
VolumeError.INTERNAL_ERROR = 1;
|
||||
VolumeError.NOT_MOUNTED = 2;
|
||||
VolumeError.READ_ERROR = 3;
|
||||
VolumeError.META_MISSING = 4;
|
||||
VolumeError.NO_SUCH_VOLUME = 5;
|
||||
|
||||
function generateNewVolumePassword() {
|
||||
return crypto.randomBytes(32).readUInt32LE(0);
|
||||
}
|
||||
@@ -150,51 +171,65 @@ Volume.prototype.listFiles = function (directory, callback) {
|
||||
var that = this;
|
||||
var folder = path.join(this.mountPoint, directory);
|
||||
|
||||
fs.readdir(folder, function (error, files) {
|
||||
this.encfs.isMounted(function (error, mounted) {
|
||||
if (error) {
|
||||
debug('Error checking if encfs for volume "' + that.name + '" is mounted.');
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
var ret = [];
|
||||
|
||||
if (folder !== that.mountPoint) {
|
||||
var dirUp = {};
|
||||
dirUp.filename = '..';
|
||||
dirUp.path = path.join(directory, '..');
|
||||
dirUp.isDirectory = true;
|
||||
dirUp.isFile = false;
|
||||
dirUp.stat = { size: 0 };
|
||||
ret.push(dirUp);
|
||||
if (!mounted) {
|
||||
debug('Encfs for volume "' + that.name + '" is not mounted.');
|
||||
return callback(new VolumeError(null, VolumeError.NOT_MOUNTED));
|
||||
}
|
||||
|
||||
files.forEach(function (file) {
|
||||
// filter .git
|
||||
if (file === '.git') {
|
||||
return;
|
||||
fs.readdir(folder, function (error, files) {
|
||||
if (error) {
|
||||
debug('Unable to read directory "' + folder + '" for volume "' + that.name + '".');
|
||||
return callback(new VolumeError(error, VolumeError.READ_ERROR));
|
||||
}
|
||||
|
||||
var tmp = {};
|
||||
tmp.filename = file;
|
||||
tmp.path = path.join(directory, file);
|
||||
var ret = [];
|
||||
|
||||
try {
|
||||
tmp.stat = fs.statSync(path.join(folder, file));
|
||||
tmp.isFile = tmp.stat.isFile();
|
||||
tmp.isDirectory = tmp.stat.isDirectory();
|
||||
} catch (e) {
|
||||
console.log('Error getting file information', e);
|
||||
if (folder !== that.mountPoint) {
|
||||
var dirUp = {};
|
||||
dirUp.filename = '..';
|
||||
dirUp.path = path.join(directory, '..');
|
||||
dirUp.isDirectory = true;
|
||||
dirUp.isFile = false;
|
||||
dirUp.stat = { size: 0 };
|
||||
ret.push(dirUp);
|
||||
}
|
||||
|
||||
ret.push(tmp);
|
||||
files.forEach(function (file) {
|
||||
// filter .git
|
||||
if (file === '.git') {
|
||||
return;
|
||||
}
|
||||
|
||||
var tmp = {};
|
||||
tmp.filename = file;
|
||||
tmp.path = path.join(directory, file);
|
||||
|
||||
try {
|
||||
tmp.stat = fs.statSync(path.join(folder, file));
|
||||
tmp.isFile = tmp.stat.isFile();
|
||||
tmp.isDirectory = tmp.stat.isDirectory();
|
||||
} catch (e) {
|
||||
debug('Error getting file information:' + JSON.stringify(e));
|
||||
}
|
||||
|
||||
ret.push(tmp);
|
||||
});
|
||||
|
||||
callback(null, ret);
|
||||
});
|
||||
|
||||
callback(null, ret);
|
||||
});
|
||||
};
|
||||
|
||||
Volume.prototype.addUser = function (user, password, callback) {
|
||||
if (!this.meta) {
|
||||
return callback(new Error('Volume not valid'));
|
||||
debug('Invalid volume "' + this.name + '". Misses the meta database.');
|
||||
return callback(new VolumeError(null, VolumeError.META_MISSING));
|
||||
}
|
||||
|
||||
// TODO encrypt password with user's password
|
||||
@@ -206,6 +241,7 @@ Volume.prototype.addUser = function (user, password, callback) {
|
||||
// pretend to encrypt the password with the users password
|
||||
this.meta.put(record, function (error) {
|
||||
if (error) {
|
||||
debug('Unable to add user to meta db. ' + JSON.stringify(error));
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
@@ -215,7 +251,8 @@ Volume.prototype.addUser = function (user, password, callback) {
|
||||
|
||||
Volume.prototype.removeUser = function (user, callback) {
|
||||
if (!this.meta) {
|
||||
return callback(new Error('Volume not valid'));
|
||||
debug('Invalid volume "' + this.name + '". Misses the meta database.');
|
||||
return callback(new VolumeError(null, VolumeError.META_MISSING));
|
||||
}
|
||||
|
||||
this.meta.remove(user.username, callback);
|
||||
@@ -231,7 +268,8 @@ function listVolumes(username, config, callback) {
|
||||
|
||||
fs.readdir(config.dataRoot, function (error, files) {
|
||||
if (error) {
|
||||
return callback(new Error('Unable to read root folder'));
|
||||
debug('Unable to list volumes.' + JSON.stringify(error));
|
||||
return callback(new VolumeError(error, VolumeError.READ_ERROR));
|
||||
}
|
||||
|
||||
var ret = [];
|
||||
@@ -256,7 +294,7 @@ function listVolumes(username, config, callback) {
|
||||
|
||||
ret.push(vol);
|
||||
|
||||
debug('Detected repo : ' + file);
|
||||
debug('Detected volume with repo: "' + file + '".');
|
||||
});
|
||||
|
||||
callback(null, ret);
|
||||
@@ -276,7 +314,8 @@ function createVolume(name, user, config, callback) {
|
||||
|
||||
encfs.create(vol.dataPath, vol.mountPoint, volPassword, function (error, result) {
|
||||
if (error) {
|
||||
return callback(new Error('Volume creation failed: ' + JSON.stringify(error)));
|
||||
debug('Unable to create encfs container for volume "' + name + '". ' + JSON.stringify(error));
|
||||
return callback(new VolumeError(error, VolumeError.INTERNAL_ERROR));
|
||||
}
|
||||
|
||||
var tmpDir = path.join(vol.mountPoint, 'tmp');
|
||||
@@ -284,19 +323,19 @@ function createVolume(name, user, config, callback) {
|
||||
|
||||
vol.addUser(user, volPassword, function (error, result) {
|
||||
if (error) {
|
||||
return callback(new Error('Adding user to volume failed.'));
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
// ## move this to repo
|
||||
vol.repo = new Repo({ rootDir: vol.mountPoint, tmpDir: tmpDir });
|
||||
vol.repo.create(user, function (error) {
|
||||
if (error) {
|
||||
return callback(new Error('Error creating repo in volume'));
|
||||
return callback(new VolumeError(error, VolumeError.INTERNAL_ERROR));
|
||||
}
|
||||
|
||||
vol.repo.addFile('README.md', { contents: 'README' }, function (error, commit) {
|
||||
if (error) {
|
||||
return callback(new Error('Error adding README: ' + error));
|
||||
return callback(new VolumeError(error, VolumeError.INTERNAL_ERROR));
|
||||
}
|
||||
|
||||
callback(null, vol);
|
||||
@@ -315,7 +354,7 @@ function destroyVolume(name, username, config, callback) {
|
||||
|
||||
var vol = getVolume(name, username, config);
|
||||
if (!vol) {
|
||||
return callback(new Error('No such volume for this user.'));
|
||||
return callback(new VolumeError(null, VolumeError.NO_SUCH_VOLUME));
|
||||
}
|
||||
|
||||
vol.destroy(callback);
|
||||
@@ -332,10 +371,11 @@ function getVolume(name, username, config) {
|
||||
var vol = new Volume(name, config);
|
||||
try {
|
||||
if (!fs.existsSync(vol.dataPath)) {
|
||||
debug('No volume "' + name + '" for user "' + username + '".');
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
debug('No such volume');
|
||||
debug('No volume "' + name + '" for user "' + username + '". ' + JSON.stringify(e));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,31 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="password-dialog" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Authorization</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="password-dialog-form" class="form-horizontal" action="" autocomplete="off">
|
||||
<div class="form-group">
|
||||
<label for="password" class="col-lg-2 control-label">Password</label>
|
||||
<div class="col-lg-10">
|
||||
<input type="password" class="form-control" id="password" placeholder="Password" name="password">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default btn-primary" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" id="password-dialog-ok-button" class="btn btn-default btn-primary">OK</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
<div id="new-volume-dialog" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
||||
@@ -163,5 +163,51 @@ function getFileListing(volume, folder) {
|
||||
document.getElementById("file-list-container").appendChild(group);
|
||||
}).fail(function (error) {
|
||||
console.error("Unable to get file listing.", error);
|
||||
if (error.status === 401) {
|
||||
mountVolume(volume, function (error) {
|
||||
if (error) {
|
||||
console.error('Failed to mount volume: ' + error);
|
||||
return;
|
||||
}
|
||||
|
||||
getFileListing(volume, folder);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function mountVolume(volume, callback) {
|
||||
$("#password-dialog").modal();
|
||||
$('#password-dialog-ok-button').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#password-dialog-form').submit();
|
||||
});
|
||||
|
||||
$('#password-dialog-form').submit(function (event) {
|
||||
event.preventDefault();
|
||||
var form = $(this);
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: '/api/v1/volume/' + volume + '/mount',
|
||||
beforeSend: function (xhr) {
|
||||
var username = window.yellowtent.username;
|
||||
var password = form.find("input[name='password']").val();
|
||||
xhr.setRequestHeader('Authorization', auth(username, password));
|
||||
},
|
||||
success: function (data) {
|
||||
$("#password-dialog").modal("hide");
|
||||
callback(null);
|
||||
},
|
||||
error: function (error) {
|
||||
var msg = 'Failed.';
|
||||
|
||||
try {
|
||||
msg = JSON.parse(error.responseText).message;
|
||||
} catch (e) {}
|
||||
|
||||
callback(msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user