-
-
Notifications
You must be signed in to change notification settings - Fork 316
Add playtime tracking backend #4108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add playtime tracking backend #4108
Conversation
…ineProgram Enhance the WineCommand and WineProgram classes to accept pre-run and post-run script arguments. Update the UI to include fields for these arguments in the launch options dialog.
…andling in WineExecutor
…lass handling in yaml utility
…ript arguments in launch options dialog
…ssFinishedPayload for improved clarity and structure.
…se shutdown on exit
…and WinePath, improving test clarity and maintainability.
… enabling tracking and setting heartbeat interval.
…uirements.dev.txt
|
Hi, sorry for the delay, would be better if they were 2 separated MRs. Also because the one dedicated to playtime tracking, could be a general one, including both frontend and backend. Do you like the idea? Code looks good but I did not test it yet. |
Depends on your preference tbh, I developed this backend PR to not depend on frontend logic to mergeable, so it should not be a problem if we split the feature into two or more PRs. I personally would prefer splitting it so it's easier to review. About the backend code: it's already tracking time (to see it, you will need to query the sqlite db tho), so the frontend solution would just need to hook on that data. I will remove code from #4102 from here as soon as possible :) |
…ime-tracking-backend
|
Removed changes from #4102, this PR is now self contained. |
|
Thanks. I'll review it 🙏🏻 |
|
Thanks for the good work! |
Backend work to implement this request #1273
Used my previous PR as base #4102, I can try to separate them if neededPlaytime Tracking – Backend PR
Summary
This PR introduces robust, low-overhead playtime tracking to Bottles’ backend. It records per-program sessions, maintains a fast aggregate table for the UI, and is resilient to crashes and concurrent sessions. The solution is event-driven (signals), easy to toggle via GSettings, and thoroughly tested.
Motivation
Design Overview
Core tracker:
ProcessSessionTracker(bottles/backend/managers/playtime.py)$XDG_DATA_HOME/bottles/process_metrics.sqlitesessions(history) andplaytime_totals(materialized aggregate)last_seenperiodicallylast_seenEvent-driven lifecycle:
bottles/backend/state.py:Signals.ProgramStartedSignals.ProgramFinishedbottles/backend/models/process.py:ProcessStartedPayloadProcessFinishedPayload(status: Literal["success", "unknown"])WineExecutoremits these signals instead of calling the tracker directlyManagersubscribes to signals and calls the trackerPublic backend API consistency
Manager.playtime_start(...) -> Result[int]andManager.playtime_finish(...) -> Result[None]Result[T]Concurrency & durability
Manager)_lockguards all DB operations and_trackedupdates-walatexithandler ensures shutdown on normal process exitReliability & WAL behavior
PRAGMA wal_checkpoint(TRUNCATE)to avoid lingering-wal-walcan remain; SQLite replays it on next open. The tracker can also checkpoint at startup if neededRisks & Mitigations
-walon disk: mitigated by a final checkpoint on shutdown and automatic WAL replay by SQLite on next open, guarantees the db wont be written until next reconciliation_lockserializes connection usage; atomic finalization prevents torn updatesData Model
Schema is created on-demand by the tracker (
_ensure_schema()):sessions(history)id,bottle_id,bottle_name,bottle_path,program_id,program_name,program_path,started_at(epoch),ended_at(epoch),last_seen(epoch),duration_seconds,status(running|success|crash|forced|unknown)(bottle_id, program_id),(status)(bottle_id, program_id, started_at)playtime_totals(materialized aggregate)bottle_id,bottle_name,program_id,program_name,program_path,total_seconds,sessions_count,last_played(bottle_id, program_id)last_played DESCNotes:
program_id = sha1(f"{bottle_id}:{program_path}")to handle rename scenarios(bottle_id, program_id)again while running returns the existing session instead of inserting a new oneSettings (GSettings)
playtime-enabled(default: true): master toggleplaytime-heartbeat-interval(seconds, default: 60)Backend reads these in
Managerat startup. UI toggle is planned for Phase 3.Tests
Unit and integration tests added/updated:
ProgramStarted/Finishedand assert sessions/totalsManagerDev dependencies (tests)
mockerfixture for stubbing without verbose manual monkeypatching.PSA
This description was originally written in portuguese and translated by AI
Manual testing
I've been testing this for a couple of days and monitoring the database while simulating scenarios like force closes (inter bottle and multiple apps in same bottle), resets and day-to-day gaming, for now the tracking is spot-on and the heartbeat system is working flawlessly
I mainly work with distributed systems and have minimal experience developing desktop apps, so feel free to change anything in the code!