Skip to content

Commit fd3c41b

Browse files
committed
add tasks
1 parent affc8de commit fd3c41b

3 files changed

Lines changed: 179 additions & 0 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Background Parse Unparsed Matches
2+
3+
## Summary
4+
5+
When the app is running, automatically parse any stored matches that are still unparsed — at a slow, throttled rate — so that over time the local database is fully parsed without triggering OpenDota's rate limits.
6+
7+
## Motivation
8+
9+
Users may have hundreds of matches in the database that were fetched but never parsed (e.g. backfilled history, or matches where parsing was skipped). Today, parsing is manual. This task makes parsing happen automatically in the background so goal evaluation and analysis are accurate without user intervention.
10+
11+
## Acceptance Criteria
12+
13+
- On app start (or shortly after), the backend checks for matches with `parsed = 0` (or `parsed IS NULL`) in the database.
14+
- It processes one unparsed match at a time, with a configurable delay between requests (default: **10 seconds** between parse attempts).
15+
- If OpenDota returns a rate-limit error (HTTP 429), the background parser backs off and retries after a longer delay (e.g. 60 seconds).
16+
- The parser stops when all matches are parsed, and restarts the next time the app launches.
17+
- Parsing runs on a background Tokio task; it does **not** block the UI.
18+
- The user sees no mandatory notification, but a subtle indicator (e.g. status text in Settings or footer) can optionally show "Parsing X matches in background…".
19+
- The feature can be paused/disabled in Settings if the user doesn't want background network activity.
20+
21+
## Implementation Notes
22+
23+
- Tauri command to query `SELECT id FROM matches WHERE parsed = 0 ORDER BY match_id DESC` (newest first, so recent matches are parsed sooner).
24+
- Spawn a `tokio::spawn` background loop at app startup (in `lib.rs` setup or via a dedicated `start_background_parser` command called from the frontend on mount).
25+
- Use `tokio::time::sleep(Duration::from_secs(10))` between parse calls.
26+
- On 429, sleep 60 s before retrying the same match.
27+
- Reuse the existing `parse_match` / `request_parse` OpenDota logic.
28+
- Consider adding a `parsing_in_progress` boolean to settings/state so the frontend can show a subtle indicator.
29+
30+
## Rate Limit Guidance
31+
32+
OpenDota free tier allows roughly 60 requests/minute. Parsing a match typically requires 1–2 calls. At 10 s between matches this is well within limits. At 5 s it is still safe but leaves less headroom.
33+
34+
## Out of Scope
35+
36+
- Prioritising specific matches for immediate parse (manual parse button still handles that).
37+
- Showing a detailed parse queue UI.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Bug: "Failed to delete data, the database is locked"
2+
3+
## Symptom
4+
5+
Users (and the dev) see warnings/errors like:
6+
7+
> Failed to delete data, the database is locked
8+
9+
This occurs intermittently, typically when multiple Tauri commands run concurrently (e.g. on app startup when the frontend calls several commands at once).
10+
11+
## Root Cause
12+
13+
Every Tauri command calls `init_db()` to open a **new** `rusqlite::Connection`. SQLite uses file-level write locking — only one writer can hold the lock at a time. When multiple async Tauri commands are in flight simultaneously, each with its own connection, any that try to write while another holds the lock will fail immediately with `SQLITE_BUSY` ("database is locked").
14+
15+
This is a structural issue: the pattern of opening a new connection per command call is inherently unsafe under concurrency.
16+
17+
## Fix
18+
19+
Replace the per-command `init_db()` call pattern with a **single shared connection** protected by a `Mutex`, stored in Tauri's managed state.
20+
21+
```rust
22+
// In lib.rs
23+
use std::sync::Mutex;
24+
use rusqlite::Connection;
25+
26+
pub struct DbState(pub Mutex<Connection>);
27+
```
28+
29+
Register it at app startup:
30+
31+
```rust
32+
tauri::Builder::default()
33+
.manage(DbState(Mutex::new(database::init_db().expect("DB init failed"))))
34+
// ...
35+
```
36+
37+
Each command receives it via `State`:
38+
39+
```rust
40+
#[tauri::command]
41+
async fn my_command(state: tauri::State<'_, DbState>) -> Result<..., String> {
42+
let conn = state.0.lock().map_err(|e| e.to_string())?;
43+
// use &*conn
44+
}
45+
```
46+
47+
This serializes all DB access through one connection, eliminating concurrent-write races entirely. The `Mutex` ensures only one command touches the DB at a time.
48+
49+
## Additional Hardening (optional, do after the fix)
50+
51+
- Set `PRAGMA journal_mode=WAL` after opening the connection. WAL allows concurrent readers with a single writer and dramatically reduces lock contention if we ever move to a connection pool.
52+
- Set `PRAGMA busy_timeout=5000` as a belt-and-suspenders measure so SQLite waits up to 5 s before returning SQLITE_BUSY instead of failing immediately.
53+
54+
## Affected Area
55+
56+
`src-tauri/src/lib.rs` — all ~35 `let conn = init_db()?;` call sites.
57+
`src-tauri/src/database.rs``init_db()` function.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Beta Release Channel
2+
3+
## Summary
4+
5+
Introduce a separate **beta build** of Dota Keeper alongside the stable build, so new features can be tested with opt-in users without risking breakage for everyone on the stable track.
6+
7+
## Decision: Separate Build (not a channel toggle)
8+
9+
A **fully separate beta build** is the right approach rather than a runtime setting that switches the update URL. Reasons:
10+
11+
- A bad beta update can't accidentally land on a stable installation.
12+
- The beta can (and should) use a separate database so beta data doesn't pollute stable saves.
13+
- It installs side-by-side — you can run both at the same time.
14+
- CI can publish beta releases independently without touching the stable update manifest.
15+
16+
A "switch update channel in settings" approach is tempting but dangerous: if the beta update itself is broken you can't roll back via the updater.
17+
18+
## What Needs to Change
19+
20+
### 1. Tauri Config (`tauri.conf.json` → new `tauri.beta.conf.json`)
21+
22+
```json
23+
{
24+
"productName": "Dota Keeper Beta",
25+
"identifier": "com.volthawk.dota-keeper-beta",
26+
"plugins": {
27+
"updater": {
28+
"endpoints": [
29+
"https://raw.githubusercontent.com/stringhandler/dota-keeper/main/meta/autoupdate/latest-beta.json"
30+
]
31+
}
32+
}
33+
}
34+
```
35+
36+
Key differences vs stable:
37+
- `productName`: `"Dota Keeper Beta"` — shows "Beta" in title bar and taskbar
38+
- `identifier`: `com.volthawk.dota-keeper-beta` — separate OS install, separate registry entry
39+
- `endpoints`: points to `latest-beta.json` instead of `latest.json`
40+
41+
### 2. Database Separation (`database.rs`)
42+
43+
The database name is already `dota_keeper.db` for release and `dota_keeper_dev.db` for dev. Add a third variant for beta: `dota_keeper_beta.db`.
44+
45+
Detect via a compile-time feature flag or a `DOTA_KEEPER_BETA` env var set in the beta build script.
46+
47+
### 3. New Autoupdate Manifest (`meta/autoupdate/latest-beta.json`)
48+
49+
Same structure as `latest.json`. CI populates this on every beta release.
50+
51+
### 4. CI / GitHub Actions
52+
53+
Add a `release-beta.yml` workflow (or a conditional path in the existing `release.yml`) that mirrors the existing `release` branch workflow:
54+
- Triggers on pushes to the `beta` branch (same pattern as `release` → stable)
55+
- Builds using `tauri.beta.conf.json`
56+
- Publishes artifacts as a GitHub **pre-release**
57+
- Updates `meta/autoupdate/latest-beta.json` and commits/pushes it to `main`
58+
59+
Stable releases continue to trigger on pushes to `release` and update `latest.json`.
60+
61+
### 5. Window Title / Visual Indicator
62+
63+
Add a subtle "BETA" badge in the custom title bar (or window title string) when running the beta build, so it's obvious which version is open.
64+
65+
## Release Flow
66+
67+
```
68+
main (dev work)
69+
70+
├─► merge to beta branch
71+
│ → CI builds beta → GitHub pre-release + latest-beta.json updated
72+
│ → beta users auto-update
73+
74+
└─► (after testing) merge beta → release branch
75+
→ CI builds stable → GitHub release + latest.json updated
76+
→ stable users auto-update
77+
```
78+
79+
This mirrors the existing `release` branch pattern — no tags needed, the branch push is the release trigger.
80+
81+
## Out of Scope
82+
83+
- In-app channel switching (too risky, decided against above)
84+
- Android / iOS beta tracks (separate concern)
85+
- Crash reporting / telemetry differences between tracks

0 commit comments

Comments
 (0)