Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Modes #2866

Merged
merged 25 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/zui/dev-app-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
owner: brimdata
repo: zui
provider: github
updaterCacheDirName: zui-dev-updater
19 changes: 19 additions & 0 deletions apps/zui/pages/update.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, {useEffect, useState} from "react"
import {AppProvider} from "src/app/core/context"
import initialize from "src/js/initializers/initialize"
import {UpdateWindow} from "src/views/update-window"

export default function Update() {
const [app, setApp] = useState(null)

useEffect(() => {
initialize().then((app) => setApp(app))
}, [])

if (!app) return null
return (
<AppProvider store={app.store} api={app.api}>
<UpdateWindow />
</AppProvider>
)
}
54 changes: 54 additions & 0 deletions apps/zui/public/zui-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions apps/zui/src/app/core/env.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {app as electronApp} from "electron"
import pkg from "src/electron/pkg"

const isPackaged = () =>
electronApp.isPackaged || process.env.NODE_ENV === "production"
Expand Down Expand Up @@ -31,4 +32,7 @@ export default {
get isLinux() {
return process.platform === "linux"
},
get isInsiders() {
return pkg.name === "zui-insiders"
},
}
2 changes: 1 addition & 1 deletion apps/zui/src/app/core/links.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const fetchStatusCode = async (link: string): Promise<[string, number]> => {
const controller = new AbortController()
const timeout = setTimeout(() => {
controller.abort()
}, 1000)
}, 10_000)

try {
const resp = await fetch(link, {signal: controller.signal})
Expand Down
2 changes: 2 additions & 0 deletions apps/zui/src/components/forms.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
color: white;
font-weight: 500;
min-width: 80px;
user-select: none;
}

.form .submit:hover:not(:disabled) {
Expand All @@ -110,6 +111,7 @@
box-shadow: 0 3px 4px -4px var(--foreground-color-light);
font-weight: 500;
min-width: 80px;
user-select: none;
}

