05/23 Phase 18,19
This commit is contained in:
+65
-3
@@ -7,15 +7,16 @@ on_fetch: callable injected by main.py to trigger the shared fetch thread.
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
import logging
|
||||
|
||||
from db.database import get_db_stats
|
||||
from db.database import get_db_stats, backup_db, restore_db
|
||||
from db.models import (
|
||||
get_all_games, get_draw_count, set_game_active,
|
||||
get_last_fetch_per_source, get_predictions,
|
||||
add_game, delete_game, _BUILTIN_GAMES,
|
||||
)
|
||||
from core.importer import import_draws_csv
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -80,6 +81,7 @@ class SettingsScreen(ttk.Frame):
|
||||
self._sources_body = self._section("Data Sources")
|
||||
self._fetch_body = self._build_fetch_section()
|
||||
self._db_body = self._section("Database")
|
||||
self._build_db_actions()
|
||||
|
||||
def _build_fetch_section(self):
|
||||
body = self._section("Fetch Schedule")
|
||||
@@ -132,11 +134,16 @@ class SettingsScreen(ttk.Frame):
|
||||
foreground="#777777",
|
||||
).pack(side="left", padx=(16, 0))
|
||||
|
||||
ttk.Button(
|
||||
row, text="Import CSV",
|
||||
command=lambda gid=game["id"], gname=game["name"]: self._import_csv(gid, gname),
|
||||
).pack(side="left", padx=(12, 0))
|
||||
|
||||
if game["name"] not in _BUILTIN_GAMES:
|
||||
ttk.Button(
|
||||
row, text="Delete",
|
||||
command=lambda gid=game["id"], gname=game["name"]: self._delete_game(gid, gname),
|
||||
).pack(side="left", padx=(12, 0))
|
||||
).pack(side="left", padx=(6, 0))
|
||||
|
||||
ttk.Button(
|
||||
self._games_body, text="+ Add Custom Game",
|
||||
@@ -187,6 +194,13 @@ class SettingsScreen(ttk.Frame):
|
||||
ttk.Label(row, text="Predictions:", width=18, anchor="w").pack(side="left")
|
||||
ttk.Label(row, text=str(pred_count), foreground="#555555").pack(side="left")
|
||||
|
||||
def _build_db_actions(self):
|
||||
body = self._section("Database Actions")
|
||||
row = ttk.Frame(body)
|
||||
row.pack(fill="x")
|
||||
ttk.Button(row, text="Backup DB", command=self._backup_db ).pack(side="left", padx=(0, 8))
|
||||
ttk.Button(row, text="Restore DB", command=self._restore_db).pack(side="left")
|
||||
|
||||
# ── Actions ───────────────────────────────────────────────────────────────
|
||||
|
||||
def _toggle_game(self, game_id: int, var: tk.BooleanVar):
|
||||
@@ -203,6 +217,54 @@ class SettingsScreen(ttk.Frame):
|
||||
else:
|
||||
self._fetch_msg_var.set("Fetch not available.")
|
||||
|
||||
def _import_csv(self, game_id: int, game_name: str):
|
||||
fp = filedialog.askopenfilename(
|
||||
title=f"Import draws for {game_name}",
|
||||
filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
|
||||
)
|
||||
if not fp:
|
||||
return
|
||||
result = import_draws_csv(game_id, fp)
|
||||
added = result["added"]
|
||||
skipped = result["skipped"]
|
||||
errors = result["errors"]
|
||||
msg = f"Added {added:,} draw{'s' if added != 1 else ''}."
|
||||
if skipped:
|
||||
msg += f"\nSkipped {skipped:,} duplicate{'s' if skipped != 1 else ''}."
|
||||
if errors:
|
||||
preview = "\n".join(errors[:5])
|
||||
suffix = f"\n… and {len(errors) - 5} more." if len(errors) > 5 else ""
|
||||
msg += f"\n\n{len(errors)} row error(s):\n{preview}{suffix}"
|
||||
messagebox.showinfo("Import Complete", msg)
|
||||
self._refresh_games()
|
||||
|
||||
def _backup_db(self):
|
||||
try:
|
||||
dest = backup_db()
|
||||
messagebox.showinfo("Backup Complete", f"Database backed up to:\n{dest}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Backup Failed", str(e))
|
||||
|
||||
def _restore_db(self):
|
||||
fp = filedialog.askopenfilename(
|
||||
title="Select backup file to restore",
|
||||
filetypes=[("SQLite DB", "*.db"), ("All files", "*.*")],
|
||||
)
|
||||
if not fp:
|
||||
return
|
||||
if not messagebox.askyesno(
|
||||
"Confirm Restore",
|
||||
"Restoring will overwrite the current database.\n"
|
||||
"This cannot be undone. Continue?",
|
||||
):
|
||||
return
|
||||
try:
|
||||
restore_db(fp)
|
||||
messagebox.showinfo("Restore Complete",
|
||||
"Database restored. Please restart the app.")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Restore Failed", str(e))
|
||||
|
||||
def _open_add_game_dialog(self):
|
||||
_AddGameDialog(self, on_save=self._refresh_games)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user