Skip to content

Commit 1c2740d

Browse files
authored
[Ref] Use DNS text queries to resolve PCI IDs (#3528)
* Use DNS text queries to resolve PCI IDs * Clear the new cache stores if we're instructed to do so We only want to clear them if we're *not* upgrading Heroic though (don't want to cause a request spike when releasing an update)
1 parent 1ebd1d6 commit 1c2740d

File tree

6 files changed

+91
-89
lines changed

6 files changed

+91
-89
lines changed

src/backend/api/misc.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
} from 'common/types'
1010
import { NileRegisterData } from 'common/types/nile'
1111

12-
export const clearCache = (showDialog?: boolean) =>
13-
ipcRenderer.send('clearCache', showDialog)
12+
export const clearCache = (showDialog?: boolean, fromVersionChange?: boolean) =>
13+
ipcRenderer.send('clearCache', showDialog, fromVersionChange)
1414
export const resetHeroic = () => ipcRenderer.send('resetHeroic')
1515

1616
export const openWeblate = () => ipcRenderer.send('openWeblate')

src/backend/main.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,8 @@ ipcMain.handle('getCurrentChangelog', async () => {
714714
return getCurrentChangelog()
715715
})
716716

717-
ipcMain.on('clearCache', (event, showDialog?: boolean) => {
718-
clearCache()
717+
ipcMain.on('clearCache', (event, showDialog, fromVersionChange = false) => {
718+
clearCache(undefined, fromVersionChange)
719719
sendFrontendMessage('refreshLibrary')
720720

721721
if (showDialog) {

src/backend/utils.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ import EasyDl from 'easydl'
8484
import decompress from '@xhmikosr/decompress'
8585
import decompressTargz from '@xhmikosr/decompress-targz'
8686
import decompressTarxz from '@felipecrs/decompress-tarxz'
87+
import {
88+
deviceNameCache,
89+
vendorNameCache
90+
} from './utils/systeminfo/gpu/pci_ids'
8791

8892
const execAsync = promisify(exec)
8993

@@ -367,7 +371,10 @@ async function openUrlOrFile(url: string): Promise<string | void> {
367371
return shell.openPath(url)
368372
}
369373

370-
function clearCache(library?: 'gog' | 'legendary' | 'nile') {
374+
function clearCache(
375+
library?: 'gog' | 'legendary' | 'nile',
376+
fromVersionChange = false
377+
) {
371378
wikiGameInfoStore.clear()
372379
if (library === 'gog' || !library) {
373380
GOGapiInfoCache.clear()
@@ -387,6 +394,11 @@ function clearCache(library?: 'gog' | 'legendary' | 'nile') {
387394
nileInstallStore.clear()
388395
nileLibraryStore.clear()
389396
}
397+
398+
if (!fromVersionChange) {
399+
deviceNameCache.clear()
400+
vendorNameCache.clear()
401+
}
390402
}
391403

392404
function resetHeroic() {

src/backend/utils/systeminfo/gpu/pci_ids.ts

+72-82
Original file line numberDiff line numberDiff line change
@@ -2,112 +2,102 @@
22
* Contains helper functions to work with the `pci.ids` file
33
* ({@link https://pci-ids.ucw.cz})
44
*/
5-
import axios, { AxiosError } from 'axios'
6-
import path from 'path'
7-
8-
import { downloadFile, DAYS } from '../../inet/downloader'
9-
import { toolsPath } from 'backend/constants'
5+
import { resolveTxt } from 'dns'
6+
import { promisify } from 'util'
7+
import CacheStore from 'backend/cache'
108

119
import type { PartialGpuInfo } from './index'
1210
import type { GPUInfo } from '../index'
1311

14-
const pciIdsMap: Record<
15-
string,
16-
{
17-
vendorName: string
18-
devices: Record<
19-
string,
20-
{
21-
deviceName: string
22-
subsystems: Record<`${string} ${string}`, string>
23-
}
24-
>
25-
}
26-
> = {}
12+
const resolveTxtAsync = promisify(resolveTxt)
2713

28-
async function getPicIds(): Promise<typeof pciIdsMap | null> {
29-
if (Object.keys(pciIdsMap).length !== 0) return pciIdsMap
14+
type DeviceNameCacheKey =
15+
| `${string}_${string}_${string}_${string}` // DeviceSubID_VendorSubID_DeviceID_VendorID
16+
| `${string}_${string}` // DeviceID_VendorID
17+
export const deviceNameCache = new CacheStore<string, DeviceNameCacheKey>(
18+
'pci_ids_device',
19+
60 * 24 * 7 // 7 days
20+
)
21+
export const vendorNameCache = new CacheStore<string, string>(
22+
'pci_ids_vendor',
23+
60 * 24 * 7
24+
)
3025

31-
const pciIdsFile = await downloadFile('https://pci-ids.ucw.cz/v2.2/pci.ids', {
32-
axiosConfig: {
33-
responseType: 'text'
34-
},
35-
writeToFile: path.join(toolsPath, 'pci.ids'),
36-
maxCache: 30 * DAYS
37-
}).catch((error) => error as AxiosError)
38-
if (axios.isAxiosError(pciIdsFile)) return null
26+
async function pciIdDnsQuery(pre: string): Promise<string | false> {
27+
const records: string[][] = await resolveTxtAsync(
28+
`${pre}.pci.id.ucw.cz`
29+
).catch(() => [])
30+
return records[0]?.[0]?.replace(/^i=/, '') ?? false
31+
}
3932

40-
let currentVendor: string | null = null
41-
let currentDevice: string | null = null
42-
for (const line of pciIdsFile.split('\n')) {
43-
// Skip comments and empty lines
44-
if (line.startsWith('#')) continue
45-
if (line === '') continue
33+
async function lookupDeviceString(
34+
vendorId: string,
35+
deviceId: string,
36+
subvendorId?: string,
37+
subdeviceId?: string
38+
): Promise<string | false> {
39+
async function lookupKey(key: DeviceNameCacheKey): Promise<string | false> {
40+
const cached = deviceNameCache.get(key)
41+
if (cached) return cached
4642

47-
// Case 1: Line describes a new vendor
48-
const vendorMatch = line.match(/^(.{4}) {2}(.*)$/)
49-
const vendorId = vendorMatch?.[1]
50-
const vendorName = vendorMatch?.[2]
51-
if (vendorId && vendorName) {
52-
pciIdsMap[vendorId] = { vendorName, devices: {} }
53-
currentVendor = vendorId
54-
continue
55-
}
43+
const result = await pciIdDnsQuery(key.replaceAll('_', '.'))
44+
if (result) deviceNameCache.set(key, result)
45+
return result
46+
}
5647

57-
// Case 2: Line describes a new device
58-
const deviceMatch = line.match(/^\t(.{4}) {2}(.*)$/)
59-
const deviceId = deviceMatch?.[1]
60-
const deviceName = deviceMatch?.[2]
61-
if (deviceId && deviceName && currentVendor) {
62-
const vendorObj = pciIdsMap[currentVendor]
63-
if (!vendorObj) continue
64-
vendorObj.devices[deviceId] = { deviceName, subsystems: {} }
65-
currentDevice = deviceId
66-
continue
67-
}
48+
if (!subvendorId || !subdeviceId) return lookupKey(`${deviceId}_${vendorId}`)
6849

69-
// Case 3: Line describes a new subsystem
70-
const subsystemMatch = line.match(/\t\t(.{4}) (.{4}) {2}(.*)$/)
71-
if (!subsystemMatch) continue
72-
const [, subvendor, subdevice, subsystemName] = subsystemMatch
73-
if (
74-
subvendor &&
75-
subdevice &&
76-
subsystemName &&
77-
currentVendor &&
78-
currentDevice
79-
) {
80-
const deviceObj = pciIdsMap[currentVendor]?.devices[currentDevice]
81-
if (!deviceObj) continue
82-
deviceObj.subsystems[`${subvendor} ${subdevice}`] = subsystemName
83-
}
84-
}
50+
// If we have a subdevice and subvendor ID, try getting a name with them first
51+
const resultWithSubIDs = await lookupKey(
52+
`${subdeviceId}_${subvendorId}_${deviceId}_${vendorId}`
53+
)
54+
if (resultWithSubIDs) return resultWithSubIDs
8555

86-
return pciIdsMap
56+
// If there's no name for this specific subdevice and subvendor ID, try
57+
// with just the device and vendor ID
58+
const resultWithoutSubIDs = await lookupKey(`${deviceId}_${vendorId}`)
59+
// Update the cache entry with subIDs, to avoid re-requesting them all the
60+
// time
61+
if (resultWithoutSubIDs)
62+
deviceNameCache.set(
63+
`${subdeviceId}_${subvendorId}_${deviceId}_${vendorId}`,
64+
resultWithoutSubIDs
65+
)
66+
return resultWithoutSubIDs
67+
}
68+
69+
async function lookupVendorString(vendorId: string): Promise<string | false> {
70+
const cached = vendorNameCache.get(vendorId)
71+
if (cached) return cached
72+
73+
const result = await pciIdDnsQuery(vendorId.replaceAll('_', '.'))
74+
if (result) vendorNameCache.set(vendorId, result)
75+
return result
8776
}
8877

8978
async function populateDeviceAndVendorName(
9079
partialGpus: PartialGpuInfo[]
9180
): Promise<GPUInfo[]> {
92-
const pciIds = await getPicIds()
93-
if (pciIds === null) return partialGpus
94-
9581
const fullGpuInfo: GPUInfo[] = []
9682
for (const gpu of partialGpus) {
9783
const vendorId = gpu.vendorId.toLowerCase()
9884
const deviceId = gpu.deviceId.toLowerCase()
9985
const subvendorId = gpu.subvendorId?.toLowerCase()
10086
const subdeviceId = gpu.subdeviceId?.toLowerCase()
101-
const vendor = pciIds[vendorId]
102-
const device = pciIds[vendorId]?.devices[deviceId]
103-
const subsystem =
104-
pciIds[vendorId]?.devices[deviceId]?.subsystems[
105-
`${subvendorId} ${subdeviceId}`
106-
]
87+
88+
const deviceString = await lookupDeviceString(
89+
vendorId,
90+
deviceId,
91+
subvendorId,
92+
subdeviceId
93+
)
94+
const vendorString = await lookupVendorString(vendorId)
95+
if (!deviceString || !vendorString) continue
96+
10797
fullGpuInfo.push({
10898
...gpu,
109-
deviceString: subsystem ?? device?.deviceName,
110-
vendorString: vendor?.vendorName
99+
deviceString,
100+
vendorString
111101
})
112102
}
113103
return fullGpuInfo

src/common/typedefs/ipcBridge.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ interface SyncIPCFunctions {
8080
openCustomThemesWiki: () => void
8181
showConfigFileInFolder: (appName: string) => void
8282
removeFolder: ([path, folderName]: [string, string]) => void
83-
clearCache: (showDialog?: boolean) => void
83+
clearCache: (showDialog?: boolean, fromVersionChange?: boolean) => void
8484
resetHeroic: () => void
8585
createNewWindow: (url: string) => void
8686
logoutGOG: () => void

src/frontend/components/UI/Sidebar/components/HeroicVersion/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default React.memo(function HeroicVersion() {
3131
window.api.getHeroicVersion().then((version) => {
3232
if (version !== lastVersion) {
3333
window.api.logInfo('Updated to a new version, cleaaning up the cache.')
34-
window.api.clearCache(false)
34+
window.api.clearCache(false, true)
3535
}
3636
storage.setItem('last_version', JSON.stringify(version))
3737
setHeroicVersion(version)

0 commit comments

Comments
 (0)