First build

This commit is contained in:
olcxja 2026-09-25 17:09:38 +02:00
commit fd773f5832
954 changed files with 305115 additions and 2 deletions

192
src/wines.js Normal file
View 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 }