diff --git a/lib/index.js b/lib/index.js index e9b6aabfb..b1aa0fb5f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -170,6 +170,17 @@ module.exports = function(config_hash) { stream.pipe(res) }) + // searching packages + app.get('/-/all/:package?', can('access'), function(req, res, next) { + var startkeyRegExp = /(.*)=([0-9]+)$/ + var startkey = startkeyRegExp.test(req.url) ? new Date(+req.url.replace(startkeyRegExp, '$2')) : 0 + storage.search(startkey, {req: req}, function(err, result) { + if (err) return next(err) + + return res.send(result) + }) + }) + //app.get('/*', function(req, res) { // proxy.request(req, res) //}) diff --git a/lib/local-storage.js b/lib/local-storage.js index 81e3460bb..53aa76a52 100644 --- a/lib/local-storage.js +++ b/lib/local-storage.js @@ -429,6 +429,33 @@ Storage.prototype.get_package = function(name, options, callback) { }) } +Storage.prototype.get_recent_packages = function(startkey, callback) { + var self = this + var i = 0 + var list = [] + fs.readdir(self.storage('').path, function(err, files) { + if (err) return callback(null, []) + + var filesL = files.length + + files.forEach(function(file) { + fs.stat(self.storage(file).path, function(err, stats) { + if (err) return callback(err) + if (stats.mtime > startkey) { + list.push({ + time: stats.mtime, + name: file + }) + } + if (++i !== filesL) { + return false + } + return callback(null, list) + }) + }) + }) +} + // // This function allows to update the package thread-safely // diff --git a/lib/storage.js b/lib/storage.js index 3c9973f59..aa82c9a05 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -320,6 +320,91 @@ Storage.prototype.get_package = function(name, options, callback) { }) } +// +// Retrieve remote and local packages more recent than {startkey} +// +// Function invokes uplink.request for npm and local.get_recent_packages for +// local ones then sum up the result in a json object +// +// Used storages: local && uplink (proxy_access) +// +Storage.prototype.search = function(startkey, options, callback) { + var self = this + var uplinks = [] + var i = 0 + + var uplinks + for (var p in self.uplinks) { + uplinks.push(p) + } + + function merge_with_local_packages(err, res, body) { + var j = 0 + + self.local.get_recent_packages(startkey, function(err, list) { + if (err) return callback(err) + + var listL = list.length + + if (!listL) return callback(null, body) + + list.forEach(function(item) { + self.local.get_package(item.name, options, function(err, data) { + if (err) return callback(err) + + var versions = utils.semver_sort(Object.keys(data.versions)) + var latest = versions[versions.length - 1] + + if (data.versions[latest]) { + body[item.name] = { + name : data.versions[latest].name, + description : data.versions[latest].description, + 'dist-tags' : { + latest: latest + }, + maintainers : data.versions[latest].maintainers || [data.versions[latest]._npmUser], + readmeFilename: data.versions[latest].readmeFilename || '', + time : { + modified: new Date(item.time).toISOString() + }, + versions : {}, + repository : data.versions[latest].repository, + keywords : data.versions[latest].keywords + } + body[item.name].versions[latest] = 'latest' + } + + if (++j !== listL) { + return false + } + + return callback(null, body) + }) + }) + }) + } + + function remote_search() { + var uplink = self.uplinks[uplinks[i]] + if (!uplink) { + return merge_with_local_packages(null, null, {}) + } + self.uplinks[uplinks[i]].request({ + uri: options.req.url, + timeout: self.uplinks[p].timeout, + json: true + }, function(err, res, body) { + if (err || Math.floor(res.statusCode / 100) > 3) { + i++ + return remote_search() + } + return merge_with_local_packages(err, res, body) + }) + } + + remote_search() +} + // function fetches package information from uplinks and synchronizes it with local data // if package is available locally, it MUST be provided in pkginfo // returns callback(err, result, uplink_errors)