var _ = require('underscore'); var remoteCatalog = require('./catalog-remote.js'); var Console = require('./console.js').Console; var buildmessage = require('./buildmessage.js'); var catalog = exports; catalog.refreshFailed = undefined; catalog.triedToRefreshRecently = false; catalog.Refresh = {}; // Refresh strategy: once at program start catalog.Refresh.OnceAtStart = function (options) { var self = this; self.options = _.extend({}, options); }; catalog.Refresh.OnceAtStart.prototype.beforeCommand = function () { var self = this; if (!catalog.refreshOrWarn(self.options)) { if (self.options.ignoreErrors) { Console.debug("Failed to update package catalog, but will continue."); } else { Console.printError(catalog.refreshError); Console.error("This command requires an up-to-date package catalog. Exiting."); // Avoid circular dependency. throw new (require('./main.js').ExitWithCode)(1); } } }; // Refresh strategy: never (we don't use the package catalog) catalog.Refresh.Never = function (options) { var self = this; self.options = _.extend({}, options); }; // Refreshes the catalog. Returns true on success. // On network error, warns and returns false. // Throws other errors (ie, programming errors in the tool). // // THIS IS A HIGH-LEVEL UI COMMAND. DO NOT CALL IT FROM LOW-LEVEL CODE (ie, call // it only from main.js or command implementations). catalog.refreshOrWarn = function (options) { catalog.triedToRefreshRecently = true; try { catalog.official.refresh(options); catalog.refreshFailed = false; return true; } catch (err) { // Example errors: // Offline, with name-based host: // Network error: ws://packages.meteor.com/websocket: getaddrinfo ENOTFOUND // Offline, with IP-based host: // Network error: ws://8.8.8.8/websocket: connect ENETUNREACH // Online, bad port: // Network error: wss://packages.meteor.com:8888/websocket: connect ECONNREFUSED // Online, socket hangup: // Network error: wss://packages.meteor.com:80/websocket: socket hang up if (err.errorType !== 'DDP.ConnectionError') throw err; // XXX is throwing correct for SQLite errors too? probably. Console.warn("Unable to update package catalog (are you offline?)"); // XXX: Make this Console.debug(err) if (Console.isDebugEnabled()) { Console.printError(err); } Console.warn(); catalog.refreshFailed = true; catalog.refreshError = err; return false; } }; // Runs 'attempt'; if it fails in a way that can be fixed by refreshing the // official catalog, does that and tries again. catalog.runAndRetryWithRefreshIfHelpful = function (attempt) { buildmessage.assertInJob(); // Run `attempt` in a nested buildmessage context. var messages = buildmessage.capture(attempt); // Did it work? Great. if (! messages.hasMessages()) { return; } // Is refreshing unlikely to be useful, either because the error wasn't // related to that, or because we tried to refresh recently, or because we're // not allowed to refresh? Fail, merging the result of these errors into the // current job. if (! messages.hasMessageWithTag('refreshCouldHelp') || catalog.triedToRefreshRecently || catalog.official.offline) { buildmessage.mergeMessagesIntoCurrentJob(messages); return; } // Refresh! // XXX This is a little hacky, as it shares a bunch of code with // catalog.refreshOrWarn, which is a higher-level function that's allowed to // log. catalog.triedToRefreshRecently = true; try { catalog.official.refresh(); catalog.refreshFailed = false; } catch (err) { if (err.errorType !== 'DDP.ConnectionError') throw err; // First place the previous errors in the capture. buildmessage.mergeMessagesIntoCurrentJob(messages); // Then put an error representing this DDP error. buildmessage.enterJob( "refreshing package catalog to resolve previous errors", function () { buildmessage.error(err.message); } ); return; } // Try again, this time directly in the current buildmessage job. attempt(); }; // As a work-around for [] !== [], we use a function to check whether values are acceptable var ACCEPT_NON_EMPTY = function (result) { // null, undefined if (result === null || result === undefined) { return false; } // [] if (result.length === 0) { return false; } return true; }; // The LayeredCatalog provides a way to query multiple catalogs in a uniform way // A LayeredCatalog contains: // - a local catalog referencing the packages of the project // - a reference to the official catalog var LayeredCatalog = function (localCatalog, otherCatalog) { var self = this; self.localCatalog = localCatalog; self.otherCatalog = otherCatalog; }; _.extend(LayeredCatalog.prototype, { toString: function () { var self = this; return "LayeredCatalog []"; }, getLatestVersion: function (name) { var self = this; return self._returnFirst("getLatestVersion", arguments, ACCEPT_NON_EMPTY); }, getAllPackageNames: function () { var self = this; return _.union(self.localCatalog.getAllPackageNames(), self.otherCatalog.getAllPackageNames()); }, _returnFirst: function(f, args, validityOracle) { var self = this; var splittedArgs = Array.prototype.slice.call(args,0); var result = self.localCatalog[f].apply(self.localCatalog, splittedArgs); if (validityOracle(result)) { return result; } return self.otherCatalog[f].apply(self.otherCatalog, splittedArgs); }, getPackage: function (name) { return this._returnFirst("getPackage", arguments, ACCEPT_NON_EMPTY); }, getSortedVersions: function (name) { return this._returnFirst("getSortedVersions", arguments, ACCEPT_NON_EMPTY); }, getSortedVersionRecords: function (name) { return this._returnFirst( "getSortedVersionRecords", arguments, ACCEPT_NON_EMPTY); }, getVersion: function (name, version) { var self = this; var result = self.localCatalog.getVersion(name, version); if (!result) { if (/\+/.test(version)) { return null; } result = self.otherCatalog.getVersion(name, version); } return result; }, // As getVersion, but returns info on the latest version of the // package, or null if the package doesn't exist or has no versions. // It does not include prereleases (with dashes in the version); getLatestMainlineVersion: function (name) { var self = this; var versions = self.getSortedVersions(name); versions.reverse(); var latest = _.find(versions, function (version) { return !/-/.test(version); }); if (!latest) return null; return self.getVersion(name, latest); } }); exports.DEFAULT_TRACK = remoteCatalog.DEFAULT_TRACK; exports.official = remoteCatalog.official; // This is the catalog that's used to actually drive the constraint solver: it // contains local packages, and since local packages always beat server // packages, it doesn't contain any information about the server version of // local packages. exports.LayeredCatalog = LayeredCatalog;