separate playtime file
This commit is contained in:
parent
2b7623227d
commit
78ef651ca7
3 changed files with 47 additions and 10 deletions
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue