Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 98 additions & 47 deletions src/extensibility/ExtensionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*/

/*jslint regexp: true */
/*global Phoenix*/
/*global path, logger*/
/*unittests: ExtensionManager*/

/**
Expand All @@ -44,6 +44,7 @@ define(function (require, exports, module) {
ExtensionLoader = require("utils/ExtensionLoader"),
ExtensionUtils = require("utils/ExtensionUtils"),
FileSystem = require("filesystem/FileSystem"),
FileUtils = require("file/FileUtils"),
PreferencesManager = require("preferences/PreferencesManager"),
Strings = require("strings"),
StringUtils = require("utils/StringUtils"),
Expand All @@ -54,6 +55,47 @@ define(function (require, exports, module) {
"test_extension_registry" : "extension_registry",
EXTENSION_REGISTRY_LOCAL_STORAGE_VERSION_KEY = Phoenix.isTestWindow ?
"test_extension_registry_version" : "extension_registry_version";

// earlier, we used to cache the full uncompressed registry in ls which has a usual size limit of 5mb, and the
// registry takes a few MB. So we moved this storage and this will clear local storage on any existing installs on
// next update. This migration code can be removed after July 2025(6 Months).
localStorage.removeItem(EXTENSION_REGISTRY_LOCAL_STORAGE_KEY);

const REGISTRY_CACHE_FILE = Phoenix.isTestWindow ?
"test_registry_cache.json" : "registry_cache.json";
const REGISTRY_CACHE_PATH = path.normalize(
Phoenix.app.getExtensionsDirectory() + "/" + REGISTRY_CACHE_FILE);
function _getCachedRegistry() {
// never rejects
return new Promise((resolve) => {
const registryFile = FileSystem.getFileForPath(REGISTRY_CACHE_PATH);
FileUtils.readAsText(registryFile)
.done(resolve)
.fail(function (err) {
console.error(`Registry cache not found ${REGISTRY_CACHE_PATH}`, err);
resolve(null);
});
});
}

function _putCachedRegistry(registryFileText) {
// never rejects
return new Promise((resolve) => {
const registryFile = FileSystem.getFileForPath(REGISTRY_CACHE_PATH);
FileUtils.writeText(registryFile, registryFileText)
.done(resolve)
.fail(function (err) {
logger.reportError(err, `Registry cache write error ${REGISTRY_CACHE_PATH}`);
resolve();
});
});
}

function _removeCachedRegistry() {
const registryFile = FileSystem.getFileForPath(REGISTRY_CACHE_PATH);
return registryFile.unlinkAsync();
}

// semver.browser is an AMD-compatible module
var semver = require("thirdparty/semver.browser");

Expand Down Expand Up @@ -223,38 +265,42 @@ define(function (require, exports, module) {
if(registryVersion.version !== parseInt(currentRegistryVersion)){
resolve(registryVersion.version);
} else {
const registryJson = localStorage.getItem(EXTENSION_REGISTRY_LOCAL_STORAGE_KEY);
if(!registryJson) {
resolve(registryVersion.version);
// if we dont have anything, best to atlest try to fetch the registry now.
return;
}
reject();
_getCachedRegistry() // never rejects
.then(registryJson => {
if(!registryJson) {
resolve(registryVersion.version);
// if we dont have anything, best to atlest try to fetch the registry now.
return;
}
reject();
});
}
})
.fail(function (err) {
console.error("error Fetching Extension Registry version", err);
const registryJson = localStorage.getItem(EXTENSION_REGISTRY_LOCAL_STORAGE_KEY);
if(!registryJson) {
resolve(1); // if we dont have anything, best to atlest try to fetch the registry now.
return;
}
reject();
_getCachedRegistry() // never rejects
.then(registryJson => {
if(!registryJson) {
resolve(1); // if we dont have anything, best to atlest try to fetch the registry now.
return;
}
reject();
});
});
});
}

function _patchDownloadCounts() {
let registryJson = localStorage.getItem(EXTENSION_REGISTRY_LOCAL_STORAGE_KEY);
async function _patchDownloadCounts() {
let registryJson = await _getCachedRegistry();
if(!registryJson){
return;
}
$.ajax({
url: brackets.config.extension_registry_popularity,
dataType: "json",
cache: false
}).done(function (popularity) {
registryJson = localStorage.getItem(EXTENSION_REGISTRY_LOCAL_STORAGE_KEY);
}).done(async function (popularity) {
registryJson = await _getCachedRegistry();
let registry = JSON.parse(registryJson);
for(let key of Object.keys(popularity)){
if(registry[key]) {
Expand All @@ -264,7 +310,7 @@ define(function (require, exports, module) {
|| null;
}
}
localStorage.setItem(EXTENSION_REGISTRY_LOCAL_STORAGE_KEY, JSON.stringify(registry));
_putCachedRegistry(JSON.stringify(registry));
});
}

Expand Down Expand Up @@ -308,6 +354,7 @@ define(function (require, exports, module) {
pendingDownloadRegistry = new $.Deferred();

function _updateRegistry(newVersion) {
console.log("downloading extension registry: ", newVersion, brackets.config.extension_registry);
$.ajax({
url: brackets.config.extension_registry,
dataType: "json",
Expand All @@ -316,20 +363,20 @@ define(function (require, exports, module) {
.done(function (registry) {
registry = _filterIncompatibleEntries(registry);
localStorage.setItem(EXTENSION_REGISTRY_LOCAL_STORAGE_VERSION_KEY, newVersion);
localStorage.setItem(EXTENSION_REGISTRY_LOCAL_STORAGE_KEY, JSON.stringify(registry));
if(!pendingDownloadRegistry.alreadyResolvedFromCache){
_populateExtensions(registry);
pendingDownloadRegistry.resolve();
}
_putCachedRegistry(JSON.stringify(registry)).then(()=>{
if(!pendingDownloadRegistry.alreadyResolvedFromCache){
_populateExtensions(registry);
pendingDownloadRegistry.resolve();
}
}).finally(()=>{
pendingDownloadRegistry = null;
});
})
.fail(function (err) {
console.error("error Fetching Extension Registry", err);
if(!pendingDownloadRegistry.alreadyResolvedFromCache){
pendingDownloadRegistry.reject();
}
})
.always(function () {
// Make sure to clean up the pending registry so that new requests can be made.
pendingDownloadRegistry = null;
});
}
Expand All @@ -339,26 +386,28 @@ define(function (require, exports, module) {
return pendingDownloadRegistry.promise();
}

const registryJson = localStorage.getItem(EXTENSION_REGISTRY_LOCAL_STORAGE_KEY);
if(registryJson) {
// we always immediately but after the promise chain is setup after function return (some bug sigh)
// resolve for ui responsiveness and then check for updates.
setTimeout(()=>{
Metrics.countEvent(Metrics.EVENT_TYPE.EXTENSIONS, "registry", "cachedUse");
let registry = JSON.parse(registryJson);
registry = _filterIncompatibleEntries(registry);
_populateExtensions(registry);
pendingDownloadRegistry.resolve();
}, 0);
pendingDownloadRegistry.alreadyResolvedFromCache = true;
}
// check for latest updates even if we have cache
_shouldUpdateExtensionRegistry()
.then(_updateRegistry)
.catch(()=>{
pendingDownloadRegistry = null;
_getCachedRegistry() // never rejects
.then(registryJson => {
if(registryJson) {
// we always immediately but after the promise chain is setup after function return (some bug sigh)
// resolve for ui responsiveness and then check for updates.
setTimeout(()=>{
Metrics.countEvent(Metrics.EVENT_TYPE.EXTENSIONS, "registry", "cachedUse");
let registry = JSON.parse(registryJson);
registry = _filterIncompatibleEntries(registry);
_populateExtensions(registry);
pendingDownloadRegistry.resolve();
}, 0);
pendingDownloadRegistry.alreadyResolvedFromCache = true;
}
// check for latest updates even if we have cache
_shouldUpdateExtensionRegistry()
.then(_updateRegistry)
.catch(()=>{
console.log("Registry update skipped");
});
_patchDownloadCounts();
});
_patchDownloadCounts();

return pendingDownloadRegistry.promise();
}
Expand Down Expand Up @@ -922,6 +971,8 @@ define(function (require, exports, module) {
exports._reset = _reset;
exports._setExtensions = _setExtensions;
if(Phoenix.isTestWindow){
exports.EXTENSION_REGISTRY_LOCAL_STORAGE_KEY = EXTENSION_REGISTRY_LOCAL_STORAGE_KEY;
exports._getCachedRegistry = _getCachedRegistry;
exports._putCachedRegistry = _putCachedRegistry;
exports._removeCachedRegistry = _removeCachedRegistry;
}
});
4 changes: 2 additions & 2 deletions src/loggerSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@
},
/**
* By default all uncaught exceptions and promise rejections are sent to logger utility. But in some cases
* you may want to sent handled errors too if it is critical. use this function to report those
* @param {Error} error
* you may want to log error without having an error object with you.
*
* @param {string} [message] optional message
*/
reportErrorMessage: function (message) {
Expand Down
8 changes: 4 additions & 4 deletions test/spec/ExtensionManager-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,9 @@ define(function (require, exports, module) {
});

it("should registry update cache registry locally", async function () {
localStorage.removeItem(ExtensionManager.EXTENSION_REGISTRY_LOCAL_STORAGE_KEY);
await ExtensionManager._removeCachedRegistry();
await awaitsForDone(ExtensionManager.downloadRegistry(true), "fetching registry");
expect(localStorage.getItem(ExtensionManager.EXTENSION_REGISTRY_LOCAL_STORAGE_KEY)).not.toBeNull();
expect(await ExtensionManager._getCachedRegistry()).not.toBeNull();
});

it("should fail if it can't access the registry", async function () {
Expand Down Expand Up @@ -419,7 +419,7 @@ define(function (require, exports, module) {
var model;

beforeEach(async function () {
localStorage.removeItem(ExtensionManager.EXTENSION_REGISTRY_LOCAL_STORAGE_KEY);
await ExtensionManager._removeCachedRegistry();
mockRegistry = JSON.parse(mockRegistryForSearch);
model = new ExtensionManagerViewModel.RegistryViewModel();
await awaitsForDone(model.initialize(), "model initialization");
Expand Down Expand Up @@ -515,7 +515,7 @@ define(function (require, exports, module) {
var model;

beforeEach(async function () {
localStorage.removeItem(ExtensionManager.EXTENSION_REGISTRY_LOCAL_STORAGE_KEY);
await ExtensionManager._removeCachedRegistry();
mockRegistry = JSON.parse(mockRegistryThemesText);
model = new ExtensionManagerViewModel.ThemesViewModel();
await awaitsForDone(model.initialize(), "model initialization");
Expand Down
Loading