var async = require('async') , semver = require('semver') , UError = require('./error').UserError , Local = require('./local-storage') , Proxy = require('./up-storage') , mystreams = require('./streams') , utils = require('./utils') , transaction = require('./transaction') // // Implements Storage interface // (same for storage.js, local-storage.js, up-storage.js) // function Storage(config) { if (!(this instanceof Storage)) return new Storage(config) this.config = config // we support a number of uplinks, but only one local storage // Proxy and Local classes should have similar API interfaces this.uplinks = {} for (var p in config.uplinks) { this.uplinks[p] = new Proxy(config.uplinks[p], config) this.uplinks[p].upname = p } this.local = new Local(config) return this } // // Add a {name} package to a system // // Function checks if package with the same name is available from uplinks. // If it isn't, we create package metadata locally and send requests to do // the same to all uplinks with write access. If all actions succeeded, we // report success, if just one uplink fails, we abort. // // TODO: if a package is uploaded to uplink1, but upload to uplink2 fails, // we report failure, but package is not removed from uplink1. This might // require manual intervention. // // Used storages: local (write) && uplinks (proxy_access, r/o) && // uplinks (proxy_publish, write) // Storage.prototype.add_package = function(name, metadata, callback) { var self = this // NOTE: // - when we checking package for existance, we ask ALL uplinks // - when we publishing package, we only publish it to some of them // so all requests are necessary check_package(function(err) { if (err) return callback(err) publish_package(function(err) { if (err) return callback(err) callback() }) }) function check_package(cb) { self.get_package(name, function(err, results, err_results) { // something weird if (err && err.status !== 404) return cb(err) for (var i=0; i= 500)) { // report internal errors right away return cb(err) } var uplinks = [] for (var i in self.uplinks) { if (self.config.proxy_access(name, i)) { uplinks.push(self.uplinks[i]) } } var result = data || { name: name, versions: {}, 'dist-tags': {}, _uplinks: {}, } var exists = !err var latest = result['dist-tags'].latest async.map(uplinks, function(up, cb) { var oldetag = null if (utils.is_object(result._uplinks[up.upname])) oldetag = result._uplinks[up.upname].etag up.get_package(name, oldetag, function(err, up_res, etag) { if (err || !up_res) return cb(null, [err || new Error('no data')]) try { utils.validate_metadata(up_res, name) } catch(err) { return cb(null, [err]) } result._uplinks[up.upname] = { etag: etag } var this_version = up_res['dist-tags'].latest if (latest == null || (!semver.gt(latest, this_version) && this_version)) { latest = this_version var is_latest = true } ;['versions', 'dist-tags'].forEach(function(key) { for (var i in up_res[key]) { if (!result[key][i] || is_latest) { result[key][i] = up_res[key][i] } } }) // if we got to this point, assume that the correct package exists // on the uplink exists = true cb() }) }, function(err, uplink_errors) { if (err) return callback(err) if (!exists) { return callback(new UError({ status: 404, msg: 'no such package available' }), null, uplink_errors) } self.local.update_versions(name, result, function(err) { if (err) return callback(err) var whitelist = ['_rev', 'name', 'versions', 'dist-tags'] for (var i in result) { if (!~whitelist.indexOf(i)) delete result[i] } callback(null, result, uplink_errors) }) }) }) } module.exports = Storage