2013-10-22 00:10:25 -05:00
|
|
|
var fs = require('fs')
|
|
|
|
, fsExt = require('fs-ext')
|
|
|
|
, Path = require('path')
|
|
|
|
, mystreams = require('./streams')
|
|
|
|
, FSError = require('./error').FSError
|
2013-05-31 17:57:28 -05:00
|
|
|
|
|
|
|
function make_directories(dest, cb) {
|
|
|
|
var dir = Path.dirname(dest);
|
|
|
|
if (dir === '.' || dir === '..') return cb();
|
|
|
|
fs.mkdir(dir, function(err) {
|
|
|
|
if (err && err.code === 'ENOENT') {
|
|
|
|
make_directories(dir, function() {
|
|
|
|
fs.mkdir(dir, cb);
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
cb();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-06-18 13:14:55 -05:00
|
|
|
function write(dest, data, cb) {
|
2013-05-31 17:57:28 -05:00
|
|
|
var safe_write = function(cb) {
|
2013-06-21 19:19:46 -05:00
|
|
|
var tmpname = dest + '.tmp' + String(Math.random()).substr(2);
|
|
|
|
fs.writeFile(tmpname, data, function(err) {
|
|
|
|
if (err) return cb(err);
|
|
|
|
return fs.rename(tmpname, dest, cb);
|
|
|
|
});
|
2013-05-31 17:57:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
safe_write(function(err) {
|
|
|
|
if (err && err.code === 'ENOENT') {
|
|
|
|
make_directories(dest, function() {
|
|
|
|
safe_write(cb);
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
cb(err);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-06-20 08:07:34 -05:00
|
|
|
function write_stream(name) {
|
2013-09-27 04:54:43 -05:00
|
|
|
var stream = new mystreams.UploadTarballStream();
|
2013-06-20 08:07:34 -05:00
|
|
|
|
2013-09-28 07:19:40 -05:00
|
|
|
var _ended = 0;
|
|
|
|
stream.on('end', function() {
|
|
|
|
_ended = 1;
|
|
|
|
});
|
|
|
|
|
2013-06-20 08:07:34 -05:00
|
|
|
fs.exists(name, function(exists) {
|
|
|
|
if (exists) return stream.emit('error', new FSError('EEXISTS'));
|
|
|
|
|
|
|
|
var tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, '');
|
|
|
|
var file = fs.createWriteStream(tmpname);
|
2013-10-11 04:50:41 -05:00
|
|
|
var opened = false;
|
2013-09-27 04:54:43 -05:00
|
|
|
stream.pipe(file);
|
2013-09-28 07:19:40 -05:00
|
|
|
|
2013-09-27 04:54:43 -05:00
|
|
|
stream.done = function() {
|
2013-09-28 07:19:40 -05:00
|
|
|
function onend() {
|
2013-09-27 06:31:28 -05:00
|
|
|
file.on('close', function() {
|
|
|
|
fs.rename(tmpname, name, function(err) {
|
|
|
|
if (err) stream.emit('error', err);
|
|
|
|
stream.emit('success');
|
|
|
|
});
|
2013-07-12 14:51:04 -05:00
|
|
|
});
|
2013-09-27 06:31:28 -05:00
|
|
|
file.destroySoon();
|
2013-09-28 07:19:40 -05:00
|
|
|
}
|
|
|
|
if (_ended) {
|
|
|
|
onend();
|
|
|
|
} else {
|
|
|
|
stream.on('end', onend);
|
|
|
|
}
|
2013-09-27 04:54:43 -05:00
|
|
|
};
|
|
|
|
stream.abort = function() {
|
2013-10-11 04:50:41 -05:00
|
|
|
if (opened) {
|
|
|
|
opened = false;
|
|
|
|
file.on('close', function() {
|
|
|
|
fs.unlink(tmpname, function(){});
|
|
|
|
});
|
|
|
|
}
|
2013-09-28 07:19:40 -05:00
|
|
|
file.destroySoon();
|
2013-09-27 04:54:43 -05:00
|
|
|
};
|
|
|
|
file.on('open', function() {
|
2013-10-11 04:50:41 -05:00
|
|
|
opened = true;
|
2013-09-27 04:54:43 -05:00
|
|
|
// re-emitting open because it's handled in storage.js
|
|
|
|
stream.emit('open');
|
2013-09-24 01:28:26 -05:00
|
|
|
});
|
2013-09-28 07:19:40 -05:00
|
|
|
file.on('error', function(err) {
|
|
|
|
stream.emit('error', err);
|
|
|
|
});
|
2013-06-20 08:07:34 -05:00
|
|
|
});
|
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
function read_stream(name, stream, callback) {
|
|
|
|
return fs.createReadStream(name);
|
|
|
|
}
|
|
|
|
|
2013-05-31 17:57:28 -05:00
|
|
|
function create(name, contents, callback) {
|
|
|
|
fs.exists(name, function(exists) {
|
2013-06-20 08:07:34 -05:00
|
|
|
if (exists) return callback(new FSError('EEXISTS'));
|
2013-06-18 13:14:55 -05:00
|
|
|
write(name, contents, callback);
|
2013-05-31 17:57:28 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function update(name, contents, callback) {
|
|
|
|
fs.exists(name, function(exists) {
|
2013-06-20 08:07:34 -05:00
|
|
|
if (!exists) return callback(new FSError('ENOENT'));
|
2013-06-18 13:14:55 -05:00
|
|
|
write(name, contents, callback);
|
2013-05-31 17:57:28 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function read(name, callback) {
|
2013-10-22 00:10:25 -05:00
|
|
|
fs.readFile(name, callback)
|
|
|
|
}
|
|
|
|
|
|
|
|
// open and flock with exponential backoff
|
|
|
|
function open_flock(name, opmod, flmod, tries, backoff, cb) {
|
|
|
|
fs.open(name, opmod, function(err, fd) {
|
|
|
|
if (err) return cb(err, fd)
|
|
|
|
|
|
|
|
fsExt.flock(fd, flmod, function(err) {
|
|
|
|
if (err) {
|
|
|
|
if (!tries) {
|
|
|
|
fs.close(fd, function() {
|
|
|
|
cb(err)
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
fs.close(fd, function() {
|
|
|
|
setTimeout(function() {
|
|
|
|
open_flock(name, opmod, flmod, tries-1, backoff*2, cb)
|
|
|
|
}, backoff)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cb(null, fd)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// this function neither unlocks file nor closes it
|
|
|
|
// it'll have to be done manually later
|
|
|
|
function lock_and_read(name, callback) {
|
|
|
|
open_flock(name, 'r', 'exnb', 4, 10, function(err, fd) {
|
|
|
|
if (err) return callback(err, fd)
|
|
|
|
|
|
|
|
fs.fstat(fd, function(err, st) {
|
|
|
|
if (err) return callback(err, fd)
|
|
|
|
|
|
|
|
var buffer = new Buffer(st.size)
|
|
|
|
fs.read(fd, buffer, 0, st.size, null, function(err, bytesRead, buffer) {
|
|
|
|
if (bytesRead != st.size) return callback(new Error('st.size != bytesRead'), fd)
|
|
|
|
|
|
|
|
callback(null, fd, buffer)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2013-05-31 17:57:28 -05:00
|
|
|
}
|
|
|
|
|
2013-06-14 03:34:29 -05:00
|
|
|
function Storage(path) {
|
|
|
|
this.path = path;
|
|
|
|
try {
|
|
|
|
fs.mkdirSync(path);
|
|
|
|
console.log('created new packages directory: ', path);
|
|
|
|
} catch(err) {
|
|
|
|
if (err.code !== 'EEXIST') throw new Error(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.read = function(name, cb) {
|
2013-10-22 00:10:25 -05:00
|
|
|
read(this.path + '/' + name, cb)
|
2013-06-14 03:34:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.read_json = function(name, cb) {
|
|
|
|
read(this.path + '/' + name, function(err, res) {
|
|
|
|
if (err) return cb(err);
|
2013-10-18 15:46:13 -05:00
|
|
|
|
|
|
|
var args = []
|
|
|
|
try {
|
|
|
|
args = [null, JSON.parse(res.toString('utf8'))]
|
|
|
|
} catch(err) {
|
|
|
|
args = [err]
|
|
|
|
}
|
|
|
|
cb.apply(null, args)
|
|
|
|
})
|
2013-06-14 03:34:29 -05:00
|
|
|
}
|
|
|
|
|
2013-10-22 00:10:25 -05:00
|
|
|
Storage.prototype.lock_and_read = function(name, cb) {
|
|
|
|
lock_and_read(this.path + '/' + name, cb)
|
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.lock_and_read_json = function(name, cb) {
|
|
|
|
lock_and_read(this.path + '/' + name, function(err, fd, res) {
|
|
|
|
if (err) return cb(err, fd)
|
|
|
|
|
|
|
|
var args = []
|
|
|
|
try {
|
|
|
|
args = [null, fd, JSON.parse(res.toString('utf8'))]
|
|
|
|
} catch(err) {
|
|
|
|
args = [err, fd]
|
|
|
|
}
|
|
|
|
cb.apply(null, args)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2013-10-18 16:17:53 -05:00
|
|
|
Storage.prototype.path_to = function(file) {
|
|
|
|
return this.path + '/' + file
|
|
|
|
}
|
|
|
|
|
2013-06-14 03:34:29 -05:00
|
|
|
Storage.prototype.create = function(name, value, cb) {
|
|
|
|
create(this.path + '/' + name, value, cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.create_json = function(name, value, cb) {
|
2013-06-20 08:07:34 -05:00
|
|
|
create(this.path + '/' + name, JSON.stringify(value, null, '\t'), cb);
|
2013-06-14 03:34:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.update = function(name, value, cb) {
|
|
|
|
update(this.path + '/' + name, value, cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.update_json = function(name, value, cb) {
|
2013-06-20 08:07:34 -05:00
|
|
|
update(this.path + '/' + name, JSON.stringify(value, null, '\t'), cb);
|
2013-06-14 03:34:29 -05:00
|
|
|
}
|
|
|
|
|
2013-06-18 13:14:55 -05:00
|
|
|
Storage.prototype.write = function(name, value, cb) {
|
|
|
|
write(this.path + '/' + name, value, cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.write_json = function(name, value, cb) {
|
2013-06-19 11:58:16 -05:00
|
|
|
write(this.path + '/' + name, JSON.stringify(value, null, '\t'), cb);
|
2013-06-18 13:14:55 -05:00
|
|
|
}
|
|
|
|
|
2013-06-20 08:07:34 -05:00
|
|
|
Storage.prototype.write_stream = function(name, value, cb) {
|
|
|
|
return write_stream(this.path + '/' + name, value, cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
Storage.prototype.read_stream = function(name, cb) {
|
|
|
|
return read_stream(this.path + '/' + name, cb);
|
|
|
|
}
|
|
|
|
|
2013-10-06 03:27:50 -05:00
|
|
|
Storage.prototype.unlink = function(name, cb) {
|
|
|
|
fs.unlink(this.path + '/' + name, cb);
|
|
|
|
}
|
|
|
|
|
2013-06-14 03:34:29 -05:00
|
|
|
module.exports = Storage;
|
2013-05-31 17:57:28 -05:00
|
|
|
|