Skip to content

Commit 55a54e2

Browse files
authored
[Tech/Demo] Migrate Wine Manager status state to Zustand (#3428)
* Migrate Wine Manager status state to Zustand State logic in general was sort-of handled by the Backend and sort-of handled by the Frontend. This was changed to now only be the Backend's job * Fixup tests Other than the one required change (at the end), I've also removed the dependence of the current working directory being the repo's origin (which makes it possible to run just this one test directly in IDEs)
1 parent 01d32fc commit 55a54e2

File tree

14 files changed

+300
-291
lines changed

14 files changed

+300
-291
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@
100100
"steam-shortcut-editor": "3.1.3",
101101
"tslib": "2.5.0",
102102
"xvfb-maybe": "^0.2.1",
103-
"zod": "3.22.3"
103+
"zod": "3.22.3",
104+
"zustand": "^4.4.7"
104105
},
105106
"devDependencies": {
106107
"@electron/notarize": "^2.3.0",

pnpm-lock.yaml

+174-113
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/backend/api/wine.ts

+8-13
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ import {
33
RuntimeName,
44
ToolArgs,
55
WineVersionInfo,
6-
ProgressInfo,
7-
State,
8-
Runner
6+
Runner,
7+
type WineManagerStatus
98
} from 'common/types'
109

1110
export const toggleDXVK = async (args: ToolArgs) =>
@@ -27,11 +26,10 @@ export const showItemInFolder = (installDir: string) =>
2726
ipcRenderer.send('showItemInFolder', installDir)
2827
export const installWineVersion = async (
2928
release: WineVersionInfo
30-
): Promise<'error' | 'abort' | 'success'> =>
31-
ipcRenderer.invoke('installWineVersion', release)
29+
): Promise<void> => ipcRenderer.invoke('installWineVersion', release)
3230
export const removeWineVersion = async (
3331
release: WineVersionInfo
34-
): Promise<boolean> => ipcRenderer.invoke('removeWineVersion', release)
32+
): Promise<void> => ipcRenderer.invoke('removeWineVersion', release)
3533
export const refreshWineVersionInfo = async (fetch?: boolean): Promise<void> =>
3634
ipcRenderer.invoke('refreshWineVersionInfo', fetch)
3735

@@ -48,18 +46,15 @@ export const handleProgressOfWinetricks = (
4846
}
4947

5048
export const handleProgressOfWineManager = (
51-
version: string,
5249
callback: (
5350
e: Electron.IpcRendererEvent,
54-
progress: {
55-
state: State
56-
progress: ProgressInfo
57-
}
51+
version: string,
52+
progress: WineManagerStatus
5853
) => void
5954
): (() => void) => {
60-
ipcRenderer.on('progressOfWineManager' + version, callback)
55+
ipcRenderer.on('progressOfWineManager', callback)
6156
return () => {
62-
ipcRenderer.removeListener('progressOfWineManager' + version, callback)
57+
ipcRenderer.removeListener('progressOfWineManager', callback)
6358
}
6459
}
6560

src/backend/utils.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import {
77
Release,
88
GameInfo,
99
GameSettings,
10-
State,
11-
ProgressInfo,
1210
GameStatus
1311
} from 'common/types'
1412
import axios from 'axios'
@@ -89,6 +87,7 @@ import {
8987
deviceNameCache,
9088
vendorNameCache
9189
} from './utils/systeminfo/gpu/pci_ids'
90+
import type { WineManagerStatus } from 'common/types'
9291

9392
const execAsync = promisify(exec)
9493

@@ -898,11 +897,8 @@ export async function downloadDefaultWine() {
898897
}
899898