.form .button:hover:not(:disabled) {
Expand Down
5 changes: 5 additions & 0 deletions apps/zui/src/core/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import electronLog from "electron-log"

export const info = electronLog.info
export const debug = electronLog.debug
export const error = electronLog.error
2 changes: 0 additions & 2 deletions apps/zui/src/core/main/main-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,6 @@ export class MainObject {
return this.store.dispatch as AppDispatch
}

select

getPath(name: PathName) {
return getPath(name)
}
Expand Down
22 changes: 22 additions & 0 deletions apps/zui/src/core/on-state-change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {Selector} from "@reduxjs/toolkit"
import {Store} from "src/js/state/types"

export function onStateChange(
store: Store,
selector: Selector,
onChange: (value: any) => void
) {
let current = undefined

function listener() {
const next = selector(store.getState())
if (next !== current) {
current = next
onChange(current)
}
}

const unsubscribe = store.subscribe(listener)
listener()
return unsubscribe
}
4 changes: 4 additions & 0 deletions apps/zui/src/core/operations.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import {ipcMain, IpcMainInvokeEvent} from "electron"
import {OperationName} from "src/domain/messages"
import {MainObject} from "./main/main-object"
import {Dispatch} from "src/js/state/types"
import {select} from "./main/select"

type OperationContext = {
dispatch: Dispatch
main: MainObject
event?: IpcMainInvokeEvent | null
select: typeof select
}

let context: OperationContext | null = null
Expand Down
3 changes: 3 additions & 0 deletions apps/zui/src/domain/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {WindowHandlers, WindowOperations} from "./window/messages"
import {LegacyOperations} from "./legacy-ops/messages"
import {E2EOperations} from "./e2e/messages"
import {EnvOperations} from "./env/messages"
import {UpdatesOperations} from "./updates/messages"
import {LoadsHandlers, LoadsOperations} from "./loads/messages"

export type Handlers = ResultsHandlers &
Expand All @@ -22,6 +23,8 @@ export type Operations = PoolsOperations &
E2EOperations &
ResultsOperations &
EnvOperations &
WindowOperations &
UpdatesOperations &
LoadsOperations &
WindowOperations

Expand Down
Empty file.
45 changes: 45 additions & 0 deletions apps/zui/src/domain/updates/linux-updater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {app, shell} from "electron"
import fetch from "node-fetch"
import semver from "semver"
import env from "src/app/core/env"
import links from "src/app/core/links"
import pkg from "src/electron/pkg"
import {Updater} from "./types"
import {getMainObject} from "src/core/main"

export class LinuxUpdater implements Updater {
async check() {
const latest = await this.latest()
const current = app.getVersion()
if (semver.lt(current, latest)) {
return latest
} else {
return null
}
}

async install() {
shell.openExternal(this.downloadUrl())
}

private async latest() {
const resp = await fetch(this.latestUrl())
if (resp.status === 204) return app.getVersion()
const data = await resp.json()
return data.name
}

private latestUrl() {
const repo = getMainObject().appMeta.repo
const platform = "darwin-x64" // If the mac version exists, the linux does too
return `https://update.electronjs.org/${repo}/${platform}/${app.getVersion()}`
}

private downloadUrl() {
if (env.isInsiders) {
return pkg.repository + "/releases/latest"
} else {
return links.ZUI_DOWNLOAD
}
}
}
43 changes: 43 additions & 0 deletions apps/zui/src/domain/updates/mac-win-updater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {autoUpdater} from "electron-updater"
import {Updater} from "./types"
import semver from "semver"
import {app} from "electron"
import {getMainObject} from "src/core/main"

autoUpdater.autoDownload = false
autoUpdater.autoInstallOnAppQuit = false

export class MacWinUpdater implements Updater {
async check() {
const {updateInfo} = await autoUpdater.checkForUpdates()
const latest = updateInfo.version
const current = app.getVersion()
if (semver.lt(current, latest)) {
return latest
} else {
return null
}
}

async install(onProgress) {
const progress = (r) => {
onProgress(r.percent / 100)
}
autoUpdater.on("error", (e) => {
throw e
})
autoUpdater.on("download-progress", progress)

return new Promise((resolve, reject) => {
autoUpdater.on("update-downloaded", resolve)
autoUpdater.on("error", reject)
autoUpdater.downloadUpdate()
}).then(() => {
// `autoUpdater.quitAndInstall()` will close all application windows first and only emit `before-quit` event on `app` after that.
// We have some logic when closing windows that checks to see if we are quitting or not.
// So we call onBeforeQuit manually here to tell the main object we are quitting
getMainObject().onBeforeQuit()
autoUpdater.quitAndInstall()
})
}
}
7 changes: 7 additions & 0 deletions apps/zui/src/domain/updates/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as operations from "./operations"

export type UpdatesOperations = {
"updates.open": typeof operations.open
"updates.check": typeof operations.check
"updates.install": typeof operations.install
}
50 changes: 50 additions & 0 deletions apps/zui/src/domain/updates/operations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {createOperation} from "src/core/operations"
import {updater} from "./updater"
import Updates from "src/js/state/Updates"
import {errorToString} from "src/util/error-to-string"
import {info} from "src/core/log"

export const open = createOperation("updates.open", ({main}) => {
check()
main.windows.activate("update")
})

export const check = createOperation(
"updates.check",
async ({main, dispatch}) => {
try {
info("Checking for Updates...")
dispatch(Updates.setIsChecking(true))
const newVersion = await updater.check()
if (newVersion) {
info("New Version Found: " + newVersion)
dispatch(Updates.setNextVersion(newVersion))
main.windows.activate("update")
}
} catch (e) {
info("Error Checking for Update: " + errorToString(e))
dispatch(Updates.setError(errorToString(e)))
} finally {
dispatch(Updates.setIsChecking(false))
}
}
)

export const install = createOperation(
"updates.install",
async ({dispatch, main}) => {
info("Installing Update")
const onProgress = (n: number) => dispatch(Updates.setDownloadProgress(n))
try {
dispatch(Updates.setIsDownloading(true))
dispatch(Updates.setDownloadProgress(0))
await updater.install(onProgress)
main.windows.byName("update").forEach((w) => w.close())
} catch (e) {
info("Error Installing")
dispatch(Updates.setError(errorToString(e)))
} finally {
dispatch(Updates.setIsDownloading(false))
}
}
)
34 changes: 34 additions & 0 deletions apps/zui/src/domain/updates/scheduler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {UpdateMode} from "./types"

export class Scheduler {
static interval = 1000 * 60 * 60 * 24 // 1 day

start(mode: UpdateMode, check: () => any, args: {delay?: number} = {}) {
switch (mode) {
case "default":
this.delay(check, args.delay)
this.schedule(check)
break
case "startup":
this.delay(check, args.delay)
}
}

private delayedId: any
private delay(check, ms = 0) {
this.delayedId = setTimeout(check, ms)
}

private scheduleId: any
private schedule(check: () => any) {
this.scheduleId = setTimeout(() => {
check()
this.schedule(check)
}, Scheduler.interval)
}

stop() {
clearTimeout(this.delayedId)
clearTimeout(this.scheduleId)
}
}
6 changes: 6 additions & 0 deletions apps/zui/src/domain/updates/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type UpdateMode = "manual" | "startup" | "default"

export interface Updater {
check(): Promise<string | null>
install(onProgress: (percent: number) => void): Promise<void>
}
5 changes: 5 additions & 0 deletions apps/zui/src/domain/updates/updater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import env from "src/app/core/env"
import {LinuxUpdater} from "./linux-updater"
import {MacWinUpdater} from "./mac-win-updater"

export const updater = env.isLinux ? new LinuxUpdater() : new MacWinUpdater()
Loading
Loading