Skip to content

Commit

Permalink
fix: extension manager not showing updated extensions due to local st…
Browse files Browse the repository at this point in the history
…orage out of space
  • Loading branch information
abose committed Nov 17, 2024
1 parent 9477596 commit 33a66fc
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 48 deletions.
134 changes: 88 additions & 46 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,40 @@ 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_PATH = path.normalize(
Phoenix.app.getExtensionsDirectory() + "/" + "registry_cache.json");
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();
});
});
}

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

Expand Down Expand Up @@ -223,38 +258,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 +303,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 +347,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 +356,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 +379,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
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

0 comments on commit 33a66fc

Please sign in to comment.