1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Remove legacy IPC usage

- From extension installation state store

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-02-16 08:31:51 -05:00
parent edafcc916c
commit 2164b4b010
29 changed files with 436 additions and 399 deletions

View File

@ -7,7 +7,7 @@ import type { MessageChannel } from "./message-channel-listener-injection-token"
export interface SendMessageToChannel {
(channel: MessageChannel<void>): void;
<Message>(channel: MessageChannel<Message>, message: Message): void;
<Channel extends MessageChannel<unknown>>(channel: Channel, message: Channel extends MessageChannel<infer Message> ? Message : never): void;
}
export type MessageChannelSender<Channel> = Channel extends MessageChannel<void | undefined>

View File

@ -14,6 +14,7 @@ interface Iterator<T> extends Iterable<T> {
flatMap<U>(fn: (val: T) => U[]): Iterator<U>;
concat(src2: IterableIterator<T>): Iterator<T>;
join(sep?: string): string;
count(): number;
}
export function chain<T>(src: IterableIterator<T>): Iterator<T> {
@ -26,6 +27,7 @@ export function chain<T>(src: IterableIterator<T>): Iterator<T> {
join: (sep) => join(src, sep),
collect: (fn) => fn(src),
concat: (src2) => chain(concat(src, src2)),
count: () => count(src),
[Symbol.iterator]: () => src,
};
}
@ -246,3 +248,14 @@ export function* concat<T>(...sources: IterableIterator<T>[]): IterableIterator<
}
}
}
export function count(src: Iterable<unknown>): number {
let i = 0;
for (const x of src) {
void x;
i += 1;
}
return i;
}

View File

@ -1,16 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import loggerInjectable from "../../common/logger.injectable";
import { ExtensionInstallationStateStore } from "./extension-installation-state-store";
const extensionInstallationStateStoreInjectable = getInjectable({
id: "extension-installation-state-store",
instantiate: (di) => new ExtensionInstallationStateStore({
logger: di.inject(loggerInjectable),
}),
});
export default extensionInstallationStateStoreInjectable;

View File

@ -1,247 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { action, computed, observable } from "mobx";
import { disposer } from "../../renderer/utils";
import type { ExtendableDisposer } from "../../renderer/utils";
import * as uuid from "uuid";
import { broadcastMessage } from "../../common/ipc";
import { ipcRenderer } from "electron";
import type { Logger } from "../../common/logger";
export enum ExtensionInstallationState {
INSTALLING = "installing",
UNINSTALLING = "uninstalling",
IDLE = "idle",
}
interface Dependencies {
readonly logger: Logger;
}
const Prefix = "[ExtensionInstallationStore]";
const installingFromMainChannel = "extension-installation-state-store:install";
const clearInstallingFromMainChannel = "extension-installation-state-store:clear-install";
export class ExtensionInstallationStateStore {
private readonly preInstallIds = observable.set<string>();
private readonly uninstallingExtensions = observable.set<string>();
private readonly installingExtensions = observable.set<string>();
constructor(private readonly dependencies: Dependencies) {}
bindIpcListeners = () => {
ipcRenderer
.on(installingFromMainChannel, (event, extId) => {
this.setInstalling(extId);
})
.on(clearInstallingFromMainChannel, (event, extId) => {
this.clearInstalling(extId);
});
};
/**
* Strictly transitions an extension from not installing to installing
* @param extId the ID of the extension
* @throws if state is not IDLE
*/
@action setInstalling = (extId: string): void => {
this.dependencies.logger.debug(`${Prefix}: trying to set ${extId} as installing`);
const curState = this.getInstallationState(extId);
if (curState !== ExtensionInstallationState.IDLE) {
throw new Error(
`${Prefix}: cannot set ${extId} as installing. Is currently ${curState}.`,
);
}
this.installingExtensions.add(extId);
};
/**
* Broadcasts that an extension is being installed by the main process
* @param extId the ID of the extension
*/
setInstallingFromMain = (extId: string): void => {
broadcastMessage(installingFromMainChannel, extId);
};
/**
* Broadcasts that an extension is no longer being installed by the main process
* @param extId the ID of the extension
*/
clearInstallingFromMain = (extId: string): void => {
broadcastMessage(clearInstallingFromMainChannel, extId);
};
/**
* Marks the start of a pre-install phase of an extension installation. The
* part of the installation before the tarball has been unpacked and the ID
* determined.
* @returns a disposer which should be called to mark the end of the install phase
*/
@action startPreInstall = (): ExtendableDisposer => {
const preInstallStepId = uuid.v4();
this.dependencies.logger.debug(
`${Prefix}: starting a new preinstall phase: ${preInstallStepId}`,
);
this.preInstallIds.add(preInstallStepId);
return disposer(() => {
this.preInstallIds.delete(preInstallStepId);
this.dependencies.logger.debug(`${Prefix}: ending a preinstall phase: ${preInstallStepId}`);
});
};
/**
* Strictly transitions an extension from not uninstalling to uninstalling
* @param extId the ID of the extension
* @throws if state is not IDLE
*/
@action setUninstalling = (extId: string): void => {
this.dependencies.logger.debug(`${Prefix}: trying to set ${extId} as uninstalling`);
const curState = this.getInstallationState(extId);
if (curState !== ExtensionInstallationState.IDLE) {
throw new Error(
`${Prefix}: cannot set ${extId} as uninstalling. Is currently ${curState}.`,
);
}
this.uninstallingExtensions.add(extId);
};
/**
* Strictly clears the INSTALLING state of an extension
* @param extId The ID of the extension
* @throws if state is not INSTALLING
*/
@action clearInstalling = (extId: string): void => {
this.dependencies.logger.debug(`${Prefix}: trying to clear ${extId} as installing`);
const curState = this.getInstallationState(extId);
switch (curState) {
case ExtensionInstallationState.INSTALLING:
return void this.installingExtensions.delete(extId);
default:
throw new Error(
`${Prefix}: cannot clear INSTALLING state for ${extId}, it is currently ${curState}`,
);
}
};
/**
* Strictly clears the UNINSTALLING state of an extension
* @param extId The ID of the extension
* @throws if state is not UNINSTALLING
*/
@action clearUninstalling = (extId: string): void => {
this.dependencies.logger.debug(`${Prefix}: trying to clear ${extId} as uninstalling`);
const curState = this.getInstallationState(extId);
switch (curState) {
case ExtensionInstallationState.UNINSTALLING:
return void this.uninstallingExtensions.delete(extId);
default:
throw new Error(
`${Prefix}: cannot clear UNINSTALLING state for ${extId}, it is currently ${curState}`,
);
}
};
/**
* Returns the current state of the extension. IDLE is default value.
* @param extId The ID of the extension
*/
getInstallationState = (extId: string): ExtensionInstallationState => {
if (this.installingExtensions.has(extId)) {
return ExtensionInstallationState.INSTALLING;
}
if (this.uninstallingExtensions.has(extId)) {
return ExtensionInstallationState.UNINSTALLING;
}
return ExtensionInstallationState.IDLE;
};
/**
* Returns true if the extension is currently INSTALLING
* @param extId The ID of the extension
*/
isExtensionInstalling = (extId: string): boolean =>
this.getInstallationState(extId) === ExtensionInstallationState.INSTALLING;
/**
* Returns true if the extension is currently UNINSTALLING
* @param extId The ID of the extension
*/
isExtensionUninstalling = (extId: string): boolean =>
this.getInstallationState(extId) ===
ExtensionInstallationState.UNINSTALLING;
/**
* Returns true if the extension is currently IDLE
* @param extId The ID of the extension
*/
isExtensionIdle = (extId: string): boolean =>
this.getInstallationState(extId) === ExtensionInstallationState.IDLE;
/**
* The current number of extensions installing
*/
@computed get installing(): number {
return this.installingExtensions.size;
}
/**
* The current number of extensions uninstalling
*/
get uninstalling(): number {
return this.uninstallingExtensions.size;
}
/**
* If there is at least one extension currently installing
*/
get anyInstalling(): boolean {
return this.installing > 0;
}
/**
* If there is at least one extension currently uninstalling
*/
get anyUninstalling(): boolean {
return this.uninstalling > 0;
}
/**
* The current number of extensions preinstalling
*/
get preinstalling(): number {
return this.preInstallIds.size;
}
/**
* If there is at least one extension currently downloading
*/
get anyPreinstalling(): boolean {
return this.preinstalling > 0;
}
/**
* If there is at least one installing or preinstalling step taking place
*/
get anyPreInstallingOrInstalling(): boolean {
return this.anyInstalling || this.anyPreinstalling;
}
}

