/
githubmirror
/
brackets
Обзор
Документация
Войти
/
githubmirror
/
brackets
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
release-1.14.1
src/utils/UpdateNotification.js
567 строк
24 KB
Swagatam Mitra
Fix for "GetItNow" action behaviour (#14691)
09 апр 2019, 21:12
09 апр 2019, 21:12
a408323
Код
Авторство
О чём код?
/* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /** * Utilities functions for displaying update notifications * */ define(function (require, exports, module) { "use strict"; var Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), ExtensionManager = require("extensibility/ExtensionManager"), PreferencesManager = require("preferences/PreferencesManager"), NativeApp = require("utils/NativeApp"), Strings = require("strings"), UpdateDialogTemplate = require("text!htmlContent/update-dialog.html"), UpdateListTemplate = require("text!htmlContent/update-list.html"), Mustache = require("thirdparty/mustache/mustache"), HealthLogger = brackets.getModule("utils/HealthLogger"); // make sure the global brackets variable is loaded require("utils/Global"); // Private variable to hold the registered update process handler var _updateProcessHandler = null; // Private variable to hold the default update process handler var _defaultUpdateProcessHandler = function(updateInfo) { if (updateInfo) { // The first entry in the updates array has the latest download link NativeApp.openURLInDefaultBrowser(updateInfo[0].downloadURL); } }; // duration of one day in milliseconds var ONE_DAY = 1000 * 60 * 60 * 24; // duration of two minutes in milliseconds var TWO_MINUTES = 1000 * 60 * 2; // Extract current build number from package.json version field 0.0.0-0 var _buildNumber = Number(/-([0-9]+)/.exec(brackets.metadata.version)[1]); var eventNames = { AUTOUPDATE_CANCEL_CLICK: "UserClickCancelInUpdateDialog", AUTOUPDATE_UPDATENOW_CLICK: "UserClickUpdateNowInUpdateDialog", AUTOUPDATE_UPDATE_AVAILABLE_DIALOG_BOX_RENDERED: "UpdateAvailableDialogBoxRendered" }; // Init default last build number PreferencesManager.stateManager.definePreference("lastNotifiedBuildNumber", "number", 0); // Init default last info URL fetch time PreferencesManager.stateManager.definePreference("lastInfoURLFetchTime", "number", 0); // Time of last registry check for update PreferencesManager.stateManager.definePreference("lastExtensionRegistryCheckTime", "number", 0); // Data about available updates in the registry PreferencesManager.stateManager.definePreference("extensionUpdateInfo", "Array", []); // URL to load version info from. By default this is loaded no more than once a day. If // you force an update check it is always loaded. // Information on all posted builds of Brackets. This is an Array, where each element is // an Object with the following fields: // // {Number} buildNumber Number of the build // {String} versionString String representation of the build number (ie "Release 0.40") // {String} dateString Date of the build // {String} releaseNotesURL URL of the release notes for this build // {String} downloadURL URL to download this build // {Array} newFeatures Array of new features in this build. Each entry has two fields: // {String} name Name of the feature // {String} description Description of the feature // // This array must be reverse sorted by buildNumber (newest build info first) /** * @private * Flag that indicates if we've added a click handler to the update notification icon. */ var _addedClickHandler = false; /** * Construct a new version update url with the given locale. * * @param {string=} locale - optional locale, defaults to 'brackets.getLocale()' when omitted. * @param {boolean=} removeCountryPartOfLocale - optional, remove existing country information from locale 'en-gb' => 'en' * return {string} the new version update url */ function _getVersionInfoUrl(locale, removeCountryPartOfLocale) { locale = locale || brackets.getLocale(); if (removeCountryPartOfLocale) { locale = locale.substring(0, 2); } //AUTOUPDATE_PRERELEASE_BEGIN // The following code is needed for supporting Auto Update in prerelease, //and will be removed eventually for stable releases { if (locale) { if(locale.length > 2) { locale = locale.substring(0, 2); } switch(locale) { case "de": break; case "es": break; case "fr": break; case "ja": break; case "en": default: locale = "en"; } return brackets.config.update_info_url.replace("<locale>", locale); } } //AUTOUPDATE_PRERELEASE_END return brackets.config.update_info_url + '?locale=' + locale; } /** * Get a data structure that has information for all builds of Brackets. * * If force is true, the information is always fetched from _versionInfoURL. * If force is false, we try to use cached information. If more than * 24 hours have passed since the last fetch, or if cached data can't be found, * the data is fetched again. * * If new data is fetched and dontCache is false, the data is saved in preferences * for quick fetching later. * _versionInfoUrl is used for unit testing. */ function _getUpdateInformation(force, dontCache, _versionInfoUrl) { // Last time the versionInfoURL was fetched var lastInfoURLFetchTime = PreferencesManager.getViewState("lastInfoURLFetchTime"); var result = new $.Deferred(); var fetchData = false; var data; // If force is true, always fetch if (force) { fetchData = true; } // If we don't have data saved in prefs, fetch data = PreferencesManager.getViewState("updateInfo"); if (!data) { fetchData = true; } // If more than 24 hours have passed since our last fetch, fetch again if ((new Date()).getTime() > lastInfoURLFetchTime + ONE_DAY) { fetchData = true; } if (fetchData) { var lookupPromise = new $.Deferred(), localVersionInfoUrl; // If the current locale isn't "en" or "en-US", check whether we actually have a // locale-specific update notification, and fall back to "en" if not. // Note: we check for both "en" and "en-US" to watch for the general case or // country-specific English locale. The former appears default on Mac, while // the latter appears default on Windows. var locale = brackets.getLocale().toLowerCase(); if (locale !== "en" && locale !== "en-us") { localVersionInfoUrl = _versionInfoUrl || _getVersionInfoUrl(); $.ajax({ url: localVersionInfoUrl, cache: false, type: "HEAD" }).fail(function (jqXHR, status, error) { // get rid of any country information from locale and try again var tmpUrl = _getVersionInfoUrl(brackets.getLocale(), true); if (tmpUrl !== localVersionInfoUrl) { $.ajax({ url: tmpUrl, cache: false, type: "HEAD" }).fail(function (jqXHR, status, error) { localVersionInfoUrl = _getVersionInfoUrl("en"); }).done(function (jqXHR, status, error) { localVersionInfoUrl = tmpUrl; }).always(function (jqXHR, status, error) { lookupPromise.resolve(); }); } else { localVersionInfoUrl = _getVersionInfoUrl("en"); lookupPromise.resolve(); } }).done(function (jqXHR, status, error) { lookupPromise.resolve(); }); } else { localVersionInfoUrl = _versionInfoUrl || _getVersionInfoUrl("en"); lookupPromise.resolve(); } lookupPromise.done(function () { $.ajax({ url: localVersionInfoUrl, dataType: "json", cache: false }).done(function (updateInfo, textStatus, jqXHR) { if (!dontCache) { lastInfoURLFetchTime = (new Date()).getTime(); PreferencesManager.setViewState("lastInfoURLFetchTime", lastInfoURLFetchTime); PreferencesManager.setViewState("updateInfo", updateInfo); } result.resolve(updateInfo); }).fail(function (jqXHR, status, error) { // When loading data for unit tests, the error handler is // called but the responseText is valid. Try to use it here, // but *don't* save the results in prefs. if (!jqXHR.responseText) { // Text is NULL or empty string, reject(). result.reject(); return; } try { data = JSON.parse(jqXHR.responseText); result.resolve(data); } catch (e) { result.reject(); } }); }); } else { result.resolve(data); } return result.promise(); } /** * Checks whether a build is applicable to the current platform. */ function _checkBuildApplicability(buildInfo) { return !buildInfo.platforms || buildInfo.platforms[brackets.getPlatformInfo()]; } /** * Return a new array of version information that is newer than "buildNumber". * Returns null if there is no new version information. */ function _stripOldVersionInfo(versionInfo, buildNumber) { // Do a simple linear search. Since we are going in reverse-chronological order, we // should get through the search quickly. var lastIndex = 0; var len = versionInfo.length; var versionEntry; var validBuildEntries; while (lastIndex < len) { versionEntry = versionInfo[lastIndex]; if (versionEntry.buildNumber <= buildNumber) { break; } lastIndex++; } if (lastIndex > 0) { // Filter recent update entries based on applicability to current platform validBuildEntries = versionInfo.slice(0, lastIndex).filter(_checkBuildApplicability); } return validBuildEntries; } /** * Show a dialog that shows the update */ function _showUpdateNotificationDialog(updates, force) { Dialogs.showModalDialogUsingTemplate(Mustache.render(UpdateDialogTemplate, Strings)) .done(function (id) { if (id === Dialogs.DIALOG_BTN_DOWNLOAD) { HealthLogger.sendAnalyticsData( eventNames.AUTOUPDATE_UPDATENOW_CLICK, "autoUpdate", "updateNotification", "updateNow", "click" ); handleUpdateProcess(updates); } else { HealthLogger.sendAnalyticsData( eventNames.AUTOUPDATE_CANCEL_CLICK, "autoUpdate", "updateNotification", "cancel", "click" ); } }); // Populate the update data var $dlg = $(".update-dialog.instance"), $updateList = $dlg.find(".update-info"), subTypeString = force ? "userAction" : "auto"; // Make the update notification icon clickable again _addedClickHandler = false; updates.Strings = Strings; $updateList.html(Mustache.render(UpdateListTemplate, updates)); HealthLogger.sendAnalyticsData( eventNames.AUTOUPDATE_UPDATE_AVAILABLE_DIALOG_BOX_RENDERED, "autoUpdate", "updateNotification", "render", subTypeString ); } /** * Calculate state of notification everytime registries are downloaded - no matter who triggered the download */ function _onRegistryDownloaded() { var availableUpdates = ExtensionManager.getAvailableUpdates(); PreferencesManager.setViewState("extensionUpdateInfo", availableUpdates); PreferencesManager.setViewState("lastExtensionRegistryCheckTime", (new Date()).getTime()); $("#toolbar-extension-manager").toggleClass("updatesAvailable", availableUpdates.length > 0); } /** * Every 24 hours downloads registry information to check for update, but only if the registry download * wasn't triggered by another action (like opening extension manager) * If there isn't 24 hours elapsed from the last download, use cached information from last download * to determine state of the update notification. */ function checkForExtensionsUpdate() { var lastExtensionRegistryCheckTime = PreferencesManager.getViewState("lastExtensionRegistryCheckTime"), timeOfNextCheck = lastExtensionRegistryCheckTime + ONE_DAY, currentTime = (new Date()).getTime(); // update icon according to previously saved information var availableUpdates = PreferencesManager.getViewState("extensionUpdateInfo"); availableUpdates = ExtensionManager.cleanAvailableUpdates(availableUpdates); $("#toolbar-extension-manager").toggleClass("updatesAvailable", availableUpdates.length > 0); if (availableUpdates.length === 0) { // icon is gray, no updates available if (currentTime > timeOfNextCheck) { // downloadRegistry, will be resolved in _onRegistryDownloaded ExtensionManager.downloadRegistry().done(function () { // schedule another check in 24 hours + 2 minutes setTimeout(checkForExtensionsUpdate, ONE_DAY + TWO_MINUTES); }); } else { // schedule the download of the registry in appropriate time setTimeout(checkForExtensionsUpdate, (timeOfNextCheck - currentTime) + TWO_MINUTES); } } } /** * * @param {boolean} enable when false Update Notification Icon in TollBar will grayed and Click will be blocked. when true icon will be green and clickable. */ function enableUpdateNotificationIcon(enable) { var $updateNotification = $("#update-notification"); _addedClickHandler = !enable; $updateNotification.toggleClass("update-in-progress", !enable); } /** * Check for updates. If "force" is true, update notification dialogs are always displayed * (if an update is available). If "force" is false, the update notification is only * displayed for newly available updates. * * If an update is available, show the "update available" notification icon in the title bar. * * @param {boolean} force If true, always show the notification dialog. * @param {Object} _testValues This should only be used for testing purposes. See comments for details. * @return {$.Promise} jQuery Promise object that is resolved or rejected after the update check is complete. */ function checkForUpdate(force, _testValues) { // This is the last version we notified the user about. If checkForUpdate() // is called with "false", only show the update notification dialog if there // is an update newer than this one. This value is saved in preferences. var lastNotifiedBuildNumber = PreferencesManager.getViewState("lastNotifiedBuildNumber"); // The second param, if non-null, is an Object containing value overrides. Values // in the object temporarily override the local values. This should *only* be used for testing. // If any overrides are set, permanent changes are not made (including showing // the update notification icon and saving prefs). var oldValues; var usingOverrides = false; // true if any of the values are overridden. var result = new $.Deferred(); var versionInfoUrl; if (_testValues) { oldValues = {}; if (_testValues.hasOwnProperty("_buildNumber")) { oldValues._buildNumber = _buildNumber; _buildNumber = _testValues._buildNumber; usingOverrides = true; } if (_testValues.hasOwnProperty("lastNotifiedBuildNumber")) { oldValues.lastNotifiedBuildNumber = lastNotifiedBuildNumber; lastNotifiedBuildNumber = _testValues.lastNotifiedBuildNumber; usingOverrides = true; } if (_testValues.hasOwnProperty("_versionInfoURL")) { versionInfoUrl = _testValues._versionInfoURL; usingOverrides = true; } } _getUpdateInformation(force || usingOverrides, usingOverrides, versionInfoUrl) .done(function (versionInfo) { // Get all available updates var allUpdates = _stripOldVersionInfo(versionInfo, _buildNumber); // When running directly from GitHub source (as opposed to // an installed build), _buildNumber is 0. In this case, if the // test is not forced, don't show the update notification icon or // dialog. if (_buildNumber === 0 && !force) { result.resolve(); return; } if (allUpdates && allUpdates.length > 0) { // Always show the "update available" icon if any updates are available var $updateNotification = $("#update-notification"); $updateNotification.css("display", "block"); $updateNotification.on("click", function () { // Block the click until the Notification Dialog opens if (!_addedClickHandler) { _addedClickHandler = true; checkForUpdate(true); } }); // Only show the update dialog if force = true, or if the user hasn't been // alerted of this update if (force || allUpdates[0].buildNumber > lastNotifiedBuildNumber) { _showUpdateNotificationDialog(allUpdates, force); // Update prefs with the last notified build number lastNotifiedBuildNumber = allUpdates[0].buildNumber; // Don't save prefs is we have overridden values if (!usingOverrides) { PreferencesManager.setViewState("lastNotifiedBuildNumber", lastNotifiedBuildNumber); } } } else if (force) { // No updates are available. If force == true, let the user know. Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.NO_UPDATE_TITLE, Strings.NO_UPDATE_MESSAGE ); } if (oldValues) { if (oldValues.hasOwnProperty("_buildNumber")) { _buildNumber = oldValues._buildNumber; } if (oldValues.hasOwnProperty("lastNotifiedBuildNumber")) { lastNotifiedBuildNumber = oldValues.lastNotifiedBuildNumber; } } result.resolve(); }) .fail(function () { // Error fetching the update data. If this is a forced check, alert the user if (force) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.ERROR_FETCHING_UPDATE_INFO_TITLE, Strings.ERROR_FETCHING_UPDATE_INFO_MSG ); } result.reject(); }); return result.promise(); } /** * Handles the update process * @param {Array} updates - array object containing info of updates */ function handleUpdateProcess(updates) { var handler = _updateProcessHandler || _defaultUpdateProcessHandler; var initSuccess = handler(updates); if (_updateProcessHandler && !initSuccess) { // Give a chance to default handler in case // the auot update mechanism has failed. _defaultUpdateProcessHandler(updates); } } /** * Launches both check for Brackets update and check for installed extensions update */ function launchAutomaticUpdate() { // launch immediately and then every 24 hours + 2 minutes checkForUpdate(); checkForExtensionsUpdate(); window.setInterval(checkForUpdate, ONE_DAY + TWO_MINUTES); } /** * Registers the update process handler function * @param {function} handler - function for update process handler */ function registerUpdateHandler(handler) { _updateProcessHandler = handler; } /** * Utility function to reset back to the default update handler */ function resetToDefaultUpdateHandler() { _updateProcessHandler = null; } // Events listeners ExtensionManager.on("registryDownload", _onRegistryDownloaded); // Define public API exports.registerUpdateHandler = registerUpdateHandler; exports.resetToDefaultUpdateHandler = resetToDefaultUpdateHandler; exports.launchAutomaticUpdate = launchAutomaticUpdate; exports.checkForUpdate = checkForUpdate; exports.enableUpdateNotificationIcon = enableUpdateNotificationIcon; });