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

Check prefix folder is correct before deleting it #3989

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 3 additions & 75 deletions src/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,7 @@ import {
import 'backend/updater'
import { autoUpdater } from 'electron-updater'
import { cpus } from 'os'
import {
existsSync,
rmSync,
watch,
readdirSync,
readFileSync
} from 'graceful-fs'
import { existsSync, watch, readdirSync, readFileSync } from 'graceful-fs'
import 'source-map-support/register'

import Backend from 'i18next-fs-backend'
Expand Down Expand Up @@ -60,6 +54,7 @@ import {
downloadDefaultWine,
sendGameStatusUpdate
} from './utils'
import { uninstallGameCallback } from './utils/uninstaller'
import {
configStore,
discordLink,
Expand Down Expand Up @@ -87,7 +82,6 @@ import {
createNecessaryFolders,
fixAsarPath,
isSnap,
fixesPath,
isWindows,
isMac
} from './constants'
Expand Down Expand Up @@ -144,7 +138,6 @@ import {
getGameOverride,
getGameSdl
} from 'backend/storeManagers/legendary/library'
import { storeMap } from 'common/utils'

app.commandLine?.appendSwitch('ozone-platform-hint', 'auto')

Expand Down Expand Up @@ -954,72 +947,7 @@ ipcMain.handle('openDialog', async (e, args) => {

ipcMain.on('showItemInFolder', async (e, item) => showItemInFolder(item))

ipcMain.handle(
'uninstall',
async (event, appName, runner, shouldRemovePrefix, shouldRemoveSetting) => {
sendGameStatusUpdate({
appName,
runner,
status: 'uninstalling'
})

const { title } = gameManagerMap[runner].getGameInfo(appName)

let uninstalled = false

try {
await gameManagerMap[runner].uninstall({ appName, shouldRemovePrefix })
uninstalled = true
} catch (error) {
notify({
title,
body: i18next.t('notify.uninstalled.error', 'Error uninstalling')
})
logError(error, LogPrefix.Backend)
}

if (uninstalled) {
if (shouldRemovePrefix) {
const { winePrefix } = await gameManagerMap[runner].getSettings(appName)
logInfo(`Removing prefix ${winePrefix}`, LogPrefix.Backend)
// remove prefix if exists
if (existsSync(winePrefix)) {
rmSync(winePrefix, { recursive: true })
}
}
if (shouldRemoveSetting) {
const removeIfExists = (filename: string) => {
logInfo(`Removing ${filename}`, LogPrefix.Backend)
const gameSettingsFile = join(gamesConfigPath, filename)
if (existsSync(gameSettingsFile)) {
rmSync(gameSettingsFile)
}
}

removeIfExists(appName.concat('.json'))
removeIfExists(appName.concat('.log'))
removeIfExists(appName.concat('-lastPlay.log'))
}

const fixFilePath = path.join(
fixesPath,
`${appName}-${storeMap[runner]}.json`
)
if (existsSync(fixFilePath)) {
rmSync(fixFilePath)
}

notify({ title, body: i18next.t('notify.uninstalled') })
logInfo('Finished uninstalling', LogPrefix.Backend)
}

sendGameStatusUpdate({
appName,
runner,
status: 'done'
})
}
)
ipcMain.handle('uninstall', uninstallGameCallback)

ipcMain.handle('repair', async (event, appName, runner) => {
if (!isOnline()) {
Expand Down
8 changes: 2 additions & 6 deletions src/backend/storeManagers/sideload/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { notify } from '../../dialog/dialog'
import { launchGame } from 'backend/storeManagers/storeManagerCommon/games'
import { GOGCloudSavesLocation } from 'common/types/gog'
import { InstallResult, RemoveArgs } from 'common/types/game_manager'
import { removePrefix } from 'backend/utils/uninstaller'

export function getGameInfo(appName: string): GameInfo {
const store = libraryStore.get('games', [])
Expand Down Expand Up @@ -110,14 +111,9 @@ export async function uninstall({
title,
install: { executable }
} = gameInfo
const { winePrefix } = await getSettings(appName)

if (shouldRemovePrefix) {
logInfo(`Removing prefix ${winePrefix}`, LogPrefix.Backend)
if (existsSync(winePrefix)) {
// remove prefix if exists
rmSync(winePrefix, { recursive: true })
}
removePrefix(appName, 'sideload')
}
libraryStore.set('games', current)

Expand Down
126 changes: 126 additions & 0 deletions src/backend/utils/uninstaller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { GlobalConfig } from 'backend/config'
import { fixesPath, gamesConfigPath } from 'backend/constants'
import { notify } from 'backend/dialog/dialog'
import { logError, logInfo, LogPrefix } from 'backend/logger/logger'
import { gameManagerMap } from 'backend/storeManagers'
import { sendGameStatusUpdate } from 'backend/utils'
import { Runner } from 'common/types'
import { storeMap } from 'common/utils'
import { Event } from 'electron'
import { existsSync, readdirSync, rmSync } from 'graceful-fs'
import i18next from 'i18next'
import { join } from 'path'

export const removePrefix = async (appName: string, runner: Runner) => {
const { winePrefix } = await gameManagerMap[runner].getSettings(appName)
logInfo(`Removing prefix ${winePrefix}`, LogPrefix.Backend)

if (!existsSync(winePrefix)) {
logInfo(`Prefix folder ${winePrefix} doesn't exist, ignoring removal`)
return
}

// folder exists, do some sanity checks before deleting it
const { defaultInstallPath, defaultWinePrefix } =
GlobalConfig.get().getSettings()

if (winePrefix === defaultInstallPath) {
logInfo(
`Can't delete folder ${winePrefix}, prefix folder is the default install directory ${defaultInstallPath}`
)
return
}

if (winePrefix === defaultWinePrefix) {
logInfo(
`Can't delete folder ${winePrefix}, prefix folder is the default prefix directory ${defaultWinePrefix}`
)
return
}

const dirContent = readdirSync(winePrefix)

if (dirContent.length > 0) {
const driveCPath = join(winePrefix, 'drive_c')
const pfxPath = join(winePrefix, 'pfx')

if (!existsSync(driveCPath) && !existsSync(pfxPath)) {
logInfo(
`Can't delete folder ${winePrefix}, folder does not contain a drive_c/pfx folder. If this is the correct prefix folder, delete it manually.`
)
return
}
}

// if we got here, we are safe to delete this folder
rmSync(winePrefix, { recursive: true })
}

const removeFixFile = (appName: string, runner: Runner) => {
const fixFilePath = join(fixesPath, `${appName}-${storeMap[runner]}.json`)
if (existsSync(fixFilePath)) {
rmSync(fixFilePath)
}
}

const removeSettingsAndLogs = (appName: string) => {
const removeIfExists = (filename: string) => {
logInfo(`Removing ${filename}`, LogPrefix.Backend)
const gameSettingsFile = join(gamesConfigPath, filename)
if (existsSync(gameSettingsFile)) {
rmSync(gameSettingsFile)
}
}

removeIfExists(appName.concat('.json'))
removeIfExists(appName.concat('.log'))
removeIfExists(appName.concat('-lastPlay.log'))
}

export const uninstallGameCallback = async (
event: Event,
appName: string,
runner: Runner,
shouldRemovePrefix: boolean,
shouldRemoveSetting: boolean
) => {
sendGameStatusUpdate({
appName,
runner,
status: 'uninstalling'
})

const { title } = gameManagerMap[runner].getGameInfo(appName)

let uninstalled = false

try {
await gameManagerMap[runner].uninstall({ appName, shouldRemovePrefix })
uninstalled = true
} catch (error) {
notify({
title,
body: i18next.t('notify.uninstalled.error', 'Error uninstalling')
})
logError(error, LogPrefix.Backend)
}

if (uninstalled) {
if (shouldRemovePrefix) {
removePrefix(appName, runner)
}
if (shouldRemoveSetting) {
removeSettingsAndLogs(appName)
}
removeFixFile(appName, runner)

notify({ title, body: i18next.t('notify.uninstalled') })
logInfo('Finished uninstalling', LogPrefix.Backend)
}

sendGameStatusUpdate({
appName,
runner,
status: 'done'
})
}
Loading