View File

@ -8,8 +8,9 @@ import getDirnameOfPathInjectable from "../../../../common/path/get-dirname.inje
import getRelativePathInjectable from "../../../../common/path/get-relative-path.injectable";
import fileSystemSeparatorInjectable from "../../../../common/path/separator.injectable";
import { manifestFilename } from "../../../../common/vars";
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import installedExtensionsInjectable from "../../common/installed-extensions.injectable";
import setExtensionAsInstallingInjectable from "../../installation-states/main/set-as-installing.injectable";
import clearExtensionAsInstallingInjectable from "../../installation-states/renderer/clear-as-installing.injectable";
import installExtensionPackageInjectable from "../common/install-package.injectable";
import localExtensionsDirectoryPathInjectable from "../common/local-extensions-directory-path.injectable";
import extensionDiscoveryLoggerInjectable from "../common/logger.injectable";
@ -22,12 +23,13 @@ const extensionFileAddedInjectable = getInjectable({
const localExtensionsDirectoryPath = di.inject(localExtensionsDirectoryPathInjectable);
const fileSystemSeparator = di.inject(fileSystemSeparatorInjectable);
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
const loadUserExtensionFromFolder = di.inject(loadUserExtensionFromFolderInjectable);
const installExtensionPackage = di.inject(installExtensionPackageInjectable);
const installedExtensions = di.inject(installedExtensionsInjectable);
const logger = di.inject(extensionDiscoveryLoggerInjectable);
const setExtensionAsInstalling = di.inject(setExtensionAsInstallingInjectable);
const clearExtensionAsInstalling = di.inject(clearExtensionAsInstallingInjectable);
return async (manifestPath: string): Promise<void> => {
// e.g. "foo/package.json"
@ -40,7 +42,7 @@ const extensionFileAddedInjectable = getInjectable({
if (getBasenameOfPath(manifestPath) === manifestFilename && isUnderLocalFolderPath) {
try {
extensionInstallationStateStore.setInstallingFromMain(manifestPath);
setExtensionAsInstalling(manifestPath);
const absPath = getDirnameOfPath(manifestPath);
// this.loadExtensionFromPath updates this.packagesJson
@ -56,7 +58,7 @@ const extensionFileAddedInjectable = getInjectable({
} catch (error) {
logger.error(`failed to add extension: ${error}`, { error });
} finally {
extensionInstallationStateStore.clearInstallingFromMain(manifestPath);
clearExtensionAsInstalling(manifestPath);
}
}
};

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { MessageChannel } from "../../../../common/utils/channel/message-channel-listener-injection-token";
import type { LensExtensionId } from "../../../../extensions/lens-extension";
export interface ExtensionInstallPhaseData {
id: LensExtensionId;
phase: "installing" | "clear-installing";
}
export const setExtensionInstallPhaseChannel: MessageChannel<ExtensionInstallPhaseData> = {
id: "set-extension-install-phase",
};

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { LensExtensionId } from "../../../../extensions/lens-extension";
export type SetExtensionAsInstalling = (id: LensExtensionId) => void;
export const setExtensionAsInstallingInjectionToken = getInjectionToken<SetExtensionAsInstalling>({
id: "set-extension-as-installing-token",
});
export type ClearExtensionAsInstalling = (id: LensExtensionId) => void;
export const clearExtensionAsInstallingInjectionToken = getInjectionToken<ClearExtensionAsInstalling>({
id: "clear-extension-as-installing-token",
});

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { sendMessageToChannelInjectionToken } from "../../../../common/utils/channel/message-to-channel-injection-token";
import { setExtensionInstallPhaseChannel } from "../common/channels";
import { clearExtensionAsInstallingInjectionToken } from "../common/tokens";
const clearExtensionAsInstallingInjectable = getInjectable({
id: "clear-extension-as-installing",
instantiate: (di) => {
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
return (id) => sendMessageToChannel(setExtensionInstallPhaseChannel, {
id,
phase: "clear-installing",
});
},
injectionToken: clearExtensionAsInstallingInjectionToken,
});
export default clearExtensionAsInstallingInjectable;

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { sendMessageToChannelInjectionToken } from "../../../../common/utils/channel/message-to-channel-injection-token";
import { setExtensionInstallPhaseChannel } from "../common/channels";
import { setExtensionAsInstallingInjectionToken } from "../common/tokens";
const setExtensionAsInstallingInjectable = getInjectable({
id: "set-extension-as-installing",
instantiate: (di) => {
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
return (id) => sendMessageToChannel(setExtensionInstallPhaseChannel, {
id,
phase: "installing",
});
},
injectionToken: setExtensionAsInstallingInjectionToken,
});
export default setExtensionAsInstallingInjectable;

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { clearExtensionAsInstallingInjectionToken } from "../common/tokens";
import extensionInstallationStatesInjectable from "./states.injectable";
const clearExtensionAsInstallingInjectable = getInjectable({
id: "clear-extension-as-installing",
instantiate: (di) => {
const states = di.inject(extensionInstallationStatesInjectable);
return (id) => states.delete(id);
},
injectionToken: clearExtensionAsInstallingInjectionToken,
});
export default clearExtensionAsInstallingInjectable;

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { LensExtensionId } from "../../../../extensions/lens-extension";
import type { InstallationState } from "./states.injectable";
import extensionInstallationStatesInjectable from "./states.injectable";
export type GetExtensionInstallationPhase = (id: LensExtensionId) => InstallationState;
const getExtensionInstallationPhaseInjectable = getInjectable({
id: "get-extension-installation-phase",
instantiate: (di): GetExtensionInstallationPhase => {
const states = di.inject(extensionInstallationStatesInjectable);
return (id) => states.get(id) ?? "idle";
},
});
export default getExtensionInstallationPhaseInjectable;

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import { iter } from "../../../../common/utils";
import extensionInstallationStatesInjectable from "./states.injectable";
const extensionsInstallingCountInjectable = getInjectable({
id: "extensions-installing-count",
instantiate: (di) => {
const states = di.inject(extensionInstallationStatesInjectable);
return computed(() => (
iter.chain(states.entries())
.filter(([, state]) => state === "installing")
.count()
));
},
});
export default extensionsInstallingCountInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import preinstallingPhasesInjectable from "./preinstalling.injectable";
const extensionsPreinstallingCountInjectable = getInjectable({
id: "extensions-preinstalling-count",
instantiate: (di) => {
const preinstallingPhases = di.inject(preinstallingPhasesInjectable);
return computed(() => preinstallingPhases.size);
},
});
export default extensionsPreinstallingCountInjectable;

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { observable } from "mobx";
const preinstallingPhasesInjectable = getInjectable({
id: "preinstalling-phases",
instantiate: () => observable.set<string>(),
});
export default preinstallingPhasesInjectable;

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { setExtensionAsInstallingInjectionToken } from "../common/tokens";
import extensionInstallationStatesInjectable from "./states.injectable";
const setExtensionAsInstallingInjectable = getInjectable({
id: "set-extension-as-installing",
instantiate: (di) => {
const states = di.inject(extensionInstallationStatesInjectable);
return (id) => states.set(id, "installing");
},
injectionToken: setExtensionAsInstallingInjectionToken,
});
export default setExtensionAsInstallingInjectable;

View File

@ -0,0 +1,21 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import type { LensExtensionId } from "../../../../extensions/lens-extension";
import extensionInstallationStatesInjectable from "./states.injectable";
export type SetExtensionAsUninstalling = (id: LensExtensionId) => void;
const setExtensionAsUninstallingInjectable = getInjectable({
id: "set-extension-as-uninstalling",
instantiate: (di): SetExtensionAsUninstalling => {
const states = di.inject(extensionInstallationStatesInjectable);
return (id) => states.set(id, "uninstalling");
},
});
export default setExtensionAsUninstallingInjectable;

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { action } from "mobx";
import type { Disposer } from "../../../../common/utils";
import preinstallingPhasesInjectable from "./preinstalling.injectable";
import * as uuid from "uuid";
export type StartPreInstallPhase = () => Disposer;
const startPreInstallPhaseInjectable = getInjectable({
id: "start-pre-install-phase",
instantiate: (di): StartPreInstallPhase => {
const preinstalling = di.inject(preinstallingPhasesInjectable);
return action(() => {
const preInstallStepId = uuid.v4();
preinstalling.add(preInstallStepId);
return () => preinstalling.delete(preInstallStepId);
});
},
});
export default startPreInstallPhaseInjectable;

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { observable } from "mobx";
import type { LensExtensionId } from "../../../../extensions/lens-extension";
export type InstallationState = ActiveInstallationState | "idle";
export type ActiveInstallationState = "installing" | "uninstalling";
const extensionInstallationStatesInjectable = getInjectable({
id: "extension-installation-states",
instantiate: () => observable.map<LensExtensionId, InstallationState>(),
});
export default extensionInstallationStatesInjectable;

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { computed } from "mobx";
import { iter } from "../../../../common/utils";
import extensionInstallationStatesInjectable from "./states.injectable";
const extensionsUninstallingCountInjectable = getInjectable({
id: "extensions-uninstalling-count",
instantiate: (di) => {
const states = di.inject(extensionInstallationStatesInjectable);
return computed(() => (
iter.chain(states.entries())
.filter(([, state]) => state === "uninstalling")
.count()
));
},
});
export default extensionsUninstallingCountInjectable;

View File

@ -10,7 +10,6 @@ import { render, unmountComponentAtNode } from "react-dom";
import { DefaultProps } from "./mui-base-theme";
import { DiContextProvider } from "@ogre-tools/injectable-react";
import type { DiContainer } from "@ogre-tools/injectable";
import extensionInstallationStateStoreInjectable from "../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import initRootFrameInjectable from "./frames/root-frame/init-root-frame.injectable";
import initClusterFrameInjectable from "./frames/cluster-frame/init-cluster-frame.injectable";
import { Router } from "react-router";
@ -27,8 +26,6 @@ export async function bootstrap(di: DiContainer) {
assert(rootElem, "#app MUST exist");
di.inject(extensionInstallationStateStoreInjectable).bindIpcListeners();
let App;
let initializeApp;

View File

@ -17,8 +17,6 @@ import directoryForDownloadsInjectable from "../../../../common/app-paths/direct
import assert from "assert";
import type { InstallExtensionFromInput } from "../install-extension-from-input.injectable";
import installExtensionFromInputInjectable from "../install-extension-from-input.injectable";
import type { ExtensionInstallationStateStore } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import type { IObservableValue, ObservableMap } from "mobx";
import { computed, observable, when } from "mobx";
import type { RemovePath } from "../../../../common/fs/remove.injectable";
@ -31,16 +29,18 @@ import installedExtensionsInjectable from "../../../../features/extensions/commo
import initialDiscoveryLoadCompletedInjectable from "../../../../features/extensions/discovery/common/initial-load-completed.injectable";
import type { RemoveExtensionFiles } from "../../../../features/extensions/discovery/common/uninstall-extension.injectable";
import removeExtensionFilesInjectable from "../../../../features/extensions/discovery/common/uninstall-extension.injectable";
import type { StartPreInstallPhase } from "../../../../features/extensions/installation-states/renderer/start-pre-install-phase.injectable";
import startPreInstallPhaseInjectable from "../../../../features/extensions/installation-states/renderer/start-pre-install-phase.injectable";
describe("Extensions", () => {
let installedExtensions: ObservableMap<LensExtensionId, InstalledExtension>;
let installExtensionFromInput: jest.MockedFunction<InstallExtensionFromInput>;
let extensionInstallationStateStore: ExtensionInstallationStateStore;
let render: DiRender;
let deleteFileMock: jest.MockedFunction<RemovePath>;
let downloadBinary: jest.MockedFunction<DownloadBinary>;
let isLoaded: IObservableValue<boolean>;
let removeExtensionFilesMock: jest.MockedFunction<RemoveExtensionFiles>;
let startPreInstallPhase: StartPreInstallPhase;
beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
@ -69,7 +69,7 @@ describe("Extensions", () => {
di.override(downloadBinaryInjectable, () => downloadBinary);
installedExtensions = di.inject(installedExtensionsInjectable);
extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
startPreInstallPhase = di.inject(startPreInstallPhaseInjectable);
installedExtensions.set("extensionId", {
id: "extensionId",
@ -133,10 +133,10 @@ describe("Extensions", () => {
installExtensionFromInput.mockImplementation(async (input) => {
expect(input).toBe("https://test.extensionurl/package.tgz");
const clear = extensionInstallationStateStore.startPreInstall();
const clearPreInstallPhase = startPreInstallPhase();
await when(() => resolveInstall.get());
clear();
clearPreInstallPhase();
});
fireEvent.change(await screen.findByPlaceholderText("File path or URL", {

View File

@ -9,7 +9,6 @@ import URLParse from "url-parse";
import { getInjectable } from "@ogre-tools/injectable";
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
import getBaseRegistryUrlInjectable from "./get-base-registry-url/get-base-registry-url.injectable";
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import confirmInjectable from "../confirm-dialog/confirm.injectable";
import { reduce } from "lodash";
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
@ -19,6 +18,7 @@ import downloadJsonInjectable from "../../../common/fetch/download-json/normal.i
import type { PackageJson } from "type-fest";
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import startPreInstallPhaseInjectable from "../../../features/extensions/installation-states/renderer/start-pre-install-phase.injectable";
export interface ExtensionInfo {
name: string;
@ -46,17 +46,17 @@ const attemptInstallByInfoInjectable = getInjectable({
instantiate: (di): AttemptInstallByInfo => {
const attemptInstall = di.inject(attemptInstallInjectable);
const getBaseRegistryUrl = di.inject(getBaseRegistryUrlInjectable);
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
const confirm = di.inject(confirmInjectable);
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
const downloadJson = di.inject(downloadJsonInjectable);
const downloadBinary = di.inject(downloadBinaryInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const logger = di.inject(loggerInjectable);
const startPreInstallPhase = di.inject(startPreInstallPhaseInjectable);
return async (info) => {
const { name, version: versionOrTagName, requireConfirmation = false } = info;
const disposer = extensionInstallationStateStore.startPreInstall();
const clearPreInstallPhase = startPreInstallPhase();
const baseUrl = await getBaseRegistryUrl();
const registryUrl = new URLParse(baseUrl).set("pathname", name).toString();
let json: NpmRegistryPackageDescriptor;
@ -67,13 +67,13 @@ const attemptInstallByInfoInjectable = getInjectable({
if (!result.callWasSuccessful) {
showErrorNotification(`Failed to get registry information for extension: ${result.error}`);
return disposer();
return clearPreInstallPhase();
}
if (!isObject(result.response) || Array.isArray(result.response)) {
showErrorNotification("Failed to get registry information for extension");
return disposer();
return clearPreInstallPhase();
}
if (result.response.error || !isObject(result.response.versions)) {
@ -81,7 +81,7 @@ const attemptInstallByInfoInjectable = getInjectable({
showErrorNotification(`Failed to get registry information for extension${message}`);
return disposer();
return clearPreInstallPhase();
}
json = result.response as unknown as NpmRegistryPackageDescriptor;
@ -95,7 +95,7 @@ const attemptInstallByInfoInjectable = getInjectable({
showErrorNotification(`Failed to get valid registry information for extension. ${error}`);
}
return disposer();
return clearPreInstallPhase();
}
let version = versionOrTagName;
@ -119,7 +119,7 @@ const attemptInstallByInfoInjectable = getInjectable({
</p>
));
return disposer();
return clearPreInstallPhase();
}
version = potentialVersion;
@ -137,7 +137,7 @@ const attemptInstallByInfoInjectable = getInjectable({
</p>
));
return disposer();
return clearPreInstallPhase();
}
} else {
const versions = Object.keys(json.versions)
@ -154,7 +154,7 @@ const attemptInstallByInfoInjectable = getInjectable({
logger.error("No versions supplied for extension", { name });
showErrorNotification(`No versions found for ${name}`);
return disposer();
return clearPreInstallPhase();
}
const versionInfo = json.versions[version];
@ -164,7 +164,7 @@ const attemptInstallByInfoInjectable = getInjectable({
showErrorNotification("Configured registry has invalid data model. Please verify that it is like NPM's.");
logger.warn(`[ATTEMPT-INSTALL-BY-INFO]: registry returned unexpected data, final version is ${version} but the versions object is missing .dist.tarball as a string`, versionInfo);
return disposer();
return clearPreInstallPhase();
}
if (requireConfirmation) {
@ -183,7 +183,7 @@ const attemptInstallByInfoInjectable = getInjectable({
});
if (!proceed) {
return disposer();
return clearPreInstallPhase();
}
}
@ -194,10 +194,10 @@ const attemptInstallByInfoInjectable = getInjectable({
if (!request.callWasSuccessful) {
showErrorNotification(`Failed to download extension: ${request.error}`);
return disposer();
return clearPreInstallPhase();
}
return attemptInstall({ fileName, data: request.response }, disposer);
return attemptInstall({ fileName, data: request.response }, clearPreInstallPhase);
};
},
});

View File

@ -7,17 +7,17 @@ import uninstallExtensionInjectable from "../uninstall-extension.injectable";
import unpackExtensionInjectable from "./unpack-extension.injectable";
import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable";
import createTempFilesAndValidateInjectable from "./create-temp-files-and-validate.injectable";
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import type { Disposer } from "../../../../common/utils";
import { disposer } from "../../../../common/utils";
import { Button } from "../../button";
import React from "react";
import { remove as removeDir } from "fs-extra";
import { shell } from "electron";
import { ExtensionInstallationState } from "../../../../extensions/extension-installation-state-store/extension-installation-state-store";
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
import showInfoNotificationInjectable from "../../notifications/show-info-notification.injectable";
import getInstalledExtensionInjectable from "../../../../features/extensions/common/get-installed-extension.injectable";
import startPreInstallPhaseInjectable from "../../../../features/extensions/installation-states/renderer/start-pre-install-phase.injectable";
import getExtensionInstallationPhaseInjectable from "../../../../features/extensions/installation-states/renderer/get-phase.injectable";
export interface InstallRequest {
fileName: string;
@ -33,14 +33,15 @@ const attemptInstallInjectable = getInjectable({
const unpackExtension = di.inject(unpackExtensionInjectable);
const createTempFilesAndValidate = di.inject(createTempFilesAndValidateInjectable);
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
const installStateStore = di.inject(extensionInstallationStateStoreInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const showInfoNotification = di.inject(showInfoNotificationInjectable);
const getInstalledExtension = di.inject(getInstalledExtensionInjectable);
const startPreInstallPhase = di.inject(startPreInstallPhaseInjectable);
const getExtensionInstallationPhase = di.inject(getExtensionInstallationPhaseInjectable);
return async (request, cleanup) => {
const dispose = disposer(
installStateStore.startPreInstall(),
startPreInstallPhase(),
cleanup,
);
@ -51,9 +52,9 @@ const attemptInstallInjectable = getInjectable({
}
const { name, version, description } = validatedRequest.manifest;
const curState = installStateStore.getInstallationState(validatedRequest.id);
const curState = getExtensionInstallationPhase(validatedRequest.id);
if (curState !== ExtensionInstallationState.IDLE) {
if (curState !== "idle") {
dispose();
return void showErrorNotification(
@ -115,7 +116,7 @@ const attemptInstallInjectable = getInjectable({
},
);
} else {
// clean up old data if still around
// clean up old data if still around
await removeDir(extensionFolder);
// install extension if not yet exists

View File

@ -4,7 +4,6 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable";
import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import type { Disposer } from "../../../../common/utils";
import { noop } from "../../../../common/utils";
import { extensionDisplayName } from "../../../../extensions/lens-extension";
@ -20,6 +19,8 @@ import showInfoNotificationInjectable from "../../notifications/show-info-notifi
import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable";
import installedUserExtensionsInjectable from "../../../../features/extensions/common/user-extensions.injectable";
import enableExtensionInjectable from "../enable-extension.injectable";
import setExtensionAsInstallingInjectable from "../../../../features/extensions/installation-states/renderer/set-as-installing.injectable";
import clearExtensionAsInstallingInjectable from "../../../../features/extensions/installation-states/renderer/clear-as-installing.injectable";
export type UnpackExtension = (request: InstallRequestValidated, disposeDownloading?: Disposer) => Promise<void>;
@ -27,13 +28,14 @@ const unpackExtensionInjectable = getInjectable({
id: "unpack-extension",
instantiate: (di): UnpackExtension => {
const getExtensionDestFolder = di.inject(getExtensionDestFolderInjectable);
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
const extractTar = di.inject(extractTarInjectable);
const logger = di.inject(loggerInjectable);
const showInfoNotification = di.inject(showInfoNotificationInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const installedUserExtensions = di.inject(installedUserExtensionsInjectable);
const enableExtension = di.inject(enableExtensionInjectable);
const setExtensionAsInstalling = di.inject(setExtensionAsInstallingInjectable);
const clearExtensionAsInstalling = di.inject(clearExtensionAsInstallingInjectable);
return async (request, disposeDownloading) => {
const {
@ -43,7 +45,7 @@ const unpackExtensionInjectable = getInjectable({
manifest: { name, version },
} = request;
extensionInstallationStateStore.setInstalling(id);
setExtensionAsInstalling(id);
disposeDownloading?.();
const displayName = extensionDisplayName(name, version);
@ -103,8 +105,8 @@ const unpackExtensionInjectable = getInjectable({
</p>
));
} finally {
// Remove install state once finished
extensionInstallationStateStore.clearInstalling(id);
// Remove install state once finished
clearExtensionAsInstalling(id);
// clean up
fse.remove(unpackingTempFolder).catch(noop);

View File

@ -35,9 +35,8 @@ import type { LensExtensionId } from "../../../extensions/lens-extension";
import type { InstallOnDrop } from "./install-on-drop.injectable";
import installOnDropInjectable from "./install-on-drop.injectable";
import { supportedExtensionFormats } from "./supported-extension-formats";
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
import Gutter from "../gutter/gutter";
import extensionsInstallingCountInjectable from "../../../features/extensions/installation-states/renderer/installing-count.injectable";
interface Dependencies {
userExtensions: IComputedValue<InstalledExtension[]>;
@ -47,7 +46,7 @@ interface Dependencies {
installExtensionFromInput: InstallExtensionFromInput;
installFromSelectFileDialog: () => Promise<void>;
installOnDrop: InstallOnDrop;
extensionInstallationStateStore: ExtensionInstallationStateStore;
extensionsInstallingCount: IComputedValue<number>;
}
@observer
@ -64,7 +63,7 @@ class NonInjectedExtensions extends React.Component<Dependencies> {
reaction(() => this.props.userExtensions.get().length, (curSize, prevSize) => {
if (curSize > prevSize) {
disposeOnUnmount(this, [
when(() => !this.props.extensionInstallationStateStore.anyInstalling, () => this.installPath = ""),
when(() => this.props.extensionsInstallingCount.get() === 0, () => this.installPath = ""),
]);
}
}),
@ -138,6 +137,6 @@ export const Extensions = withInjectables<Dependencies>(NonInjectedExtensions, {
installExtensionFromInput: di.inject(installExtensionFromInputInjectable),
installOnDrop: di.inject(installOnDropInjectable),
installFromSelectFileDialog: di.inject(installFromSelectFileDialogInjectable),
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
extensionsInstallingCount: di.inject(extensionsInstallingCountInjectable),
}),
});

View File

@ -3,19 +3,20 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import React from "react";
import type { ExtendableDisposer } from "../../../common/utils";
import type { Disposer } from "../../../common/utils";
import { noop } from "../../../common/utils";
import { InputValidators } from "../input";
import { getMessageFromError } from "./get-message-from-error/get-message-from-error";
import { getInjectable } from "@ogre-tools/injectable";
import attemptInstallInjectable from "./attempt-install/attempt-install.injectable";
import attemptInstallByInfoInjectable from "./attempt-install-by-info.injectable";
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import readFileNotifyInjectable from "./read-file-notify/read-file-notify.injectable";
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import downloadBinaryInjectable from "../../../common/fetch/download-binary.injectable";
import { withTimeout } from "../../../common/fetch/timeout-controller";
import startPreInstallPhaseInjectable from "../../../features/extensions/installation-states/renderer/start-pre-install-phase.injectable";
export type InstallExtensionFromInput = (input: string) => Promise<void>;
@ -25,33 +26,34 @@ const installExtensionFromInputInjectable = getInjectable({
instantiate: (di): InstallExtensionFromInput => {
const attemptInstall = di.inject(attemptInstallInjectable);
const attemptInstallByInfo = di.inject(attemptInstallByInfoInjectable);
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
const readFileNotify = di.inject(readFileNotifyInjectable);
const getBasenameOfPath = di.inject(getBasenameOfPathInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const logger = di.inject(loggerInjectable);
const downloadBinary = di.inject(downloadBinaryInjectable);
const startPreInstallPhase = di.inject(startPreInstallPhaseInjectable);
return async (input) => {
let disposer: ExtendableDisposer | undefined = undefined;
let clearPreInstallPhase: Disposer = noop;
try {
// fixme: improve error messages for non-tar-file URLs
if (InputValidators.isUrl.validate(input)) {
// install via url
disposer = extensionInstallationStateStore.startPreInstall();
clearPreInstallPhase = startPreInstallPhase();
const { signal } = withTimeout(10 * 60 * 1000);
const result = await downloadBinary(input, { signal });
if (!result.callWasSuccessful) {
showErrorNotification(`Failed to download extension: ${result.error}`);
clearPreInstallPhase();
return disposer();
return;
}
const fileName = getBasenameOfPath(input);
return await attemptInstall({ fileName, data: result.response }, disposer);
return await attemptInstall({ fileName, data: result.response }, clearPreInstallPhase);
}
try {
@ -88,7 +90,7 @@ const installExtensionFromInputInjectable = getInjectable({
</p>
));
} finally {
disposer?.();
clearPreInstallPhase();
}
};
},

View File

@ -8,14 +8,15 @@ import React from "react";
import { prevDefault } from "../../utils";
import { Button } from "../button";
import { Icon } from "../icon";
import { observer } from "mobx-react";
import { Input, InputValidators } from "../input";
import { SubTitle } from "../layout/sub-title";
import { TooltipPosition } from "../tooltip";
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import { withInjectables } from "@ogre-tools/injectable-react";
import { unionInputValidatorsAsync } from "../input/input_validators";
import type { IComputedValue } from "mobx";
import extensionsInstallingCountInjectable from "../../../features/extensions/installation-states/renderer/installing-count.injectable";
import extensionsPreinstallingCountInjectable from "../../../features/extensions/installation-states/renderer/preinstalling-count.injectable";
import { observer } from "mobx-react";
export interface InstallProps {
installPath: string;
@ -26,7 +27,8 @@ export interface InstallProps {
}
interface Dependencies {
extensionInstallationStateStore: ExtensionInstallationStateStore;
extensionsInstallingCount: IComputedValue<number>;
extensionsPreinstallingCount: IComputedValue<number>;
}
const installInputValidator = unionInputValidatorsAsync(
@ -38,71 +40,67 @@ const installInputValidator = unionInputValidatorsAsync(
InputValidators.isPath,
);
const NonInjectedInstall: React.FC<Dependencies & InstallProps> = ({
const NonInjectedInstall = observer(({
installPath,
supportedFormats,
onChange,
installFromInput,
installFromSelectFileDialog,
extensionInstallationStateStore,
}) => (
<section>
<SubTitle
title={`Name or file path or URL to an extension package (${supportedFormats.join(
", ",
)})`}
/>
<div className={styles.inputs}>
<div>
<Input
theme="round-black"
disabled={extensionInstallationStateStore.anyPreInstallingOrInstalling}
placeholder={"Name or file path or URL"}
showErrorsAsTooltip={{ preferredPositions: TooltipPosition.BOTTOM }}
validators={installPath ? installInputValidator : undefined}
value={installPath}
onChange={onChange}
onSubmit={installFromInput}
iconRight={(
<Icon
className={styles.icon}
smallest
material="folder_open"
onClick={prevDefault(installFromSelectFileDialog)}
tooltip="Browse"
/>
)}
/>
</div>
<div>
<Button
className={styles.button}
primary
label="Install"
disabled={
extensionInstallationStateStore.anyPreInstallingOrInstalling
}
waiting={extensionInstallationStateStore.anyPreInstallingOrInstalling}
onClick={installFromInput}
/>
</div>
</div>
<small className={styles.proTip}>
<b>Pro-Tip</b>
: you can drag and drop a tarball file to this area
</small>
</section>
);
extensionsInstallingCount,
extensionsPreinstallingCount,
}: Dependencies & InstallProps) => {
const anyPreInstallingOrInstalling = extensionsInstallingCount.get() > 0 || extensionsPreinstallingCount.get() > 0;
export const Install = withInjectables<Dependencies, InstallProps>(
observer(NonInjectedInstall),
{
getProps: (di, props) => ({
extensionInstallationStateStore: di.inject(
extensionInstallationStateStoreInjectable,
),
return (
<section>
<SubTitle
title={`Name or file path or URL to an extension package (${supportedFormats.join(", ")})`}
/>
<div className={styles.inputs}>
<div>
<Input
theme="round-black"
disabled={anyPreInstallingOrInstalling}
placeholder={"Name or file path or URL"}
showErrorsAsTooltip={{ preferredPositions: TooltipPosition.BOTTOM }}
validators={installPath ? installInputValidator : undefined}
value={installPath}
onChange={onChange}
onSubmit={installFromInput}
iconRight={(
<Icon
className={styles.icon}
smallest
material="folder_open"
onClick={prevDefault(installFromSelectFileDialog)}
tooltip="Browse"
/>
)}
/>
</div>
<div>
<Button
className={styles.button}
primary
label="Install"
disabled={anyPreInstallingOrInstalling}
waiting={anyPreInstallingOrInstalling}
onClick={installFromInput}
/>
</div>
</div>
<small className={styles.proTip}>
<b>Pro-Tip</b>
: you can drag and drop a tarball file to this area
</small>
</section>
);
});
...props,
}),
},
);
export const Install = withInjectables<Dependencies, InstallProps>(NonInjectedInstall, {
getProps: (di, props) => ({
...props,
extensionsInstallingCount: di.inject(extensionsInstallingCountInjectable),
extensionsPreinstallingCount: di.inject(extensionsPreinstallingCountInjectable),
}),
});

View File

@ -5,9 +5,7 @@
import styles from "./installed-extensions.module.scss";
import React, { useMemo } from "react";
import type {
InstalledExtension,
} from "../../../extensions/extension-discovery/extension-discovery";
import type { InstalledExtension } from "../../../extensions/extension-discovery/extension-discovery";
import { Icon } from "../icon";
import { List } from "../list/list";
import { MenuActions, MenuItem } from "../menu";
@ -16,14 +14,12 @@ import { cssNames, toJS } from "../../utils";
import { observer } from "mobx-react";
import type { Row } from "react-table";
import type { LensExtensionId } from "../../../extensions/lens-extension";
import { withInjectables } from "@ogre-tools/injectable-react";
import extensionInstallationStateStoreInjectable
from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import type { ExtensionInstallationStateStore } from "../../../extensions/extension-installation-state-store/extension-installation-state-store";
import type { IComputedValue } from "mobx";
import initialDiscoveryLoadCompletedInjectable from "../../../features/extensions/discovery/common/initial-load-completed.injectable";
import type { GetExtensionInstallationPhase } from "../../../features/extensions/installation-states/renderer/get-phase.injectable";
import getExtensionInstallationPhaseInjectable from "../../../features/extensions/installation-states/renderer/get-phase.injectable";
import extensionsUninstallingCountInjectable from "../../../features/extensions/installation-states/renderer/uninstalling-count.injectable";
export interface InstalledExtensionsProps {
extensions: InstalledExtension[];
@ -34,7 +30,8 @@ export interface InstalledExtensionsProps {
interface Dependencies {
initialDiscoveryLoadCompleted: IComputedValue<boolean>;
extensionInstallationStateStore: ExtensionInstallationStateStore;
getExtensionInstallationPhase: GetExtensionInstallationPhase;
extensionsUninstallingCount: IComputedValue<number>;
}
function getStatus(extension: InstalledExtension) {
@ -47,11 +44,12 @@ function getStatus(extension: InstalledExtension) {
const NonInjectedInstalledExtensions = observer(({
initialDiscoveryLoadCompleted,
extensionInstallationStateStore,
getExtensionInstallationPhase,
extensions,
uninstall,
enable,
disable,
extensionsUninstallingCount,
}: Dependencies & InstalledExtensionsProps) => {
const columns = useMemo(
() => [
@ -91,7 +89,7 @@ const NonInjectedInstalledExtensions = observer(({
() => extensions.map(extension => {
const { id, isEnabled, isCompatible, manifest } = extension;
const { name, description, version } = manifest;
const isUninstalling = extensionInstallationStateStore.isExtensionUninstalling(id);
const isUninstalling = getExtensionInstallationPhase(id) === "uninstalling";
return {
extension: (
@ -145,7 +143,7 @@ const NonInjectedInstalledExtensions = observer(({
</MenuActions>
),
};
}), [toJS(extensions), extensionInstallationStateStore.anyUninstalling],
}), [toJS(extensions), extensionsUninstallingCount.get() > 0],
);
if (!initialDiscoveryLoadCompleted.get()) {
@ -184,7 +182,8 @@ const NonInjectedInstalledExtensions = observer(({
export const InstalledExtensions = withInjectables<Dependencies, InstalledExtensionsProps>(NonInjectedInstalledExtensions, {
getProps: (di, props) => ({
...props,
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
initialDiscoveryLoadCompleted: di.inject(initialDiscoveryLoadCompletedInjectable).value,
getExtensionInstallationPhase: di.inject(getExtensionInstallationPhaseInjectable),
extensionsUninstallingCount: di.inject(extensionsUninstallingCountInjectable),
}),
});

View File

@ -3,7 +3,6 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import extensionInstallationStateStoreInjectable from "../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import type { LensExtensionId } from "../../../extensions/lens-extension";
import { extensionDisplayName } from "../../../extensions/lens-extension";
@ -15,18 +14,21 @@ import showErrorNotificationInjectable from "../notifications/show-error-notific
import installedUserExtensionsInjectable from "../../../features/extensions/common/user-extensions.injectable";
import getInstalledExtensionInjectable from "../../../features/extensions/common/get-installed-extension.injectable";
import removeExtensionFilesInjectable from "../../../features/extensions/discovery/common/uninstall-extension.injectable";
import clearExtensionAsInstallingInjectable from "../../../features/extensions/installation-states/renderer/clear-as-installing.injectable";
import setExtensionAsUninstallingInjectable from "../../../features/extensions/installation-states/renderer/set-as-uninstalling.injectable";
const uninstallExtensionInjectable = getInjectable({
id: "uninstall-extension",
instantiate: (di) => {
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
const logger = di.inject(loggerInjectable);
const showSuccessNotification = di.inject(showSuccessNotificationInjectable);
const showErrorNotification = di.inject(showErrorNotificationInjectable);
const installedUserExtensions = di.inject(installedUserExtensionsInjectable);
const getInstalledExtension = di.inject(getInstalledExtensionInjectable);
const removeExtensionFiles = di.inject(removeExtensionFilesInjectable);
const clearExtensionAsInstalling = di.inject(clearExtensionAsInstallingInjectable);
const setExtensionAsUninstalling = di.inject(setExtensionAsUninstallingInjectable);
return async (extensionId: LensExtensionId): Promise<boolean> => {
const ext = getInstalledExtension(extensionId);
@ -42,8 +44,8 @@ const uninstallExtensionInjectable = getInjectable({
try {
logger.debug(`[EXTENSIONS]: trying to uninstall ${extensionId}`);
extensionInstallationStateStore.setUninstalling(extensionId);
setExtensionAsUninstalling(extensionId);
await removeExtensionFiles(extensionId);
// wait for the ExtensionLoader to actually uninstall the extension
@ -76,8 +78,7 @@ const uninstallExtensionInjectable = getInjectable({
return false;
} finally {
// Remove uninstall state on uninstall failure
extensionInstallationStateStore.clearUninstalling(extensionId);
clearExtensionAsInstalling(extensionId);
}
};
},