nci/db.js

280 lines
6.5 KiB
JavaScript
Raw Normal View History

'use strict';
2015-05-15 22:25:28 +00:00
var Steppy = require('twostep').Steppy,
_ = require('underscore'),
2015-10-03 14:14:41 +00:00
nlevel = require('nlevel'),
2015-10-04 16:57:53 +00:00
path = require('path'),
2015-11-30 20:08:54 +00:00
utils = require('./lib/utils'),
through = require('through');
exports.init = function(dbPath, params, callback) {
2015-10-03 14:14:41 +00:00
callback = _.after(2, callback);
2015-06-12 13:26:34 +00:00
2015-10-03 14:14:41 +00:00
var maindbPath = path.join(dbPath, 'main'),
2015-11-30 20:08:54 +00:00
mainDb = nlevel.db(maindbPath, _({
valueEncoding: 'json'
}).defaults(params), callback);
2015-10-03 14:14:41 +00:00
exports.builds = new nlevel.DocsSection(mainDb, 'builds', {
projections: [
{key: {createDate: 1}, value: pickId},
{key: {descCreateDate: descCreateDate, id: 1}},
{key: {
projectName: pickProjectName,
descCreateDate: descCreateDate,
id: 1
}},
// note that's unordered projection (coz number is numeric),
// it also contains only id
{key: {
projectName: pickProjectName,
number: 1,
id: 1
}, value: pickId},
{key: {
projectName: pickProjectName,
status: 1,
descCreateDate: descCreateDate,
id: 1
}, value: function(build) {
return _(build).pick('id', 'number', 'startDate', 'endDate');
}}
]
});
2015-05-17 10:26:28 +00:00
exports.builds.beforePut = function(builds, callback) {
var self = this,
build;
2015-05-17 10:26:28 +00:00
Steppy(
function() {
2015-12-04 19:54:21 +00:00
// quit if we already have ids and numbers
if (_(builds).all(function(build) {
return build.id && build.number;
})) {
return callback();
}
if (builds.length > 1) {
throw new Error('Build put hooks work only with single build');
}
build = builds[0];
2015-05-17 10:26:28 +00:00
// generate number for build
if (!build.number && build.status === 'in-progress') {
// find last build with number in the same project
self.find({
start: {projectName: build.project.name, descCreateDate: ''},
filter: function(build) {
return 'number' in build;
},
limit: 1
}, this.slot());
} else {
this.pass([]);
}
2015-05-17 10:26:28 +00:00
generateIds(self, builds, this.slot());
},
function(err, prevBuilds) {
var prevBuild = prevBuilds[0];
if (!build.number && build.status === 'in-progress') {
build.number = prevBuild ? prevBuild.number + 1 : 1;
}
this.pass(null);
},
callback
);
};
2015-10-03 14:14:41 +00:00
2015-10-04 12:56:04 +00:00
var buildLogsDbPath = path.join(dbPath, 'buildLogs'),
2015-10-03 14:14:41 +00:00
buildLogsDb = nlevel.db(buildLogsDbPath, params, callback);
2015-11-30 20:08:54 +00:00
// custom optimized emplementation for storing log lines
exports.logLines = {};
exports.logLines.separator = '~';
exports.logLines.end = '\xff';
exports.logLines._getStrKey = function(line) {
return line.buildId + this.separator + (
_(line).has('number') ? utils.toNumberStr(line.number) : ''
);
};
exports.logLines._parseData = function(data) {
var keyParts = data.key.split(this.separator),
buildId = Number(keyParts[0]),
number = Number(keyParts[1]);
return {buildId: buildId, number: number, text: data.value};
};
exports.logLines.put = function(lines, callback) {
var self = this;
lines = _(lines).isArray() ? lines : [lines];
var operations = _(lines).map(function(line) {
return {
type: 'put',
key: self._getStrKey(line),
value: line.text
};
});
buildLogsDb.batch(operations, callback);
};
exports.logLines.createReadStream = function(params) {
var self = this;
if (!params.start && params.end) {
new Error('`end` selected without `start`');
}
params.start = self._getStrKey(params.start);
params.end = params.end ? self._getStrKey(params.end) : params.start;
// add end character
params.end += self.end;
// swap `start` `end` conditions when reverse is set
if (params.reverse) {
var prevStart = params.start;
params.start = params.end;
params.end = prevStart;
}
var resultStream = through(function(data) {
this.emit('data', _(data).isObject() ? self._parseData(data) : data);
});
return buildLogsDb.createReadStream(params)
.on('error', function(err) {
resultStream.emit('error', err);
})
.pipe(resultStream)
};
exports.logLines.find = function(params, callback) {
var self = this;
callback = _(callback).once();
var lines = [];
self.createReadStream(params)
.on('error', callback)
.on('data', function(line) {
lines.push(line);
})
.on('end', function() {
callback(null, lines);
})
};
exports.logLines.remove = function(params, callback) {
var self = this;
callback = _(callback).once();
var operations = [];
self.createReadStream(_({values: false}).extend(params))
.on('error', callback)
.on('data', function(key) {
operations.push({type: 'del', key: key});
})
.on('end', function() {
buildLogsDb.batch(operations, callback);
});
};
2015-05-15 22:25:28 +00:00
};
/*
* Introduce safe `beforePut` (instead of directly use `_beforePut`) for
* id generation etc
*/
nlevel.DocsSection.prototype._beforePut = function(docs, callback) {
var self = this;
2015-10-03 14:14:41 +00:00
// Quit early if beforePut is not set
if (!self.beforePut) {
return callback();
}
if (self._beforePutInProgress) {
setTimeout(function() {
nlevel.DocsSection.prototype._beforePut.call(
self, docs, callback
);
}, 1);
} else {
self._beforePutInProgress = true;
// update createDate before put to provide latest date for last id
// it's rquired for correct generateIds function
_(docs).each(function(doc) {
if (!doc.id) {
doc.createDate = Date.now();
}
});
self.beforePut(docs, callback);
}
};
2015-07-07 20:26:49 +00:00
nlevel.DocsSection.prototype._afterPut = function(docs, callback) {
this._beforePutInProgress = false;
callback();
};
function pickProjectName(build) {
return build.project.name;
}
2015-05-15 22:25:28 +00:00
function generateIds(section, docs, callback) {
Steppy(
function() {
2015-05-17 10:26:28 +00:00
var isAllDocsWithId = _(docs).all(function(doc) {
return 'id' in doc;
});
if (isAllDocsWithId) {
2015-05-15 22:25:28 +00:00
return callback();
}
2015-05-17 10:26:28 +00:00
var isAllDocsWithoutId = _(docs).all(function(doc) {
return 'id' in doc === false;
});
if (!isAllDocsWithoutId) {
throw new Error(
'Documents with id and without should not be mixed'
);
}
2015-05-15 22:25:28 +00:00
section.find({
start: {createDate: ''}, limit: 1, reverse: true
}, this.slot());
},
function(err, lastDocs) {
var id = lastDocs[0] && ++lastDocs[0].id || 1;
_(docs).each(function(doc) {
doc.id = id;
id++;
});
2015-05-15 22:25:28 +00:00
this.pass(null);
},
callback
);
}
function pickId(doc) {
return {id: doc.id};
}
// reversed date - for sorting forward (it's fatster for leveldb then
// reverse: true, see levelup reverse notes for details) but have documents
// sorted by some date in descending order
var maxTime = new Date('03:14:07 UTC 2138-01-19').getTime();
function descCreateDate(doc) {
return maxTime - doc.createDate;
}