separate playtime file

This commit is contained in:
olcxja 2026-09-26 09:55:18 +02:00
commit 78ef651ca7
3 changed files with 47 additions and 10 deletions

View file

@ -1,14 +1,41 @@
'use strict'
const path = require('path')
const fs = require('fs')
const { DIRS, readJson, writeJson } = require('./paths')
const { prefixPath } = require('./prefixes')
const REGISTRY = path.join(DIRS.DATA, 'apps.json')
const SETTINGS = path.join(DIRS.DATA, 'settings.json')
// Playtime lives in its own tiny file holding ONLY a number of seconds
// (files/data/appdata/<id>/playtime.txt) so the running game can update it
// every ~30s without rewriting the whole apps.json registry on each tick.
function playtimeFile(id) {
return path.join(DIRS.DATA, 'appdata', id, 'playtime.txt')
}
function readPlaySeconds(id) {
try {
const n = parseInt(fs.readFileSync(playtimeFile(id), 'utf8').trim(), 10)
if (Number.isFinite(n) && n >= 0) return n
} catch {}
return null // no file yet -> fall back to the registry value
}
function writePlaySeconds(id, seconds) {
const file = playtimeFile(id)
fs.mkdirSync(path.dirname(file), { recursive: true })
fs.writeFileSync(file, String(Math.max(0, Math.round(seconds))))
}
function loadGames() {
return readJson(REGISTRY, [])
const games = readJson(REGISTRY, [])
for (const g of games) {
const ps = readPlaySeconds(g.id)
if (ps != null) g.playSeconds = ps
}
return games
}
function saveGames(games) {
@ -30,11 +57,18 @@ function updateGame(id, patch) {
const games = loadGames()
const g = games.find(x => x.id === id)
if (!g) return null
// playSeconds goes to its own tiny file; the registry just mirrors it for
// anything reading apps.json directly (loadGames prefers the file anyway).
if (patch.playSeconds !== undefined) writePlaySeconds(id, patch.playSeconds)
Object.assign(g, patch)
saveGames(games)
return g
}
function clearPlaytime(id) {
return updateGame(id, { playSeconds: 0, lastPlayed: 0 })
}
function removeGame(id) {
const games = loadGames()
const idx = games.findIndex(g => g.id === id)
@ -86,4 +120,4 @@ function resolvePrefixDir(game) {
return prefixPath(defs.default || 'default')
}
module.exports = { loadGames, saveGames, getGame, addGame, updateGame, removeGame, loadSettings, saveSettings, newGame, resolvePrefixDir }
module.exports = { loadGames, saveGames, getGame, addGame, updateGame, removeGame, loadSettings, saveSettings, newGame, resolvePrefixDir, clearPlaytime, readPlaySeconds, writePlaySeconds, playtimeFile }

View file

@ -110,7 +110,7 @@ ipcMain.handle('games:list', () => library.loadGames())
ipcMain.handle('games:remove', (e, id) => library.removeGame(id))
ipcMain.handle('games:get', (e, id) => library.getGame(id))
ipcMain.handle('games:update', (e, id, patch) => library.updateGame(id, patch))
ipcMain.handle('games:clearPlaytime', (e, id) => library.updateGame(id, { playSeconds: 0, lastPlayed: 0 }))
ipcMain.handle('games:clearPlaytime', (e, id) => library.clearPlaytime(id))
ipcMain.handle('games:resetIcon', (e, id) => {
const game = library.getGame(id)
if (!game) return { ok: false }

View file

@ -77,15 +77,16 @@ function parseArgs(args) {
const GRACE_MS = 60000
// Live playtime: update stats while the game is still running (not only on exit).
function startPlaytimeTicker({ game, startedAt, onTick }) {
// Live playtime: while the game runs, update ONLY the tiny playtime file
// (a number of seconds) every 30s - apps.json is left untouched to spare the
// disk. lastPlayed is stamped once at start, final totals are mirrored into
// the registry once on exit.
function startPlaytimeTicker({ game, startedAt }) {
const library = require('./library')
const base = game.playSeconds || 0
const timer = setInterval(() => {
const counted = Math.max(0, Math.round((Date.now() - startedAt - GRACE_MS) / 1000))
require('./library').updateGame(game.id, {
lastPlayed: startedAt,
playSeconds: (game.playSeconds || 0) + counted
})
if (onTick) onTick()
library.writePlaySeconds(game.id, base + counted)
}, 30000)
return () => clearInterval(timer)
}
@ -147,6 +148,8 @@ function runGame({ game, wine, systemWine, onExit }) {
child.unref()
const startedAt = Date.now()
const stopTicker = startPlaytimeTicker({ game, startedAt })
// stamp "last played" once at start so it is correct even if we crash later
require('./library').updateGame(game.id, { lastPlayed: startedAt })
child.on('exit', (code, signal) => {
stopTicker()
ws.end()