Skip to content

Commit a5d8ccd

Browse files
committed
Revert "[TECH] Improve Patching process to avoid file and directory removal errors (#1246)"
This reverts commit 3ed2940.
1 parent 3ed2940 commit a5d8ccd

File tree

5 files changed

+20
-225
lines changed

5 files changed

+20
-225
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hyperplay",
3-
"version": "0.24.0",
3+
"version": "0.23.3",
44
"private": true,
55
"main": "build/main/main.js",
66
"homepage": "./",

src/backend/metrics/types.ts

-25
Original file line numberDiff line numberDiff line change
@@ -228,29 +228,6 @@ export interface PatchingFailed {
228228
sensitiveProperties?: never
229229
}
230230

231-
export interface PatchingAborted {
232-
event: 'Patching Aborted'
233-
properties: {
234-
game_name: string
235-
game_title: string
236-
platform: ReturnType<typeof getPlatformName>
237-
platform_arch: InstallPlatform
238-
}
239-
sensitiveProperties?: never
240-
}
241-
242-
export interface PatchingCleanupFailed {
243-
event: 'Patching Cleanup Failed'
244-
properties: {
245-
game_name: string
246-
error: string
247-
game_title: string
248-
platform?: ReturnType<typeof getPlatformName>
249-
platform_arch?: InstallPlatform
250-
}
251-
sensitiveProperties?: never
252-
}
253-
254231
export interface PatchingTooSlow {
255232
event: 'Patching Too Slow'
256233
properties: {
@@ -498,8 +475,6 @@ export type PossibleMetricPayloads =
498475
| PatchingStarted
499476
| PatchingSuccess
500477
| PatchingFailed
501-
| PatchingAborted
502-
| PatchingCleanupFailed
503478
| PatchingTooSlow
504479
| AccountDropdownPortfolioClicked
505480

src/backend/storeManagers/hyperplay/games.ts

+16-86
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ import {
6565
handleArchAndPlatform,
6666
handlePlatformReversed,
6767
runModPatcher,
68-
sanitizeVersion,
69-
safeRemoveDirectory
68+
sanitizeVersion
7069
} from './utils'
7170
import { getSettings as getSettingsSideload } from 'backend/storeManagers/sideload/games'
7271
import {
@@ -424,11 +423,11 @@ const findExecutables = async (folderPath: string): Promise<string[]> => {
424423
return executables
425424
}
426425

427-
export async function cleanUpDownload(appName: string, directory: string) {
426+
export function cleanUpDownload(appName: string, directory: string) {
428427
inProgressDownloadsMap.delete(appName)
429428
inProgressExtractionsMap.delete(appName)
430429
deleteAbortController(appName)
431-
await safeRemoveDirectory(directory)
430+
rmSync(directory, { recursive: true, force: true })
432431
}
433432

434433
function getDownloadUrl(platformInfo: PlatformConfig, appName: string) {
@@ -524,9 +523,9 @@ async function downloadGame(
524523
res()
525524
}
526525

527-
async function onCancel() {
526+
function onCancel() {
528527
try {
529-
await cleanUpDownload(appName, directory)
528+
cleanUpDownload(appName, directory)
530529
} catch (err) {
531530
rej(err)
532531
}
@@ -1182,7 +1181,7 @@ export async function extract(
11821181
body: `Installed`
11831182
})
11841183

1185-
await cleanUpDownload(appName, directory)
1184+
cleanUpDownload(appName, directory)
11861185

11871186
sendFrontendMessage('refreshLibrary', 'hyperplay')
11881187

@@ -1191,21 +1190,21 @@ export async function extract(
11911190
})
11921191
}
11931192
)
1194-
extractService.once('error', async (error: Error) => {
1193+
extractService.once('error', (error: Error) => {
11951194
logError(`Extracting Error ${error.message}`, LogPrefix.HyperPlay)
11961195

11971196
cancelQueueExtraction()
11981197
callAbortController(appName)
11991198

1200-
await cleanUpDownload(appName, directory)
1199+
cleanUpDownload(appName, directory)
12011200

12021201
sendFrontendMessage('refreshLibrary', 'hyperplay')
12031202

12041203
resolve({
12051204
status: 'error'
12061205
})
12071206
})
1208-
extractService.once('canceled', async () => {
1207+
extractService.once('canceled', () => {
12091208
logInfo(
12101209
`Canceled Extracting: Cancellation completed on ${appName} - Destination ${destinationPath}`,
12111210
LogPrefix.HyperPlay
@@ -1243,7 +1242,7 @@ export async function extract(
12431242
body: 'Installation Stopped'
12441243
})
12451244

1246-
await cleanUpDownload(appName, directory)
1245+
cleanUpDownload(appName, directory)
12471246

12481247
sendFrontendMessage('refreshLibrary', 'hyperplay')
12491248

@@ -1915,18 +1914,13 @@ async function applyPatching(
19151914

19161915
if (signal.aborted) {
19171916
logInfo(`Patching ${appName} aborted`, LogPrefix.HyperPlay)
1918-
await safeRemoveDirectory(datastoreDir, {
1919-
sizeThresholdMB: blockSize * totalBlocks
1920-
})
1921-
aborted = true
1917+
rmSync(datastoreDir, { recursive: true })
19221918
return { status: 'abort' }
19231919
}
19241920

1925-
signal.onabort = async () => {
1921+
signal.onabort = () => {
19261922
aborted = true
1927-
await safeRemoveDirectory(datastoreDir, {
1928-
sizeThresholdMB: blockSize * totalBlocks
1929-
})
1923+
rmSync(datastoreDir, { recursive: true })
19301924
return { status: 'abort' }
19311925
}
19321926

@@ -2011,36 +2005,7 @@ async function applyPatching(
20112005
}
20122006
// need this to cover 100% of abort cases
20132007
if (aborted) {
2014-
try {
2015-
await safeRemoveDirectory(datastoreDir, {
2016-
sizeThresholdMB: blockSize * totalBlocks
2017-
})
2018-
} catch (cleanupError) {
2019-
trackEvent({
2020-
event: 'Patching Cleanup Failed',
2021-
properties: {
2022-
error: `${cleanupError}`,
2023-
game_name: gameInfo.app_name,
2024-
game_title: gameInfo.title,
2025-
platform: getPlatformName(platform),
2026-
platform_arch: platform
2027-
}
2028-
})
2029-
2030-
logWarning(
2031-
`Patching aborted and cleanup failed: ${cleanupError}`,
2032-
LogPrefix.HyperPlay
2033-
)
2034-
}
2035-
trackEvent({
2036-
event: 'Patching Aborted',
2037-
properties: {
2038-
game_name: gameInfo.app_name,
2039-
game_title: gameInfo.title,
2040-
platform: getPlatformName(platform),
2041-
platform_arch: platform
2042-
}
2043-
})
2008+
rmSync(datastoreDir, { recursive: true })
20442009
return { status: 'abort' }
20452010
}
20462011

@@ -2055,27 +2020,8 @@ async function applyPatching(
20552020
})
20562021

20572022
logInfo(`Patching ${appName} completed`, LogPrefix.HyperPlay)
2058-
try {
2059-
await safeRemoveDirectory(datastoreDir, {
2060-
sizeThresholdMB: blockSize * totalBlocks
2061-
})
2062-
} catch (cleanupError) {
2063-
trackEvent({
2064-
event: 'Patching Cleanup Failed',
2065-
properties: {
2066-
error: `${cleanupError}`,
2067-
game_name: gameInfo.app_name,
2068-
game_title: gameInfo.title,
2069-
platform: getPlatformName(platform),
2070-
platform_arch: platform
2071-
}
2072-
})
2023+
rmSync(datastoreDir, { recursive: true })
20732024

2074-
logWarning(
2075-
`Patching succeeded but cleanup failed: ${cleanupError}`,
2076-
LogPrefix.HyperPlay
2077-
)
2078-
}
20792025
return { status: 'done' }
20802026
} catch (error) {
20812027
if (error instanceof PatchingError) {
@@ -2115,23 +2061,7 @@ async function applyPatching(
21152061

21162062
// errors can be thrown before datastore dir created. rmSync on nonexistent dir blocks indefinitely
21172063
if (existsSync(datastoreDir)) {
2118-
try {
2119-
await safeRemoveDirectory(datastoreDir)
2120-
} catch (cleanupError) {
2121-
trackEvent({
2122-
event: 'Patching Cleanup Failed',
2123-
properties: {
2124-
error: `${cleanupError}`,
2125-
game_name: gameInfo.app_name,
2126-
game_title: gameInfo.title
2127-
}
2128-
})
2129-
2130-
logWarning(
2131-
`Patching failed and cleanup failed: ${cleanupError}`,
2132-
LogPrefix.HyperPlay
2133-
)
2134-
}
2064+
rmSync(datastoreDir, { recursive: true })
21352065
}
21362066

21372067
return { status: 'error', error: `Error while patching ${error}` }

src/backend/storeManagers/hyperplay/utils.ts

+2-112
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@ import {
1616
valistListingsApiUrl
1717
} from 'backend/constants'
1818
import { getGameInfo } from './games'
19-
import { LogPrefix, logError, logInfo, logWarning } from 'backend/logger/logger'
19+
import { LogPrefix, logError, logInfo } from 'backend/logger/logger'
2020
import { join } from 'path'
21-
import { existsSync, rmSync } from 'graceful-fs'
21+
import { existsSync } from 'graceful-fs'
2222
import { ProjectMetaInterface } from '@valist/sdk/dist/typesShared'
2323
import getPartitionCookies from 'backend/utils/get_partition_cookies'
2424
import { DEV_PORTAL_URL } from 'common/constants'
25-
import { captureException } from '@sentry/electron'
2625

2726
export async function getHyperPlayStoreRelease(
2827
appName: string
@@ -420,112 +419,3 @@ export const runModPatcher = async (appName: string) => {
420419
throw new Error(`Error running patcher: ${error}`)
421420
}
422421
}
423-
424-
type SafeRemoveDirectoryOptions = {
425-
maxRetries?: number
426-
retryDelay?: number
427-
sizeThresholdMB?: number
428-
}
429-
430-
/**
431-
* Safely removes a directory with retry logic to handle Windows EPERM issues
432-
* @param directory Path to the directory to remove
433-
* @param options Optional configuration for removal
434-
* @param options.maxRetries Maximum number of removal attempts before giving up (default: 5)
435-
* @param options.retryDelay Delay in milliseconds between removal attempts (default: 10000)
436-
* @param options.sizeThresholdMB Threshold in MB above which async removal is used (default: 500)
437-
* @returns True if directory was successfully removed or doesn't exist, false otherwise
438-
* @warning This function MUST always be awaited to prevent race conditions
439-
*/
440-
export async function safeRemoveDirectory(
441-
directory: string,
442-
{
443-
maxRetries = 10,
444-
retryDelay = 6000,
445-
sizeThresholdMB = 500
446-
}: SafeRemoveDirectoryOptions = {}
447-
): Promise<boolean> {
448-
if (!existsSync(directory)) {
449-
return true // Directory doesn't exist, nothing to remove
450-
}
451-
452-
// Log start of removal process
453-
logInfo(`Starting removal of directory ${directory}`, LogPrefix.HyperPlay)
454-
455-
// Import fs promises for async operations only when needed
456-
const fsPromises = await import('fs/promises')
457-
458-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
459-
try {
460-
// Use different removal strategies based on expected size
461-
// For directories larger than threshold, use async removal to not block the main thread
462-
if (sizeThresholdMB > 250) {
463-
try {
464-
await fsPromises.rm(directory, {
465-
recursive: true,
466-
force: true,
467-
maxRetries: 3
468-
})
469-
} catch (asyncError) {
470-
// Fallback to sync if async fails
471-
logWarning(
472-
`Async removal failed, falling back to sync removal: ${asyncError}`,
473-
LogPrefix.HyperPlay
474-
)
475-
rmSync(directory, { recursive: true, force: true, maxRetries: 3 })
476-
}
477-
} else {
478-
// For smaller directories, use sync removal
479-
rmSync(directory, { recursive: true, force: true, maxRetries: 3 })
480-
}
481-
482-
// Verify directory was actually removed
483-
try {
484-
await fsPromises.access(directory)
485-
// If we get here, directory still exists
486-
logWarning(
487-
`Failed to remove directory ${directory} on attempt ${attempt}/${maxRetries}, directory still exists`,
488-
LogPrefix.HyperPlay
489-
)
490-
captureException(
491-
new Error(`Failed to remove directory, directory still exists`),
492-
{
493-
extra: {
494-
directory,
495-
attempts: attempt,
496-
method: 'safeRemoveDirectory'
497-
}
498-
}
499-
)
500-
} catch {
501-
// Directory doesn't exist (access threw an error), removal succeeded
502-
logInfo(
503-
`Successfully removed directory ${directory}`,
504-
LogPrefix.HyperPlay
505-
)
506-
return true
507-
}
508-
} catch (error) {
509-
logWarning(
510-
`Error removing directory ${directory} on attempt ${attempt}/${maxRetries}: ${error}`,
511-
LogPrefix.HyperPlay
512-
)
513-
}
514-
515-
// Use exponential backoff for retries (increases wait time with each attempt)
516-
if (attempt < maxRetries) {
517-
const backoffDelay = retryDelay * Math.pow(1.5, attempt - 1)
518-
logInfo(
519-
`Waiting ${backoffDelay}ms before retry ${attempt + 1}/${maxRetries}`,
520-
LogPrefix.HyperPlay
521-
)
522-
await new Promise((resolve) => setTimeout(resolve, backoffDelay))
523-
}
524-
}
525-
526-
logError(
527-
`Failed to remove directory ${directory} after ${maxRetries} attempts`,
528-
LogPrefix.HyperPlay
529-
)
530-
return false
531-
}

src/backend/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ export async function downloadFile(
983983
abortController: AbortController,
984984
progressCallback?: ProgressCallback,
985985
onCompleted?: (file: File) => void,
986-
onCancel?: (item: DownloadItem) => Promise<void>
986+
onCancel?: (item: DownloadItem) => void
987987
): Promise<DownloadItem> {
988988
let lastProgressUpdateTime = Date.now()
989989
let lastBytesWritten = 0

0 commit comments

Comments
 (0)