Skip to content

Commit

Permalink
Good Updaters for Linux and Others
Browse files Browse the repository at this point in the history
  • Loading branch information
jameskerr committed Nov 1, 2023
1 parent a732ed6 commit 0f4c1b9
Show file tree
Hide file tree
Showing 25 changed files with 234 additions and 294 deletions.
7 changes: 2 additions & 5 deletions apps/zui/pages/update.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import React, {useEffect, useState} from "react"
import {AppProvider} from "src/app/core/context"
import {invoke} from "src/core/invoke"
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)
invoke("updates.check")
})
initialize().then((app) => setApp(app))
}, [])

if (!app) return null
Expand Down
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"
},
}
17 changes: 17 additions & 0 deletions apps/zui/src/core/on-state-change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {Selector} from "@reduxjs/toolkit"
import {Store} from "src/js/state/types"

export function onStateChange(
store: Store,
selector: Selector,
onChange: (value: any) => void
) {
let first = true
let prev = undefined
store.subscribe(() => {
const next = selector(store.getState())
if (prev !== next || first) onChange(next)
prev = next
first = false
})
}
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
107 changes: 0 additions & 107 deletions apps/zui/src/domain/updates/app-updater.ts

This file was deleted.

44 changes: 44 additions & 0 deletions apps/zui/src/domain/updates/linux-updater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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"

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

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 = pkg.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.repo + "/releases/latest"
} else {
return links.ZUI_DOWNLOAD
}
}
}
46 changes: 46 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,46 @@
import {autoUpdater} from "electron-updater"
import {Updater} from "./types"
import semver from "semver"
import {app} from "electron"

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.lte(current, latest)) {
return latest
} else {
return null
}
}

async install(onProgress) {
const progress = (r) => {
onProgress(r.percent / 100)
}

let resolve
let reject

return new Promise((res, rej) => {
resolve = res
reject = rej
autoUpdater.on("update-downloaded", resolve)
autoUpdater.on("download-progress", progress)
autoUpdater.on("error", reject)
autoUpdater.downloadUpdate()
})
.finally(() => {
autoUpdater.off("download-progress", onProgress)
autoUpdater.off("update-downloaded", resolve)
autoUpdater.off("error", reject)
})
.then(() => {
autoUpdater.quitAndInstall()
})
}
}
2 changes: 1 addition & 1 deletion apps/zui/src/domain/updates/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import * as operations from "./operations"
export type UpdatesOperations = {
"updates.open": typeof operations.open
"updates.check": typeof operations.check
"updates.downloadAndInstall": typeof operations.downloadAndInstall
"updates.install": typeof operations.install
}
60 changes: 28 additions & 32 deletions apps/zui/src/domain/updates/operations.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,38 @@
import {createOperation} from "src/core/operations"
import {appUpdater} from "./app-updater"
import {updater} from "./updater"
import Updates from "src/js/state/Updates"
import {errorToString} from "src/util/error-to-string"

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

export const downloadAndInstall = createOperation(
"updates.downloadAndInstall",
async () => {
export const check = createOperation(
"updates.check",
async ({main, dispatch}) => {
dispatch(Updates.setIsChecking(true))
const newVersion = await updater.check()
dispatch(Updates.setIsChecking(false))

if (newVersion) {
dispatch(Updates.setNextVersion(newVersion))
main.windows.activate("update")
}
}
)

export const install = createOperation(
"updates.install",
async ({dispatch}) => {
const onProgress = (n: number) => dispatch(Updates.setDownloadProgress(n))
try {
await appUpdater.download()
appUpdater.install()
dispatch(Updates.setIsDownloading(true))
dispatch(Updates.setDownloadProgress(0))
await updater.install(onProgress)
} catch (e) {
console.log("Error", e)
dispatch(Updates.setError(errorToString(e)))
} finally {
dispatch(Updates.setIsDownloading(false))
}
}
)

// MANUAL FLOW
// 1. user click check for updates
// 2. app triggers update check
// 3. app opens update window

// 4. update window checks state
// 5. renders progress bar if state.isChecking
// 8. renders up to date if state.isUpToDate
// 9. renders new version available if state.newVersionAvailable
// 10. Button to Install
// 11. Button to Cancel
// 12. Note that you can change update settings in Settings

// AUTO FLOW
// app auto checks on startup,
// app downloads update in the background
// app waits until restart to install
// app udates menu with "New Version Available..."
// when the auu quits it will be updated
28 changes: 28 additions & 0 deletions apps/zui/src/domain/updates/scheduler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {UpdateMode} from "./types"

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

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

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

stop() {
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 = "disabled" | "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

0 comments on commit 0f4c1b9

Please sign in to comment.