From 4b2aaa8923663446f3d4152e00ff1f21dd6bd800 Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Wed, 11 May 2022 08:17:17 +0300 Subject: [PATCH] Expand scope of behaviour for updating using tray also contain checking for updates Co-authored-by: Mikko Aspiala Signed-off-by: Janne Savolainen --- .../installing-update-using-tray.test.ts.snap | 67 +++ .../trigger-updating-using-tray.test.ts.snap | 25 - .../installing-update-using-tray.test.ts | 468 ++++++++++++++++++ .../trigger-updating-using-tray.test.ts | 96 ---- src/main/check-for-updates.injectable.ts | 14 - .../check-for-updates-tray-item.injectable.ts | 39 -- .../check-for-platform-updates.injectable.ts | 30 ++ .../check-for-updates-tray-item.injectable.ts | 59 +++ .../download-platform-update.injectable.ts | 15 + ...pplication-update-tray-item.injectable.ts} | 29 +- .../progress-of-update-download.injectable.ts | 24 + .../selected-update-channel.injectable.ts | 26 + src/main/update-app/update-channels.ts | 28 ++ .../update-app/version-update.injectable.ts | 87 ++++ 14 files changed, 823 insertions(+), 184 deletions(-) create mode 100644 src/behaviours/update-app/__snapshots__/installing-update-using-tray.test.ts.snap delete mode 100644 src/behaviours/update-app/__snapshots__/trigger-updating-using-tray.test.ts.snap create mode 100644 src/behaviours/update-app/installing-update-using-tray.test.ts delete mode 100644 src/behaviours/update-app/trigger-updating-using-tray.test.ts delete mode 100644 src/main/check-for-updates.injectable.ts delete mode 100644 src/main/tray/tray-menu-item/implementations/check-for-updates-tray-item.injectable.ts create mode 100644 src/main/update-app/check-for-platform-updates.injectable.ts create mode 100644 src/main/update-app/check-for-updates-tray-item.injectable.ts create mode 100644 src/main/update-app/download-platform-update.injectable.ts rename src/main/update-app/{trigger-application-update-tray-item.injectable.ts => install-application-update-tray-item.injectable.ts} (54%) create mode 100644 src/main/update-app/progress-of-update-download.injectable.ts create mode 100644 src/main/update-app/selected-update-channel.injectable.ts create mode 100644 src/main/update-app/update-channels.ts create mode 100644 src/main/update-app/version-update.injectable.ts diff --git a/src/behaviours/update-app/__snapshots__/installing-update-using-tray.test.ts.snap b/src/behaviours/update-app/__snapshots__/installing-update-using-tray.test.ts.snap new file mode 100644 index 0000000000..1b0f3b1380 --- /dev/null +++ b/src/behaviours/update-app/__snapshots__/installing-update-using-tray.test.ts.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`installing update using tray given a non-installed update is already downloaded, when started renders 1`] = ` + +
+ +`; + +exports[`installing update using tray given a non-installed update is already downloaded, when started when user checks for even newer update renders 1`] = ` + +
+ +`; + +exports[`installing update using tray given a non-installed update is already downloaded, when started when user checks for even newer update when new update is discovered renders 1`] = ` + +
+ +`; + +exports[`installing update using tray given a non-installed update is already downloaded, when started when user checks for even newer update when no new update is discovered renders 1`] = ` + +
+ +`; + +exports[`installing update using tray given a non-installed update is already downloaded, when started when user installs the update renders 1`] = ` + +
+ +`; + +exports[`installing update using tray given no update is already downloaded, and "latest" update channel is selected, when started renders 1`] = ` + +
+ +`; + +exports[`installing update using tray given no update is already downloaded, and "latest" update channel is selected, when started when user checks for updates using tray renders 1`] = ` + +
+ +`; + +exports[`installing update using tray given no update is already downloaded, and "latest" update channel is selected, when started when user checks for updates using tray when new update is discovered renders 1`] = ` + +
+ +`; + +exports[`installing update using tray given no update is already downloaded, and "latest" update channel is selected, when started when user checks for updates using tray when new update is discovered when update is downloaded renders 1`] = ` + +
+ +`; + +exports[`installing update using tray given no update is already downloaded, and "latest" update channel is selected, when started when user checks for updates using tray when new update is discovered when update is downloaded when user installs the update renders 1`] = ` + +
+ +`; + +exports[`installing update using tray given no update is already downloaded, and "latest" update channel is selected, when started when user checks for updates using tray when no new update is discovered renders 1`] = ` + +
+ +`; diff --git a/src/behaviours/update-app/__snapshots__/trigger-updating-using-tray.test.ts.snap b/src/behaviours/update-app/__snapshots__/trigger-updating-using-tray.test.ts.snap deleted file mode 100644 index db6628682a..0000000000 --- a/src/behaviours/update-app/__snapshots__/trigger-updating-using-tray.test.ts.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`trigger updating using tray given no update available, when started renders 1`] = ` - -
- -`; - -exports[`trigger updating using tray given no update available, when started when an update becomes ready to be installed renders 1`] = ` - -
- -`; - -exports[`trigger updating using tray given no update available, when started when an update becomes ready to be installed when triggering installation of the update renders 1`] = ` - -
- -`; - -exports[`trigger updating using tray given no update available, when started when an update becomes ready to be installed when update becomes unavailable renders 1`] = ` - -
- -`; diff --git a/src/behaviours/update-app/installing-update-using-tray.test.ts b/src/behaviours/update-app/installing-update-using-tray.test.ts new file mode 100644 index 0000000000..97f2b8a8a0 --- /dev/null +++ b/src/behaviours/update-app/installing-update-using-tray.test.ts @@ -0,0 +1,468 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import quitAndInstallUpdateInjectable from "../../main/electron-app/features/quit-and-install-update.injectable"; +import type { RenderResult } from "@testing-library/react"; +import electronUpdaterIsActiveInjectable from "../../main/electron-app/features/electron-updater-is-active.injectable"; +import publishIsConfiguredInjectable from "../../main/update-app/publish-is-configured.injectable"; +import type { CheckForPlatformUpdates } from "../../main/update-app/check-for-platform-updates.injectable"; +import checkForPlatformUpdatesInjectable from "../../main/update-app/check-for-platform-updates.injectable"; +import type { AsyncFnMock } from "@async-fn/jest"; +import asyncFn from "@async-fn/jest"; +import { updateChannels } from "../../main/update-app/update-channels"; +import downloadPlatformUpdateInjectable from "../../main/update-app/download-platform-update.injectable"; +import selectedUpdateChannelInjectable from "../../main/update-app/selected-update-channel.injectable"; +import progressOfUpdateDownloadInjectable from "../../main/update-app/progress-of-update-download.injectable"; + +describe("installing update using tray", () => { + let applicationBuilder: ApplicationBuilder; + let quitAndInstallUpdateMock: jest.Mock; + let checkForPlatformUpdatesMock: AsyncFnMock; + let downloadPlatformUpdateMock: AsyncFnMock<() => void>; + + beforeEach(() => { + applicationBuilder = getApplicationBuilder(); + + applicationBuilder.beforeApplicationStart(({ mainDi }) => { + quitAndInstallUpdateMock = jest.fn(); + checkForPlatformUpdatesMock = asyncFn(); + downloadPlatformUpdateMock = asyncFn(); + + mainDi.override( + checkForPlatformUpdatesInjectable, + () => checkForPlatformUpdatesMock, + ); + + mainDi.override( + downloadPlatformUpdateInjectable, + () => downloadPlatformUpdateMock, + ); + + mainDi.override( + quitAndInstallUpdateInjectable, + () => quitAndInstallUpdateMock, + ); + + mainDi.override(electronUpdaterIsActiveInjectable, () => true); + mainDi.override(publishIsConfiguredInjectable, () => true); + }); + }); + + describe('given no update is already downloaded, and "latest" update channel is selected, when started', () => { + let rendered: RenderResult; + + beforeEach(async () => { + rendered = await applicationBuilder.render(); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("user cannot install update yet", () => { + expect(applicationBuilder.tray.get("install-update")).toBeUndefined(); + }); + + describe("when user checks for updates using tray", () => { + let checkForUpdatesPromise: Promise; + + beforeEach(async () => { + checkForUpdatesPromise = applicationBuilder.tray.click("check-for-updates"); + }); + + it('checks for updates from "latest" update channel', () => { + expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith( + updateChannels.latest, + ); + }); + + xit("notifies the user that checking for updates is happening", () => {}); + + it("user cannot check for updates again", () => { + expect( + applicationBuilder.tray.get("check-for-updates").enabled.get(), + ).toBe(false); + }); + + it("name of tray item for checking updates indicates that checking is happening", () => { + expect( + applicationBuilder.tray.get("check-for-updates").label.get(), + ).toBe("Checking for updates..."); + }); + + it("user cannot install update yet", () => { + expect(applicationBuilder.tray.get("install-update")).toBeUndefined(); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + describe("when no new update is discovered", () => { + beforeEach(async () => { + await checkForPlatformUpdatesMock.resolve({ + updateWasDiscovered: false, + }); + + await checkForUpdatesPromise; + }); + + xit("notifies the user", () => {}); + + it("does not start downloading update", () => { + expect(downloadPlatformUpdateMock).not.toHaveBeenCalled(); + }); + + it("user cannot install update", () => { + expect(applicationBuilder.tray.get("install-update")).toBeUndefined(); + }); + + it("user can check for updates again", () => { + expect( + applicationBuilder.tray.get("check-for-updates").enabled.get(), + ).toBe(true); + }); + + it("name of tray item for checking updates no longer indicates that checking is happening", () => { + expect( + applicationBuilder.tray.get("check-for-updates").label.get(), + ).toBe("Check for updates"); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + }); + + describe("when new update is discovered", () => { + beforeEach(async () => { + await checkForPlatformUpdatesMock.resolve({ + updateWasDiscovered: true, + version: "some-version", + }); + + await checkForUpdatesPromise; + }); + + it("starts downloading the update!?!?", () => { + expect(downloadPlatformUpdateMock).toHaveBeenCalled(); + }); + + xit("notifies the user that download is happening", () => {}); + + it("user cannot check for updates again yet", () => { + expect( + applicationBuilder.tray.get("check-for-updates").enabled.get(), + ).toBe(false); + }); + + it("name of tray item for checking updates indicates that downloading is happening", () => { + expect( + applicationBuilder.tray.get("check-for-updates").label.get(), + ).toBe('Downloading update "some-version" (0%)...'); + }); + + it("when download progresses, percentage increases", () => { + const progressOfUpdateDownload = applicationBuilder.dis.mainDi.inject(progressOfUpdateDownloadInjectable); + + progressOfUpdateDownload.setValue(42); + + expect( + applicationBuilder.tray.get("check-for-updates").label.get(), + ).toBe('Downloading update "some-version" (42%)...'); + }); + + it("user still cannot install update", () => { + expect(applicationBuilder.tray.get("install-update")).toBeUndefined(); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + describe("when update is downloaded", () => { + beforeEach(async () => { + await downloadPlatformUpdateMock.resolve(); + }); + + it("does not quit and install update yet", () => { + expect(quitAndInstallUpdateMock).not.toHaveBeenCalled(); + }); + + it("user can install update", () => { + expect( + applicationBuilder.tray.get("install-update").label.get(), + ).toBe('Install update "some-version"'); + }); + + it("user can check for updates again", () => { + expect( + applicationBuilder.tray.get("check-for-updates").enabled.get(), + ).toBe(true); + }); + + it("name of tray item for checking updates no longer indicates that downloading is happening", () => { + expect( + applicationBuilder.tray.get("check-for-updates").label.get(), + ).toBe("Check for updates"); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + describe("when user installs the update", () => { + beforeEach(async () => { + await applicationBuilder.tray.click("install-update"); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("quits application and installs update", () => { + expect(quitAndInstallUpdateMock).toHaveBeenCalled(); + }); + }); + }); + + describe("when user changes update channel to other channel instead of just installing", () => { + beforeEach(() => { + const selectedUpdateChannel = applicationBuilder.dis.mainDi.inject(selectedUpdateChannelInjectable); + + selectedUpdateChannel.setValue(updateChannels.beta.id); + }); + + it("user cannot install existing update for being from wrong update channel", () => { + + }); + + describe("when user installs an update", () => { + + beforeEach(() => { + + }); + + it('still installs the update from "latest" update channel', () => { + + }); + }); + + it("when checking updates again, checks for updates from the other update channel", async () => { + checkForPlatformUpdatesMock.mockClear(); + + applicationBuilder.tray.click("check-for-updates"); + + expect(checkForPlatformUpdatesMock).toHaveBeenCalledWith( + updateChannels.beta, + ); + }); + }); + }); + }); + + describe('given update channel "alpha" is selected, when checking for updates', () => { + beforeEach(() => { + + }); + + it('checks updates from update channel "alpha"', () => { + + }); + + it("when update is discovered, does not check update from other update channels", () => { + + }); + + describe("when no update is discovered", () => { + beforeEach(() => { + + }); + + it('checks updates from update channel "beta"', () => { + + }); + + it("when update is discovered, does not check update from other update channels", () => { + + }); + + describe("when no update is discovered again", () => { + beforeEach(() => { + + }); + + it('finally checks updates from update channel "latest"', () => { + + }); + + it("when update is discovered, does not check update from other update channels", () => { + + }); + }); + }); + }); + }); + + xdescribe("given a non-installed update is already downloaded, when started", () => { + let rendered: RenderResult; + + beforeEach(async () => { + rendered = await applicationBuilder.render(); + }); + + it("user can check for updates", () => { + expect( + applicationBuilder.tray.get("check-for-updates").enabled.get(), + ).toBe(true); + }); + + it("user can install the update", () => { + expect( + applicationBuilder.tray.get("install-update").label.get(), + ).toBe('Install update "some-old-version"'); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + describe("when user installs the update", () => { + beforeEach(() => {}); + + it("quits application and installs update", () => {}); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + }); + + describe("when user checks for even newer update", () => { + let checkForUpdatesPromise: Promise; + + beforeEach(() => { + checkForUpdatesPromise = applicationBuilder.tray.click("check-for-updates"); + }); + + it("user cannot check for updates again", () => { + expect( + applicationBuilder.tray.get("check-for-updates").enabled.get(), + ).toBe(false); + }); + + it("user cannot install any update", () => { + expect(applicationBuilder.tray.get("install-update")).toBeUndefined(); + }); + + it("name of tray item indicates that checking is happening", () => { + expect( + applicationBuilder.tray.get("check-for-updates").label.get(), + ).toBe("Checking for updates..."); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + describe("when no new update is discovered", () => { + beforeEach(async () => { + await checkForPlatformUpdatesMock.resolve({ + updateWasDiscovered: false, + }); + + await checkForUpdatesPromise; + }); + + xit("notifies the user about the old update", () => {}); + + it("user can check for updates again", () => { + expect( + applicationBuilder.tray.get("check-for-updates").enabled.get(), + ).toBe(true); + }); + + it("user can still install the old update", () => { + expect( + applicationBuilder.tray.get("install-update").label.get(), + ).toBe('Install update "some-old-version"'); + }); + + it("name of tray item for checking update no longer indicates that checking is happening", () => { + expect( + applicationBuilder.tray.get("check-for-updates").label.get(), + ).toBe("Check for updates"); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + }); + + describe("when new update is discovered", () => { + beforeEach(async () => { + await checkForPlatformUpdatesMock.resolve({ + updateWasDiscovered: true, + version: "some-newer-version", + }); + + await checkForUpdatesPromise; + }); + + it("starts downloading the update!?!?", () => {}); + + it("user cannot check for updates again yet", () => { + expect( + applicationBuilder.tray.get("check-for-updates").enabled.get(), + ).toBe(false); + }); + + it("name of tray item still indicates that download is happening", () => { + expect( + applicationBuilder.tray.get("check-for-updates").label.get(), + ).toBe('Downloading update "some-newer-version"...'); + }); + + it("user cannot install any update yet", () => { + expect(applicationBuilder.tray.get("install-update")).toBeUndefined(); + }); + + xit("notifies the user that download of new update is happening", () => {}); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + describe("when new update is downloaded", () => { + beforeEach(async () => { + await downloadPlatformUpdateMock.resolve(); + }); + + it("user can install the new update", () => { + expect( + applicationBuilder.tray.get("install-update").label.get(), + ).toBe('Install update "some-new-version"'); + }); + + it("user can check for updates again", () => { + expect( + applicationBuilder.tray.get("check-for-updates").enabled.get(), + ).toBe(true); + }); + + it("name of tray item no longer indicates that checking is happening", () => { + expect( + applicationBuilder.tray.get("check-for-updates").label.get(), + ).toBe("Check for updates"); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + }); + }); + }); + }); +}); diff --git a/src/behaviours/update-app/trigger-updating-using-tray.test.ts b/src/behaviours/update-app/trigger-updating-using-tray.test.ts deleted file mode 100644 index db456447fd..0000000000 --- a/src/behaviours/update-app/trigger-updating-using-tray.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; -import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; - -import quitAndInstallUpdateInjectable from "../../main/electron-app/features/quit-and-install-update.injectable"; -import type { RenderResult } from "@testing-library/react"; - -describe("trigger updating using tray", () => { - let applicationBuilder: ApplicationBuilder; - let quitAndInstallUpdateMock: jest.Mock; - - beforeEach(() => { - applicationBuilder = getApplicationBuilder(); - - applicationBuilder.beforeApplicationStart(({ mainDi }) => { - quitAndInstallUpdateMock = jest.fn(); - - mainDi.override(quitAndInstallUpdateInjectable, () => quitAndInstallUpdateMock); - }); - }); - - describe("given no update available, when started", () => { - let rendered: RenderResult; - - beforeEach(async () => { - rendered = await applicationBuilder.render(); - }); - - it("renders", () => { - expect(rendered.baseElement).toMatchSnapshot(); - }); - - it("does not quit and install update yet", () => { - expect(quitAndInstallUpdateMock).not.toHaveBeenCalled(); - }); - - it("does not have possibility to trigger installation of an update", () => { - const trayItem = applicationBuilder.tray.get("trigger-application-update"); - - expect(trayItem).toBe(undefined); - }); - - describe("when an update becomes ready to be installed", () => { - beforeEach(() => { - applicationBuilder.applicationUpdater.setUpdateIsReadyToBeInstalled(true); - }); - - it("renders", () => { - expect(rendered.baseElement).toMatchSnapshot(); - }); - - it("does not quit and install update yet", () => { - expect(quitAndInstallUpdateMock).not.toHaveBeenCalled(); - }); - - it("has possibility to trigger installation of the update", () => { - const trayItem = applicationBuilder.tray.get("trigger-application-update"); - - expect(trayItem).not.toBe(undefined); - }); - - describe("when triggering installation of the update", () => { - beforeEach(() => { - applicationBuilder.tray.click("trigger-application-update"); - }); - - it("renders", () => { - expect(rendered.baseElement).toMatchSnapshot(); - }); - - it("quits application and installs update", () => { - expect(quitAndInstallUpdateMock).toHaveBeenCalled(); - }); - }); - - describe("when update becomes unavailable", () => { - beforeEach(async () => { - applicationBuilder.applicationUpdater.setUpdateIsReadyToBeInstalled(false); - }); - - it("renders", () => { - expect(rendered.baseElement).toMatchSnapshot(); - }); - - it("does not have possibility to trigger installation of the update anymore", () => { - const trayItem = applicationBuilder.tray.get("trigger-application-update"); - - expect(trayItem).toBe(undefined); - }); - }); - }); - }); -}); diff --git a/src/main/check-for-updates.injectable.ts b/src/main/check-for-updates.injectable.ts deleted file mode 100644 index 755b4aa0c4..0000000000 --- a/src/main/check-for-updates.injectable.ts +++ /dev/null @@ -1,14 +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 { checkForUpdates } from "./app-updater"; - -const checkForUpdatesInjectable = getInjectable({ - id: "check-for-updates", - instantiate: () => checkForUpdates, - causesSideEffects: true, -}); - -export default checkForUpdatesInjectable; diff --git a/src/main/tray/tray-menu-item/implementations/check-for-updates-tray-item.injectable.ts b/src/main/tray/tray-menu-item/implementations/check-for-updates-tray-item.injectable.ts deleted file mode 100644 index 3abf232c33..0000000000 --- a/src/main/tray/tray-menu-item/implementations/check-for-updates-tray-item.injectable.ts +++ /dev/null @@ -1,39 +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 { computed } from "mobx"; -import showApplicationWindowInjectable from "../../../start-main-application/lens-window/show-application-window.injectable"; -import checkForUpdatesInjectable from "../../../check-for-updates.injectable"; -import isAutoUpdateEnabledInjectable from "../../../update-app/is-auto-update-enabled.injectable"; -import { trayMenuItemInjectionToken } from "../tray-menu-item-injection-token"; - -const checkForUpdatesTrayItemInjectable = getInjectable({ - id: "check-for-updates-tray-item", - - instantiate: (di) => { - const showApplicationWindow = di.inject(showApplicationWindowInjectable); - const checkForUpdates = di.inject(checkForUpdatesInjectable); - const isAutoUpdateEnabled = di.inject(isAutoUpdateEnabledInjectable); - - return { - id: "check-for-updates", - parentId: null, - orderNumber: 30, - label: "Check for updates", - enabled: computed(() => true), - visible: computed(() => isAutoUpdateEnabled()), - - click: async () => { - await checkForUpdates(); - - await showApplicationWindow(); - }, - }; - }, - - injectionToken: trayMenuItemInjectionToken, -}); - -export default checkForUpdatesTrayItemInjectable; diff --git a/src/main/update-app/check-for-platform-updates.injectable.ts b/src/main/update-app/check-for-platform-updates.injectable.ts new file mode 100644 index 0000000000..e3fa1386e9 --- /dev/null +++ b/src/main/update-app/check-for-platform-updates.injectable.ts @@ -0,0 +1,30 @@ +/** + * 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 electronUpdaterInjectable from "../electron-app/features/electron-updater.injectable"; +import type { UpdateChannel } from "./update-channels"; + +export type CheckForPlatformUpdates = (updateChannel: UpdateChannel) => Promise<{ updateWasDiscovered: boolean; version?: string }>; + +const checkForPlatformUpdatesInjectable = getInjectable({ + id: "check-for-platform-updates", + + instantiate: (di): CheckForPlatformUpdates => { + const electronUpdater = di.inject(electronUpdaterInjectable); + + return async (updateChannel) => { + electronUpdater.channel = updateChannel.id; + + await electronUpdater.checkForUpdates(); + + return { + updateWasDiscovered: true, + version: "42", + }; + }; + }, +}); + +export default checkForPlatformUpdatesInjectable; diff --git a/src/main/update-app/check-for-updates-tray-item.injectable.ts b/src/main/update-app/check-for-updates-tray-item.injectable.ts new file mode 100644 index 0000000000..1009edc408 --- /dev/null +++ b/src/main/update-app/check-for-updates-tray-item.injectable.ts @@ -0,0 +1,59 @@ +/** + * 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 updatingIsEnabledInjectable from "./updating-is-enabled.injectable"; +import { trayMenuItemInjectionToken } from "../tray/tray-menu-item/tray-menu-item-injection-token"; +import versionUpdateInjectable from "./version-update.injectable"; +import progressOfUpdateDownloadInjectable from "./progress-of-update-download.injectable"; +import showApplicationWindowInjectable from "../start-main-application/lens-window/show-application-window.injectable"; + +const checkForUpdatesTrayItemInjectable = getInjectable({ + id: "check-for-updates-tray-item", + + instantiate: (di) => { + const showApplicationWindow = di.inject(showApplicationWindowInjectable); + const updatingIsEnabled = di.inject(updatingIsEnabledInjectable); + const versionUpdate = di.inject(versionUpdateInjectable); + const progressOfUpdateDownload = di.inject(progressOfUpdateDownloadInjectable); + + return { + id: "check-for-updates", + parentId: null, + orderNumber: 30, + + label: computed(() => { + if (versionUpdate.downloading.get()) { + return `Downloading update "${versionUpdate.discoveredVersion.get()}" (${progressOfUpdateDownload.value.get()}%)...`; + } + + if (versionUpdate.checking.get()) { + return "Checking for updates..."; + } + + return "Check for updates"; + }), + + enabled: computed(() => !versionUpdate.checking.get() && !versionUpdate.downloading.get()), + + visible: computed(() => updatingIsEnabled), + + click: async () => { + const { updateWasDiscovered } = await versionUpdate.checkForUpdates(); + + if (updateWasDiscovered) { + versionUpdate.downloadUpdate(); + } + + + // await showApplicationWindow(); + }, + }; + }, + + injectionToken: trayMenuItemInjectionToken, +}); + +export default checkForUpdatesTrayItemInjectable; diff --git a/src/main/update-app/download-platform-update.injectable.ts b/src/main/update-app/download-platform-update.injectable.ts new file mode 100644 index 0000000000..bdffb524b7 --- /dev/null +++ b/src/main/update-app/download-platform-update.injectable.ts @@ -0,0 +1,15 @@ +/** + * 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"; + +const downloadPlatformUpdateInjectable = getInjectable({ + id: "download-platform-update", + + instantiate: () => { + return async () => {}; + }, +}); + +export default downloadPlatformUpdateInjectable; diff --git a/src/main/update-app/trigger-application-update-tray-item.injectable.ts b/src/main/update-app/install-application-update-tray-item.injectable.ts similarity index 54% rename from src/main/update-app/trigger-application-update-tray-item.injectable.ts rename to src/main/update-app/install-application-update-tray-item.injectable.ts index d6eee575bb..4ffde893dd 100644 --- a/src/main/update-app/trigger-application-update-tray-item.injectable.ts +++ b/src/main/update-app/install-application-update-tray-item.injectable.ts @@ -5,25 +5,34 @@ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; import { trayMenuItemInjectionToken } from "../tray/tray-menu-item/tray-menu-item-injection-token"; -import updateIsReadyToBeInstalledInjectable from "./update-is-ready-to-be-installed.injectable"; import quitAndInstallUpdateInjectable from "../electron-app/features/quit-and-install-update.injectable"; +import versionUpdateInjectable from "./version-update.injectable"; -const triggerApplicationUpdateTrayItemInjectable = getInjectable({ - id: "trigger-application-update-tray-item", +const installApplicationUpdateTrayItemInjectable = getInjectable({ + id: "install-update-tray-item", instantiate: (di) => { - const updateIsReadyToBeInstalled = di.inject(updateIsReadyToBeInstalledInjectable); const quitAndInstallUpdate = di.inject(quitAndInstallUpdateInjectable); + const versionUpdate = di.inject(versionUpdateInjectable); return { - id: "trigger-application-update", + id: "install-update", parentId: null, orderNumber: 50, - label: "Trigger update", - enabled: computed(() => true), - visible: computed(() => updateIsReadyToBeInstalled.get()), - click: () => { + label: computed(() => { + const versionToBeInstalled = versionUpdate.discoveredVersion.get(); + + return `Install update "${versionToBeInstalled}"`; + }), + + enabled: computed(() => true), + + visible: computed( + () => versionUpdate.discoveredVersion.get() && !versionUpdate.downloading.get(), + ), + + click: () => { quitAndInstallUpdate(); }, }; @@ -32,4 +41,4 @@ const triggerApplicationUpdateTrayItemInjectable = getInjectable({ injectionToken: trayMenuItemInjectionToken, }); -export default triggerApplicationUpdateTrayItemInjectable; +export default installApplicationUpdateTrayItemInjectable; diff --git a/src/main/update-app/progress-of-update-download.injectable.ts b/src/main/update-app/progress-of-update-download.injectable.ts new file mode 100644 index 0000000000..9ecf8602e6 --- /dev/null +++ b/src/main/update-app/progress-of-update-download.injectable.ts @@ -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 { action, computed, observable } from "mobx"; + +const progressOfUpdateDownloadInjectable = getInjectable({ + id: "progress-of-update-download", + + instantiate: () => { + const state = observable.box(0); + + return { + value: computed(() => state.get()), + + setValue: action((percentage: number) => { + state.set(percentage); + }), + }; + }, +}); + +export default progressOfUpdateDownloadInjectable; diff --git a/src/main/update-app/selected-update-channel.injectable.ts b/src/main/update-app/selected-update-channel.injectable.ts new file mode 100644 index 0000000000..5291baa240 --- /dev/null +++ b/src/main/update-app/selected-update-channel.injectable.ts @@ -0,0 +1,26 @@ +/** + * 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, computed, observable } from "mobx"; +import type { UpdateChannelId } from "./update-channels"; +import { updateChannels } from "./update-channels"; + +const selectedUpdateChannelInjectable = getInjectable({ + id: "selected-update-channel", + + instantiate: () => { + const state = observable.box(updateChannels.latest); + + return { + value: computed(() => state.get()), + + setValue: action((channelId: UpdateChannelId) => { + state.set(updateChannels[channelId]); + }), + }; + }, +}); + +export default selectedUpdateChannelInjectable; diff --git a/src/main/update-app/update-channels.ts b/src/main/update-app/update-channels.ts new file mode 100644 index 0000000000..1ad59a2504 --- /dev/null +++ b/src/main/update-app/update-channels.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +export type UpdateChannelId = "alpha" | "beta" | "latest"; + +export const updateChannels: Record = { + alpha: { + id: "alpha", + label: "Alpha", + }, + + beta: { + id: "beta", + label: "Beta", + }, + + latest: { + id: "latest", + label: "Stable", + }, +}; + +export interface UpdateChannel { + readonly id: UpdateChannelId; + readonly label: string; +} diff --git a/src/main/update-app/version-update.injectable.ts b/src/main/update-app/version-update.injectable.ts new file mode 100644 index 0000000000..55a1638aa9 --- /dev/null +++ b/src/main/update-app/version-update.injectable.ts @@ -0,0 +1,87 @@ +/** + * 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 { IComputedValue, IObservableValue } from "mobx"; +import { computed, observable, runInAction } from "mobx"; +import selectedUpdateChannelInjectable from "./selected-update-channel.injectable"; +import downloadPlatformUpdateInjectable from "./download-platform-update.injectable"; +import type { CheckForPlatformUpdates } from "./check-for-platform-updates.injectable"; +import checkForPlatformUpdatesInjectable from "./check-for-platform-updates.injectable"; +import type { UpdateChannel } from "./update-channels"; + +const versionUpdateInjectable = getInjectable({ + id: "version-update", + + instantiate: (di) => { + const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable); + const downloadPlatformUpdate = di.inject(downloadPlatformUpdateInjectable); + const checkForPlatformUpdates = di.inject(checkForPlatformUpdatesInjectable); + + const discoveredVersionState = observable.box(); + const downloadingState = observable.box(false); + const checkingState = observable.box(false); + + return { + discoveredVersion: computed(() => discoveredVersionState.get()), + downloading: computed(() => downloadingState.get()), + checking: computed(() => checkingState.get()), + + checkForUpdates: checkForUpdatesFor( + checkForPlatformUpdates, + discoveredVersionState, + checkingState, + selectedUpdateChannel.value, + ), + + downloadUpdate: downloadUpdateFor( + downloadPlatformUpdate, + downloadingState, + ), + }; + }, +}); + +export default versionUpdateInjectable; + +const downloadUpdateFor = + ( + downloadPlatformUpdate: () => Promise, + downloadingState: IObservableValue, + ) => + async () => { + runInAction(() => { + downloadingState.set(true); + }); + + await downloadPlatformUpdate(); + + runInAction(() => { + downloadingState.set(false); + }); + }; + +const checkForUpdatesFor = ( + checkForPlatformUpdates: CheckForPlatformUpdates, + discoveredVersionState: IObservableValue, + checkingState: IObservableValue, + selectedUpdateChannel: IComputedValue, +) => { + return async () => { + runInAction(() => { + checkingState.set(true); + }); + + const { updateWasDiscovered, version } = await checkForPlatformUpdates( + selectedUpdateChannel.get(), + ); + + runInAction(() => { + discoveredVersionState.set(version); + checkingState.set(false); + }); + + return { updateWasDiscovered }; + }; +};