Persist uninstalled plugins list

Summary: This diff changes uninstallation procedure for plugins. Instead of deleting plugin files immediately we are keeping them, but mark them as "uninstalled". This makes it possible to re-install plugins quickly in case when user clicked "delete" by mistake.

Reviewed By: mweststrate

Differential Revision: D25493479

fbshipit-source-id: 9ff29d717cdd5401c55388f24d479599579c8dd3
This commit is contained in:
Anton Nikolaev
2020-12-15 09:28:58 -08:00
committed by Facebook GitHub Bot
parent df03ccbeab
commit c3d61cc32d
9 changed files with 81 additions and 93 deletions

View File

@@ -73,37 +73,43 @@ async function handlePluginDownload(
dispatch(
pluginDownloadStarted({plugin, cancel: cancellationSource.cancel}),
);
await fs.ensureDir(targetDir);
let percentCompleted = 0;
const response = await axios.get(plugin.downloadUrl, {
adapter: axiosHttpAdapter,
cancelToken: cancellationSource.token,
responseType: 'stream',
onDownloadProgress: async (progressEvent) => {
const newPercentCompleted = !progressEvent.total
? 0
: Math.round((progressEvent.loaded * 100) / progressEvent.total);
if (newPercentCompleted - percentCompleted >= 20) {
percentCompleted = newPercentCompleted;
console.log(
`Downloading plugin "${title}" v${version} from "${downloadUrl}": ${percentCompleted}% completed (${progressEvent.loaded} from ${progressEvent.total})`,
);
}
},
});
if (response.headers['content-type'] !== 'application/octet-stream') {
throw new Error(
`Unexpected content type ${response.headers['content-type']} received from ${plugin.downloadUrl}`,
if (await fs.pathExists(dir)) {
console.log(
`Using existing files instead of downloading plugin "${title}" v${version} from "${downloadUrl}" to "${dir}"`,
);
} else {
await fs.ensureDir(targetDir);
let percentCompleted = 0;
const response = await axios.get(plugin.downloadUrl, {
adapter: axiosHttpAdapter,
cancelToken: cancellationSource.token,
responseType: 'stream',
onDownloadProgress: async (progressEvent) => {
const newPercentCompleted = !progressEvent.total
? 0
: Math.round((progressEvent.loaded * 100) / progressEvent.total);
if (newPercentCompleted - percentCompleted >= 20) {
percentCompleted = newPercentCompleted;
console.log(
`Downloading plugin "${title}" v${version} from "${downloadUrl}": ${percentCompleted}% completed (${progressEvent.loaded} from ${progressEvent.total})`,
);
}
},
});
if (response.headers['content-type'] !== 'application/octet-stream') {
throw new Error(
`Unexpected content type ${response.headers['content-type']} received from ${plugin.downloadUrl}`,
);
}
const responseStream = response.data as fs.ReadStream;
const writeStream = responseStream.pipe(
fs.createWriteStream(targetFile, {autoClose: true}),
);
await new Promise((resolve, reject) =>
writeStream.once('finish', resolve).once('error', reject),
);
await installPluginFromFile(targetFile);
}
const responseStream = response.data as fs.ReadStream;
const writeStream = responseStream.pipe(
fs.createWriteStream(targetFile, {autoClose: true}),
);
await new Promise((resolve, reject) =>
writeStream.once('finish', resolve).once('error', reject),
);
await installPluginFromFile(targetFile);
if (!store.getState().plugins.clientPlugins.has(plugin.id)) {
const pluginDefinition = requirePlugin(plugin);
dispatch(

View File

@@ -9,22 +9,20 @@
import {Store} from '../reducers/index';
import {Logger} from '../fb-interfaces/Logger';
import {
pluginFilesRemoved,
registerInstalledPlugins,
} from '../reducers/pluginManager';
import {registerInstalledPlugins} from '../reducers/pluginManager';
import {
getInstalledPlugins,
cleanupOldInstalledPluginVersions,
removePlugin,
removePlugins,
} from 'flipper-plugin-lib';
import {sideEffect} from '../utils/sideEffect';
import pMap from 'p-map';
const maxInstalledPluginVersionsToKeep = 2;
function refreshInstalledPlugins(store: Store) {
cleanupOldInstalledPluginVersions(maxInstalledPluginVersionsToKeep)
removePlugins(store.getState().pluginManager.uninstalledPlugins.values())
.then(() =>
cleanupOldInstalledPluginVersions(maxInstalledPluginVersionsToKeep),
)
.then(() => getInstalledPlugins())
.then((plugins) => store.dispatch(registerInstalledPlugins(plugins)));
}
@@ -34,26 +32,4 @@ export default (store: Store, _logger: Logger) => {
window.requestIdleCallback(() => {
refreshInstalledPlugins(store);
});
sideEffect(
store,
{
name: 'removeUninstalledPluginFiles',
throttleMs: 1000,
fireImmediately: true,
},
(state) => state.pluginManager.removedPlugins,
(removedPlugins) => {
pMap(removedPlugins, (p) => {
removePlugin(p.name)
.then(() => pluginFilesRemoved(p))
.catch((e) =>
console.error(
`Error while removing files of uninstalled plugin ${p.title}`,
e,
),
);
}).then(() => refreshInstalledPlugins(store));
},
);
};

View File

@@ -57,10 +57,13 @@ export default async (store: Store, logger: Logger) => {
defaultPluginsIndex = getDefaultPluginsIndex();
const uninstalledPlugins = store.getState().pluginManager.uninstalledPlugins;
const initialPlugins: PluginDefinition[] = filterNewestVersionOfEachPlugin(
getBundledPlugins(),
await getDynamicPlugins(),
)
.filter((p) => !uninstalledPlugins.has(p.name))
.map(reportVersion)
.filter(checkDisabled(disabledPlugins))
.filter(checkGK(gatekeepedPlugins))