var mout = require('mout'); var Q = require('q'); var ResolveCache = require('./ResolveCache'); var resolverFactory = require('./resolverFactory'); var createError = require('../util/createError'); var RegistryClient = require('bower-registry-client'); function PackageRepository(config, logger) { var registryOptions; this._config = config; this._logger = logger; // Instantiate the registry registryOptions = mout.object.deepMixIn({}, this._config); registryOptions.cache = this._config.storage.registry; this._registryClient = new RegistryClient(registryOptions, logger); // Instantiate the resolve cache this._resolveCache = new ResolveCache(this._config); } // ----------------- PackageRepository.prototype.fetch = function(decEndpoint) { var logger; var that = this; var isTargetable; var info = { decEndpoint: decEndpoint }; // Create a new logger that pipes everything to ours that will be // used to fetch logger = this._logger.geminate(); // Intercept all logs, adding additional information logger.intercept(function(log) { that._extendLog(log, info); }); return ( this._getResolver(decEndpoint, logger) // Decide if we retrieve from the cache or not // Also decide if we validate the cached entry or not .then(function(resolver) { info.resolver = resolver; isTargetable = resolver.constructor.isTargetable; if (!resolver.isCacheable()) { return that._resolve(resolver, logger); } // If force flag is used, bypass cache, but write to cache anyway if (that._config.force) { logger.action( 'resolve', resolver.getSource() + '#' + resolver.getTarget() ); return that._resolve(resolver, logger); } // Note that we use the resolver methods to query the // cache because transformations/normalisations can occur return ( that._resolveCache .retrieve(resolver.getSource(), resolver.getTarget()) // Decide if we can use the one from the resolve cache .spread(function(canonicalDir, pkgMeta) { // If there's no package in the cache if (!canonicalDir) { // And the offline flag is passed, error out if (that._config.offline) { throw createError( 'No cached version for ' + resolver.getSource() + '#' + resolver.getTarget(), 'ENOCACHE', { resolver: resolver } ); } // Otherwise, we have to resolve it logger.info( 'not-cached', resolver.getSource() + (resolver.getTarget() ? '#' + resolver.getTarget() : '') ); logger.action( 'resolve', resolver.getSource() + '#' + resolver.getTarget() ); return that._resolve(resolver, logger); } info.canonicalDir = canonicalDir; info.pkgMeta = pkgMeta; logger.info( 'cached', resolver.getSource() + (pkgMeta._release ? '#' + pkgMeta._release : '') ); // If offline flag is used, use directly the cached one if (that._config.offline) { return [canonicalDir, pkgMeta, isTargetable]; } // Otherwise check for new contents logger.action( 'validate', (pkgMeta._release ? pkgMeta._release + ' against ' : '') + resolver.getSource() + (resolver.getTarget() ? '#' + resolver.getTarget() : '') ); return resolver .hasNew(pkgMeta) .then(function(hasNew) { // If there are no new contents, resolve to // the cached one if (!hasNew) { return [ canonicalDir, pkgMeta, isTargetable ]; } // Otherwise resolve to the newest one logger.info( 'new', 'version for ' + resolver.getSource() + '#' + resolver.getTarget() ); logger.action( 'resolve', resolver.getSource() + '#' + resolver.getTarget() ); return that._resolve(resolver, logger); }); }) ); }) // If something went wrong, also extend the error .fail(function(err) { that._extendLog(err, info); throw err; }) ); }; PackageRepository.prototype.versions = function(source) { // Resolve the source using the factory because the // source can actually be a registry name return this._getResolver({ source: source }).then( function(resolver) { // If offline, resolve using the cached versions if (this._config.offline) { return this._resolveCache.versions(resolver.getSource()); } // Otherwise, fetch remotely return resolver.constructor.versions(resolver.getSource()); }.bind(this) ); }; PackageRepository.prototype.eliminate = function(pkgMeta) { return Q.all([ this._resolveCache.eliminate(pkgMeta), Q.nfcall( this._registryClient.clearCache.bind(this._registryClient), pkgMeta.name ) ]); }; PackageRepository.prototype.clear = function() { return Q.all([ this._resolveCache.clear(), Q.nfcall(this._registryClient.clearCache.bind(this._registryClient)) ]); }; PackageRepository.prototype.reset = function() { this._resolveCache.reset(); this._registryClient.resetCache(); }; PackageRepository.prototype.list = function() { return this._resolveCache.list(); }; PackageRepository.prototype.getRegistryClient = function() { return this._registryClient; }; PackageRepository.prototype.getResolveCache = function() { return this._resolveCache; }; PackageRepository.clearRuntimeCache = function() { ResolveCache.clearRuntimeCache(); RegistryClient.clearRuntimeCache(); resolverFactory.clearRuntimeCache(); }; // --------------------- PackageRepository.prototype._getResolver = function(decEndpoint, logger) { logger = logger || this._logger; // Get the appropriate resolver return resolverFactory( decEndpoint, { config: this._config, logger: logger }, this._registryClient ); }; PackageRepository.prototype._resolve = function(resolver, logger) { var that = this; // Resolve the resolver return ( resolver .resolve() // Store in the cache .then(function(canonicalDir) { if (!resolver.isCacheable()) { return canonicalDir; } return that._resolveCache.store( canonicalDir, resolver.getPkgMeta() ); }) // Resolve promise with canonical dir and package meta .then(function(dir) { var pkgMeta = resolver.getPkgMeta(); logger.info( 'resolved', resolver.getSource() + (pkgMeta._release ? '#' + pkgMeta._release : '') ); return [dir, pkgMeta, resolver.constructor.isTargetable()]; }) ); }; PackageRepository.prototype._extendLog = function(log, info) { log.data = log.data || {}; // Store endpoint info in each log if (info.decEndpoint) { log.data.endpoint = mout.object.pick(info.decEndpoint, [ 'name', 'source', 'target' ]); } // Store the resolver info in each log if (info.resolver) { log.data.resolver = { name: info.resolver.getName(), source: info.resolver.getSource(), target: info.resolver.getTarget() }; } // Store the canonical dir and its meta in each log if (info.canonicalDir) { log.data.canonicalDir = info.canonicalDir; log.data.pkgMeta = info.pkgMeta; } return log; }; module.exports = PackageRepository;