Files
bower/lib/core/ResolveCache.js
2013-05-24 23:03:19 +01:00

212 lines
5.8 KiB
JavaScript

var crypto = require('crypto');
var fs = require('fs');
var path = require('path');
var semver = require('semver');
var mout = require('mout');
var Q = require('q');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
function ResolveCache(dir) {
// TODO: Make some options, such as:
// - Max MB
// - Max versions per source
// - Max MB per source
// - etc..
this._dir = dir;
this._versions = {};
mkdirp.sync(dir);
}
// -----------------
ResolveCache.prototype.retrieve = function (source, target) {
var sourceId = this._getSourceId(source);
var dir = path.join(this._dir, sourceId);
target = target || '*';
return this._getVersions(source)
.then(function (versions) {
var suitable;
// If target is a semver, find a suitable version
if (semver.valid(target) != null || semver.validRange(target) != null) {
suitable = mout.array.find(versions, function (version) {
return semver.satisfies(version, target);
});
if (suitable) {
return suitable;
}
}
// If target is '*' check if there's a cached '_unversioned'
if (target === '*') {
return mout.array.find(versions, function (version) {
return version === '_unversioned';
});
}
// Otherwise check if there's an exact match
return mout.array.find(versions, function (version) {
return version === target;
});
})
.then(function (version) {
var canonicalPkg;
if (!version) {
return [];
}
// Resolve with canonical package and package meta
canonicalPkg = path.join(dir, version);
return this._readPkgMeta(canonicalPkg)
.then(function (pkgMeta) {
return [canonicalPkg, pkgMeta];
});
}.bind(this));
};
ResolveCache.prototype.store = function (canonicalPkg, pkgMeta) {
var promise = pkgMeta ? Q.resolve(pkgMeta) : this._readPkgMeta(canonicalPkg);
var sourceId;
var pkgVersion;
var dir;
return promise
.then(function (pkgMeta) {
sourceId = this._getSourceId(pkgMeta._source);
pkgVersion = pkgMeta.version || '_unversioned';
dir = path.join(this._dir, sourceId, pkgVersion);
// Check if directory exists
return Q.nfcall(fs.stat, dir)
.then(function () {
// If it does exists, remove it
return Q.nfcall(rimraf, dir);
}, function (err) {
// If directory does not exists, ensure its basename
// is created
if (err.code === 'ENOENT') {
return Q.nfcall(mkdirp, path.dirname(dir));
}
throw err;
})
// Move the canonical to sourceId/target
.then(function () {
return Q.nfcall(fs.rename, canonicalPkg, dir);
});
}.bind(this))
.then(function () {
var pkgVersion = pkgMeta.version || '_unversioned';
var versions = this._versions[sourceId];
var inCache;
if (versions) {
// Check if this exact version already exists in the
// memory cache
inCache = versions.some(function (version) {
return pkgVersion === version;
});
// If it doesn't, add it to the in memory cache
// and sort the versions afterwards
if (!inCache) {
versions.push(pkgVersion);
this._sortVersions(versions);
}
}
// Resolve with the final location
return dir;
}.bind(this));
};
ResolveCache.prototype.eliminate = function (source, version) {
// TODO:
};
ResolveCache.prototype.empty = function (source) {
// TODO:
};
// ------------------------
ResolveCache.prototype._getSourceId = function (source) {
return crypto.createHash('md5').update(source).digest('hex');
};
ResolveCache.prototype._readPkgMeta = function (dir) {
return Q.nfcall(fs.readFile, path.join(dir, '.bower.json'))
.then(function (contents) {
return JSON.parse(contents.toString());
});
};
ResolveCache.prototype._getVersions = function (source) {
var dir;
var sourceId = this._getSourceId(source);
var cache = this._versions[sourceId];
if (cache) {
return Q.resolve(cache);
}
dir = path.join(this._dir, sourceId);
return Q.nfcall(fs.readdir, dir)
.then(function (versions) {
// If there are no versions there, do not cache in memory
if (!versions.length) {
return versions;
}
// Sort and cache in memory
this._sortVersions(versions);
return this._versions[sourceId] = versions;
}.bind(this), function (err) {
// If the directory does not exists, resolve
// as an empty array
if (err.code === 'ENOENT') {
return this._versions[sourceId] = [];
}
throw err;
}.bind(this));
};
ResolveCache.prototype._sortVersions = function (versions) {
versions.sort(function (version1, version2) {
var validSemver1 = semver.valid(version1) != null;
var validSemver2 = semver.valid(version2) != null;
// If both are semvers, compare them
if (validSemver1 && validSemver2) {
if (semver.gt(version1, version2)) {
return -1;
}
if (semver.lt(version1, version2)) {
return 1;
}
return 0;
}
// If one of them are semvers, give higher priority
if (validSemver1) {
return -1;
}
if (validSemver2) {
return 1;
}
// Otherwise they are considered equal
return 0;
});
};
module.exports = ResolveCache;