900899
// download the latest version
901-
const onProgress = (state: State, progress?: ProgressInfo) => {
902-
sendFrontendMessage(`progressOfWineManager${release.version}`, {
903-
state,
904-
progress
905-
})
900+
const onProgress = (state: WineManagerStatus) => {
901+
sendFrontendMessage('progressOfWineManager', release.version, state)
906902
}
907903
const result = await installWineVersion(release, onProgress)
908904

src/backend/wine/manager/downloader/__tests__/utilities/unzip.test.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ jest.mock('@xhmikosr/decompress-targz', () => {
1414
jest.mock('@felipecrs/decompress-tarxz', () => {
1515
return jest.fn().mockImplementation(() => {})
1616
})
17-
const workDir = process.cwd()
1817

1918
describe('Utilities - Unzip', () => {
2019
test('unzip file fails because of invalid archive path', async () => {
@@ -36,9 +35,7 @@ describe('Utilities - Unzip', () => {
3635
unzipDir: __dirname,
3736
onProgress: progress
3837
})
39-
).rejects.toStrictEqual(
40-
`Archive path ${workDir}/src/backend/wine/manager/downloader/__tests__/utilities is not a file!`
41-
)
38+
).rejects.toStrictEqual(`Archive path ${__dirname} is not a file!`)
4239
})
4340

4441
test('unzip file fails because of invalid install path', async () => {
@@ -67,7 +64,7 @@ describe('Utilities - Unzip', () => {
6764
onProgress: progress
6865
})
6966
).resolves.toStrictEqual(
70-
`Succesfully unzip ${workDir}/src/backend/wine/manager/downloader/__tests__/utilities/../test_data/test.tar.xz to ${workDir}/src/backend/wine/manager/downloader/__tests__/utilities/test_unzip.`
67+
`Succesfully unzip ${__dirname}/../test_data/test.tar.xz to ${installDir}.`
7168
)
7269

7370
if (existsSync(installDir)) {
@@ -90,7 +87,7 @@ describe('Utilities - Unzip', () => {
9087
onProgress: progress
9188
})
9289
).resolves.toStrictEqual(
93-
`Succesfully unzip ${workDir}/src/backend/wine/manager/downloader/__tests__/utilities/../test_data/test.tar.gz to ${workDir}/src/backend/wine/manager/downloader/__tests__/utilities/test_unzip.`
90+
`Succesfully unzip ${__dirname}/../test_data/test.tar.gz to ${installDir}.`
9491
)
9592

9693
if (existsSync(installDir)) {
@@ -113,7 +110,7 @@ describe('Utilities - Unzip', () => {
113110
onProgress: progress
114111
})
115112
).resolves.toStrictEqual(
116-
`Succesfully unzip ${workDir}/src/backend/wine/manager/downloader/__tests__/utilities/../test_data/test.tar.gz to ${workDir}/src/backend/wine/manager/downloader/__tests__/utilities/test_unzip.`
113+
`Succesfully unzip ${__dirname}/../test_data/test.tar.gz to ${installDir}.`
117114
)
118115

119116
await expect(
@@ -124,14 +121,14 @@ describe('Utilities - Unzip', () => {
124121
onProgress: progress
125122
})
126123
).resolves.toStrictEqual(
127-
`Succesfully unzip ${workDir}/src/backend/wine/manager/downloader/__tests__/utilities/../test_data/test.tar.gz to ${workDir}/src/backend/wine/manager/downloader/__tests__/utilities/test_unzip.`
124+
`Succesfully unzip ${__dirname}/../test_data/test.tar.gz to ${installDir}.`
128125
)
129126

130127
if (existsSync(installDir)) {
131128
rmSync(installDir, { recursive: true })
132129
}
133130

134-
expect(progress).toBeCalledWith('unzipping')
135-
expect(progress).toBeCalledWith('idle')
131+
expect(progress).toBeCalledWith({ status: 'unzipping' })
132+
expect(progress).toBeCalledWith({ status: 'idle' })
136133
})
137134
})

src/backend/wine/manager/downloader/main.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ import {
1717
WINECROSSOVER_URL,
1818
WINESTAGINGMACOS_URL
1919
} from './constants'
20-
import { VersionInfo, Repositorys, State, ProgressInfo } from 'common/types'
20+
import { VersionInfo, Repositorys } from 'common/types'
2121
import {
2222
fetchReleases,
2323
getFolderSize,
2424
unlinkFile,
2525
unzipFile
2626
} from './utilities'
2727
import { axiosClient, calculateEta, downloadFile } from 'backend/utils'
28+
import type { WineManagerStatus } from 'common/types'
2829

