First build
This commit is contained in:
parent
511556737e
commit
fd773f5832
954 changed files with 305115 additions and 2 deletions
192
src/wines.js
Normal file
192
src/wines.js
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const fsp = fs.promises
|
||||
const path = require('path')
|
||||
const { DIRS, readJson, writeJson } = require('./paths')
|
||||
|
||||
const WINES_DIR = DIRS.WINES
|
||||
const REGISTRY = path.join(DIRS.DATA, 'wines.json')
|
||||
const DEFAULT_KEY = 'defaultWine'
|
||||
|
||||
function exists(p) {
|
||||
try {
|
||||
fs.accessSync(p)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function detectType(dir) {
|
||||
if (exists(path.join(dir, 'proton'))) return 'proton'
|
||||
for (const rel of ['files/bin/wine', 'bin/wine', 'dist/bin/wine']) {
|
||||
if (exists(path.join(dir, rel))) return 'wine'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function findWineBinary(dir) {
|
||||
for (const rel of ['files/bin/wine', 'bin/wine', 'dist/bin/wine']) {
|
||||
const p = path.join(dir, rel)
|
||||
if (exists(p)) return p
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function versionOf(wineBin, env) {
|
||||
try {
|
||||
const { execFileSync } = require('child_process')
|
||||
const out = execFileSync(wineBin, ['--version'], {
|
||||
timeout: 15000,
|
||||
env: { ...process.env, ...env, WINEPREFIX: path.join(DIRS.DATA, 'probe-prefix') }
|
||||
})
|
||||
return String(out).trim().replace(/^wine-?/i, '')
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function labelFor(type, dir, version) {
|
||||
const name = path.basename(dir)
|
||||
if (type === 'proton') return /GE|CachyOS|TKG/i.test(name) ? name : (version ? `Proton ${version}` : name)
|
||||
return version ? `Wine ${version}` : name
|
||||
}
|
||||
|
||||
async function scanBuild(dir, depth) {
|
||||
const type = detectType(dir)
|
||||
if (type) {
|
||||
const entry = {
|
||||
id: path.basename(dir).toLowerCase().replace(/[^a-z0-9._-]+/g, '-'),
|
||||
name: path.basename(dir),
|
||||
path: dir,
|
||||
type
|
||||
}
|
||||
if (type === 'proton') {
|
||||
entry.wineBin = path.join(dir, 'proton')
|
||||
} else if (type === 'wine') {
|
||||
const bin = findWineBinary(dir)
|
||||
if (bin) {
|
||||
entry.wineBin = bin
|
||||
const ver = versionOf(bin, {})
|
||||
if (ver) entry.version = ver
|
||||
}
|
||||
}
|
||||
entry.label = labelFor(type, dir, entry.version)
|
||||
return [entry]
|
||||
}
|
||||
if (depth <= 0) return []
|
||||
const out = []
|
||||
let names = []
|
||||
try {
|
||||
names = await fsp.readdir(dir)
|
||||
} catch {
|
||||
return out
|
||||
}
|
||||
for (const name of names) {
|
||||
const sub = path.join(dir, name)
|
||||
let st
|
||||
try {
|
||||
st = await fsp.stat(sub)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
if (st.isDirectory() && name !== 'wineprefixes' && name !== 'share') {
|
||||
out.push(...(await scanBuild(sub, depth - 1)))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
async function refreshWines() {
|
||||
let found = []
|
||||
try {
|
||||
found = await scanBuild(WINES_DIR, 2)
|
||||
} catch {
|
||||
found = []
|
||||
}
|
||||
const prev = readJson(REGISTRY, [])
|
||||
const disabled = new Set(prev.filter(w => w.disabled).map(w => w.id))
|
||||
for (const w of found) if (disabled.has(w.id)) w.disabled = true
|
||||
const merged = [...found]
|
||||
for (const p of prev) {
|
||||
if (p.system) merged.push(p)
|
||||
else if (!found.some(f => f.id === p.id) && exists(p.path)) merged.push(p)
|
||||
}
|
||||
writeJson(REGISTRY, merged)
|
||||
return merged
|
||||
}
|
||||
|
||||
function getWines() {
|
||||
return readJson(REGISTRY, [])
|
||||
}
|
||||
|
||||
function getWine(id) {
|
||||
return getWines().find(w => w.id === id) || null
|
||||
}
|
||||
|
||||
function setWineDisabled(id, disabled) {
|
||||
const wines = getWines()
|
||||
const w = wines.find(x => x.id === id)
|
||||
if (w) {
|
||||
w.disabled = !!disabled
|
||||
writeJson(REGISTRY, wines)
|
||||
}
|
||||
return wines
|
||||
}
|
||||
|
||||
function getDefaultWine() {
|
||||
return require('./library').loadSettings().defaultWine || 'system'
|
||||
}
|
||||
|
||||
function setDefaultWine(id) {
|
||||
const wines = getWines()
|
||||
if (id !== 'system' && !wines.some(w => w.id === id)) return { ok: false, error: 'notFound' }
|
||||
require('./library').saveSettings({ defaultWine: id })
|
||||
return { ok: true, defaultWine: id }
|
||||
}
|
||||
|
||||
function deleteWine(id) {
|
||||
const wines = getWines()
|
||||
const w = wines.find(x => x.id === id)
|
||||
if (!w || w.system) return { ok: false, error: w ? 'isSystem' : 'notFound' }
|
||||
try {
|
||||
fs.rmSync(w.path, { recursive: true, force: true })
|
||||
} catch {}
|
||||
writeJson(REGISTRY, wines.filter(x => x.id !== id))
|
||||
if (getDefaultWine() === id) require('./library').saveSettings({ defaultWine: 'system' })
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
function dedupeDir(base, name) {
|
||||
let candidate = path.join(base, name)
|
||||
let n = 2
|
||||
while (fs.existsSync(candidate)) {
|
||||
candidate = path.join(base, `${name} (${n})`)
|
||||
n++
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
|
||||
async function importWineFolder(srcDir) {
|
||||
const type = detectType(srcDir)
|
||||
let copySrc = srcDir
|
||||
if (!type) {
|
||||
let subs = []
|
||||
try {
|
||||
subs = (await fsp.readdir(srcDir, { withFileTypes: true })).filter(e => e.isDirectory())
|
||||
} catch {}
|
||||
for (const s of subs) {
|
||||
if (detectType(path.join(srcDir, s.name))) {
|
||||
copySrc = path.join(srcDir, s.name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
const dst = dedupeDir(WINES_DIR, path.basename(copySrc))
|
||||
await fsp.cp(copySrc, dst, { recursive: true })
|
||||
await refreshWines()
|
||||
return { ok: true, path: dst }
|
||||
}
|
||||
|
||||
module.exports = { refreshWines, getWines, getWine, setWineDisabled, getDefaultWine, setDefaultWine, deleteWine, importWineFolder, findWineBinary, detectType }
|
||||
Loading…
Add table
Add a link
Reference in a new issue