2930
interface getVersionsProps {
3031
repositorys?: Repositorys[]
@@ -150,7 +151,7 @@ interface installProps {
150151
versionInfo: VersionInfo
151152
installDir: string
152153
overwrite?: boolean
153-
onProgress?: (state: State, progress?: ProgressInfo) => void
154+
onProgress?: (state: WineManagerStatus) => void
154155
abortSignal?: AbortSignal
155156
}
156157

@@ -247,7 +248,8 @@ async function installVersion({
247248
versionInfo.downsize
248249
)
249250

250-
onProgress('downloading', {
251+
onProgress({
252+
status: 'downloading',
251253
percentage,
252254
eta: eta!,
253255
avgSpeed: downloadSpeed

src/backend/wine/manager/downloader/utilities.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { isMac } from '../../../constants'
22
import { existsSync, statSync, unlinkSync } from 'graceful-fs'
33
import { spawnSync } from 'child_process'
44

5-
import { ProgressInfo, State, VersionInfo, Type } from 'common/types'
5+
import { VersionInfo, Type, type WineManagerStatus } from 'common/types'
66
import { axiosClient, extractFiles } from 'backend/utils'
77

88
interface fetchProps {
@@ -116,7 +116,7 @@ interface unzipProps {
116116
filePath: string
117117
unzipDir: string
118118
overwrite?: boolean
119-
onProgress: (state: State, progress?: ProgressInfo) => void
119+
onProgress: (state: WineManagerStatus) => void
120120
abortSignal?: AbortSignal
121121
}
122122

@@ -151,15 +151,15 @@ async function unzipFile({
151151

152152
extractFiles({ path: filePath, destination: unzipDir, strip: 1 })
153153
.then(() => {
154-
onProgress('idle')
154+
onProgress({ status: 'idle' })
155155
resolve(`Succesfully unzip ${filePath} to ${unzipDir}.`)
156156
})
157157
.catch((error) => {
158-
onProgress('idle')
158+
onProgress({ status: 'idle' })
159159
reject(`Unzip of ${filePath} failed with:\n ${error}!`)
160160
})
161161

162-
onProgress('unzipping')
162+
onProgress({ status: 'unzipping' })
163163
})
164164
}
165165

+34-10
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,45 @@
11
import { ipcMain } from 'electron'
2-
import { ProgressInfo, State } from 'common/types'
32
import {
43
installWineVersion,
54
removeWineVersion,
65
updateWineVersionInfos
76
} from './utils'
87
import { logError, LogPrefix } from '../../logger/logger'
98
import { sendFrontendMessage } from '../../main_window'
9+
import type { WineManagerStatus } from 'common/types'
10+
import { notify } from '../../dialog/dialog'
11+
import { t } from 'i18next'
1012

1113
ipcMain.handle('installWineVersion', async (e, release) => {
12-
const onProgress = (state: State, progress?: ProgressInfo) => {
13-
sendFrontendMessage(`progressOfWineManager${release.version}`, {
14-
state,
15-
progress
16-
})
14+
const onProgress = (state: WineManagerStatus) => {
15+
sendFrontendMessage('progressOfWineManager', release.version, state)
1716
}
17+
18+
notify({ title: release.version, body: t('notify.install.startInstall') })
19+
onProgress({
20+
status: 'downloading',
21+
percentage: 0,
22+
avgSpeed: 0,
23+
eta: '00:00:00'
24+
})
25+
1826
const result = await installWineVersion(release, onProgress)
19-
return result
27+
28+
let notifyBody: string | null = null
29+
switch (result) {
30+
case 'error':
31+
notifyBody = t('notify.install.error')
32+
break
33+
case 'abort':
34+
notifyBody = t('notify.install.canceled')
35+
break
36+
case 'success':
37+
notifyBody = t('notify.install.finished')
38+
}
39+
if (notifyBody) notify({ title: release.version, body: notifyBody })
40+
onProgress({
41+
status: 'idle'
42+
})
2043
})
2144

2245
ipcMain.handle('refreshWineVersionInfo', async (e, fetch?) => {
@@ -29,6 +52,7 @@ ipcMain.handle('refreshWineVersionInfo', async (e, fetch?) => {
2952
}
3053
})
3154

32-
ipcMain.handle('removeWineVersion', async (e, release) =>
33-
removeWineVersion(release)
34-
)
55+
ipcMain.handle('removeWineVersion', async (e, release) => {
56+
const result = await removeWineVersion(release)
57+
if (result) notify({ title: release.version, body: t('notify.uninstalled') })
58+
})

src/backend/wine/manager/utils.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import { existsSync, mkdirSync, rmSync } from 'graceful-fs'
77
import { logError, logInfo, LogPrefix, logWarning } from '../../logger/logger'
88
import {
99
WineVersionInfo,
10-
ProgressInfo,
1110
Repositorys,
12-
State,
13-
VersionInfo
11+
VersionInfo,
12+
WineManagerStatus
1413
} from 'common/types'
1514

1615
import { getAvailableVersions, installVersion } from './downloader/main'
@@ -89,7 +88,7 @@ async function updateWineVersionInfos(
8988

9089
async function installWineVersion(
9190
release: WineVersionInfo,
92-
onProgress: (state: State, progress?: ProgressInfo) => void
91+
onProgress: (status: WineManagerStatus) => void
9392
) {
9493
let updatedInfo: WineVersionInfo
9594
const variant = release.hasUpdate ? 'update' : 'installation'

src/common/typedefs/ipcBridge.d.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,9 @@ interface AsyncIPCFunctions {
224224
}) => Promise<void>
225225
isNative: (args: { appName: string; runner: Runner }) => boolean
226226
getLogContent: (appNameOrRunner: string) => string
227-
installWineVersion: (
228-
release: WineVersionInfo
229-
) => Promise<'error' | 'abort' | 'success'>
227+
installWineVersion: (release: WineVersionInfo) => Promise<void>
230228
refreshWineVersionInfo: (fetch?: boolean) => Promise<void>
231-
removeWineVersion: (release: WineVersionInfo) => Promise<boolean>
229+
removeWineVersion: (release: WineVersionInfo) => Promise<void>
232230
shortcutsExists: (appName: string, runner: Runner) => boolean
233231
addToSteam: (appName: string, runner: Runner) => Promise<boolean>
234232
removeFromSteam: (appName: string, runner: Runner) => Promise<void>

src/common/types.ts

+3-13
Original file line numberDiff line numberDiff line change
@@ -714,19 +714,9 @@ export enum Repositorys {
714714
WINESTAGINGMACOS
715715
}
716716

717-
/**
718-
* Type for the progress callback state
719-
*/
720-
export type State = 'downloading' | 'unzipping' | 'idle'
721-
722-
/**
723-
* Interface for the information that progress callback returns
724-
*/
725-
export interface ProgressInfo {
726-
percentage: number
727-
avgSpeed: number
728-
eta: string
729-
}
717+
export type WineManagerStatus =
718+
| { status: 'idle' | 'unzipping' }
719+
| { status: 'downloading'; percentage: number; avgSpeed: number; eta: string }
730720

731721
export interface WineManagerUISettings {
732722
value: string

src/common/types/frontend_messages.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ import type {
66
DownloadManagerState,
77
GameInfo,
88
GameStatus,
9-
ProgressInfo,
109
RecentGame,
1110
Runner,
12-
State
11+
WineManagerStatus
1312
} from 'common/types'
1413

1514
type FrontendMessages = {
@@ -42,13 +41,10 @@ type FrontendMessages = {
4241
messages: string[]
4342
installingComponent: string
4443
}) => void
44+
progressOfWineManager: (version: string, progress: WineManagerStatus) => void
4545
'installing-winetricks-component': (component: string) => void
4646

4747
[key: `progressUpdate${string}`]: (progress: GameStatus) => void
48-
[key: `progressOfWineManager${string}`]: (progress: {
49-
state: State
50-
progress?: ProgressInfo
51-
}) => void
5248

5349
// Used inside tests, so we can be a bit lenient with the type checking here
5450
message: (...params: unknown[]) => void

0 commit comments

Comments
 (0)