278 lines
12 KiB
Python
278 lines
12 KiB
Python
# FlightMonitor/gui/dialogs/maintenance_window.py
|
|
"""
|
|
Provides a maintenance window for managing stored application data,
|
|
such as daily recordings, scan sessions, and caches.
|
|
"""
|
|
import tkinter as tk
|
|
from tkinter import ttk, messagebox
|
|
from typing import Optional, Any, List
|
|
from datetime import datetime, timezone
|
|
|
|
from flightmonitor.utils.logger import get_logger
|
|
|
|
module_logger = get_logger(__name__)
|
|
|
|
|
|
class MaintenanceWindow(tk.Toplevel):
|
|
"""
|
|
A Toplevel window for data maintenance tasks.
|
|
"""
|
|
|
|
def __init__(self, parent: tk.Tk, controller: Any):
|
|
"""
|
|
Initializes the MaintenanceWindow.
|
|
|
|
Args:
|
|
parent: The parent window (the root Tk instance).
|
|
controller: The application controller to delegate actions.
|
|
"""
|
|
super().__init__(parent)
|
|
self.title("Data Maintenance")
|
|
self.controller = controller
|
|
|
|
self.geometry("800x600")
|
|
self.minsize(700, 500)
|
|
|
|
# Widget references
|
|
self.recordings_tree: Optional[ttk.Treeview] = None
|
|
self.sessions_tree: Optional[ttk.Treeview] = None
|
|
|
|
main_frame = ttk.Frame(self, padding="10")
|
|
main_frame.pack(fill=tk.BOTH, expand=True)
|
|
|
|
notebook = ttk.Notebook(main_frame)
|
|
notebook.pack(fill=tk.BOTH, expand=True)
|
|
|
|
self._create_recordings_tab(notebook)
|
|
self._create_sessions_tab(notebook)
|
|
self._create_general_tab(notebook)
|
|
|
|
self.protocol("WM_DELETE_WINDOW", self.destroy)
|
|
self.center_window()
|
|
self.after(100, self._populate_all_tabs)
|
|
module_logger.info("MaintenanceWindow initialized.")
|
|
|
|
def _populate_all_tabs(self):
|
|
"""Initial population of all data tabs."""
|
|
self._populate_recordings_tab()
|
|
self._populate_sessions_tab()
|
|
|
|
def _create_recordings_tab(self, notebook: ttk.Notebook):
|
|
"""Creates the tab for managing daily recording database files."""
|
|
container = ttk.Frame(notebook, padding="10")
|
|
notebook.add(container, text="Daily Recordings")
|
|
container.rowconfigure(1, weight=1)
|
|
container.columnconfigure(0, weight=1)
|
|
|
|
controls_frame = ttk.Frame(container)
|
|
controls_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))
|
|
|
|
ttk.Button(
|
|
controls_frame, text="Refresh List", command=self._populate_recordings_tab
|
|
).pack(side=tk.LEFT)
|
|
ttk.Button(
|
|
controls_frame,
|
|
text="Delete Selected Day(s)",
|
|
command=self._delete_selected_recordings,
|
|
).pack(side=tk.LEFT, padx=5)
|
|
|
|
tree_frame = ttk.Frame(container)
|
|
tree_frame.grid(row=1, column=0, sticky="nsew")
|
|
tree_frame.rowconfigure(0, weight=1)
|
|
tree_frame.columnconfigure(0, weight=1)
|
|
|
|
self.recordings_tree = ttk.Treeview(
|
|
tree_frame,
|
|
columns=("date", "size", "flights", "positions"),
|
|
show="headings",
|
|
)
|
|
self.recordings_tree.grid(row=0, column=0, sticky="nsew")
|
|
|
|
self.recordings_tree.heading("date", text="Date")
|
|
self.recordings_tree.column("date", width=120, anchor=tk.W)
|
|
self.recordings_tree.heading("size", text="File Size")
|
|
self.recordings_tree.column("size", width=100, anchor=tk.E)
|
|
self.recordings_tree.heading("flights", text="Flights Count")
|
|
self.recordings_tree.column("flights", width=100, anchor=tk.E)
|
|
self.recordings_tree.heading("positions", text="Positions Count")
|
|
self.recordings_tree.column("positions", width=120, anchor=tk.E)
|
|
|
|
vsb = ttk.Scrollbar(
|
|
tree_frame, orient="vertical", command=self.recordings_tree.yview
|
|
)
|
|
vsb.grid(row=0, column=1, sticky="ns")
|
|
self.recordings_tree.configure(yscrollcommand=vsb.set)
|
|
|
|
def _populate_recordings_tab(self):
|
|
"""Fetches and displays the daily recording summaries."""
|
|
if not self.recordings_tree or not self.controller:
|
|
return
|
|
|
|
for item in self.recordings_tree.get_children():
|
|
self.recordings_tree.delete(item)
|
|
|
|
summaries = self.controller.get_daily_recordings_summary()
|
|
for summary in summaries:
|
|
date = summary.get("date", "N/A")
|
|
size_mb = summary.get("file_size_mb", 0)
|
|
flights = summary.get("flights_count", 0)
|
|
positions = summary.get("positions_count", 0)
|
|
self.recordings_tree.insert(
|
|
"", "end", values=(date, f"{size_mb:.2f} MB", flights, positions), iid=date
|
|
)
|
|
|
|
def _delete_selected_recordings(self):
|
|
"""Deletes the selected daily database files."""
|
|
selected_items = self.recordings_tree.selection()
|
|
if not selected_items:
|
|
messagebox.showwarning("No Selection", "Please select one or more dates to delete.", parent=self)
|
|
return
|
|
|
|
dates_to_delete = [self.recordings_tree.item(item, "values")[0] for item in selected_items]
|
|
|
|
msg = f"Are you sure you want to permanently delete the data for the following {len(dates_to_delete)} day(s)?\n\n- "
|
|
msg += "\n- ".join(dates_to_delete)
|
|
|
|
if messagebox.askyesno("Confirm Deletion", msg, parent=self):
|
|
deleted_count = 0
|
|
for date_str in dates_to_delete:
|
|
if self.controller.delete_daily_recording(date_str):
|
|
deleted_count += 1
|
|
|
|
messagebox.showinfo("Deletion Complete", f"Successfully deleted data for {deleted_count} day(s).", parent=self)
|
|
self._populate_recordings_tab()
|
|
|
|
def _create_sessions_tab(self, notebook: ttk.Notebook):
|
|
"""Creates the tab for managing explicit scan sessions."""
|
|
container = ttk.Frame(notebook, padding="10")
|
|
notebook.add(container, text="Scan Sessions")
|
|
container.rowconfigure(1, weight=1)
|
|
container.columnconfigure(0, weight=1)
|
|
|
|
controls_frame = ttk.Frame(container)
|
|
controls_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))
|
|
ttk.Button(
|
|
controls_frame, text="Refresh List", command=self._populate_sessions_tab
|
|
).pack(side=tk.LEFT)
|
|
ttk.Button(
|
|
controls_frame,
|
|
text="Delete Selected Session(s)",
|
|
command=self._delete_selected_sessions,
|
|
).pack(side=tk.LEFT, padx=5)
|
|
|
|
tree_frame = ttk.Frame(container)
|
|
tree_frame.grid(row=1, column=0, sticky="nsew")
|
|
tree_frame.rowconfigure(0, weight=1)
|
|
tree_frame.columnconfigure(0, weight=1)
|
|
|
|
self.sessions_tree = ttk.Treeview(
|
|
tree_frame,
|
|
columns=("id", "start_time", "end_time", "status", "bbox"),
|
|
show="headings",
|
|
)
|
|
self.sessions_tree.grid(row=0, column=0, sticky="nsew")
|
|
|
|
self.sessions_tree.heading("id", text="ID")
|
|
self.sessions_tree.column("id", width=40, anchor=tk.E)
|
|
self.sessions_tree.heading("start_time", text="Start Time (UTC)")
|
|
self.sessions_tree.column("start_time", width=150, anchor=tk.W)
|
|
self.sessions_tree.heading("end_time", text="End Time (UTC)")
|
|
self.sessions_tree.column("end_time", width=150, anchor=tk.W)
|
|
self.sessions_tree.heading("status", text="Status")
|
|
self.sessions_tree.column("status", width=80, anchor=tk.W)
|
|
self.sessions_tree.heading("bbox", text="Bounding Box")
|
|
self.sessions_tree.column("bbox", width=250, stretch=True)
|
|
|
|
vsb = ttk.Scrollbar(
|
|
tree_frame, orient="vertical", command=self.sessions_tree.yview
|
|
)
|
|
vsb.grid(row=0, column=1, sticky="ns")
|
|
self.sessions_tree.configure(yscrollcommand=vsb.set)
|
|
|
|
def _populate_sessions_tab(self):
|
|
"""Fetches and displays the explicit scan sessions."""
|
|
if not self.sessions_tree or not self.controller:
|
|
return
|
|
|
|
for item in self.sessions_tree.get_children():
|
|
self.sessions_tree.delete(item)
|
|
|
|
sessions = self.controller.get_all_scan_sessions()
|
|
for session in sessions:
|
|
scan_id = session.get("scan_id")
|
|
start_str = datetime.fromtimestamp(session.get("start_timestamp", 0), timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
end_str = datetime.fromtimestamp(session.get("end_timestamp", 0), timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
bbox_str = f"[{session.get('lat_min', 0):.2f}, {session.get('lon_min', 0):.2f}] - [{session.get('lat_max', 0):.2f}, {session.get('lon_max', 0):.2f}]"
|
|
|
|
self.sessions_tree.insert(
|
|
"", "end", values=(
|
|
scan_id,
|
|
start_str,
|
|
end_str,
|
|
session.get("status", "N/A"),
|
|
bbox_str
|
|
), iid=scan_id
|
|
)
|
|
|
|
def _delete_selected_sessions(self):
|
|
"""Deletes the selected scan sessions."""
|
|
selected_items = self.sessions_tree.selection()
|
|
if not selected_items:
|
|
messagebox.showwarning("No Selection", "Please select one or more sessions to delete.", parent=self)
|
|
return
|
|
|
|
ids_to_delete = [int(item) for item in selected_items]
|
|
msg = f"Are you sure you want to permanently delete the following {len(ids_to_delete)} session(s)?\n\n- Session IDs: {', '.join(map(str, ids_to_delete))}"
|
|
|
|
if messagebox.askyesno("Confirm Deletion", msg, parent=self):
|
|
deleted_count = 0
|
|
for scan_id in ids_to_delete:
|
|
if self.controller.delete_scan_session(scan_id):
|
|
deleted_count += 1
|
|
|
|
messagebox.showinfo("Deletion Complete", f"Successfully deleted {deleted_count} session(s).", parent=self)
|
|
self._populate_sessions_tab()
|
|
|
|
def _create_general_tab(self, notebook: ttk.Notebook):
|
|
"""Creates the tab for general cleanup tasks."""
|
|
container = ttk.Frame(notebook, padding="10")
|
|
notebook.add(container, text="General Maintenance")
|
|
|
|
ac_db_frame = ttk.LabelFrame(container, text="Aircraft Database", padding=10)
|
|
ac_db_frame.pack(fill=tk.X, pady=(0, 10))
|
|
ttk.Button(ac_db_frame, text="Clear Entire Aircraft Database...", command=self._clear_aircraft_db).pack(pady=5)
|
|
ttk.Button(ac_db_frame, text="Clear Scan History...", command=self._clear_scan_history).pack(pady=5)
|
|
|
|
map_cache_frame = ttk.LabelFrame(container, text="Map Tile Cache", padding=10)
|
|
map_cache_frame.pack(fill=tk.X, pady=(0, 10))
|
|
ttk.Button(map_cache_frame, text="Clear Map Tile Cache...", command=self._clear_map_cache).pack(pady=5)
|
|
|
|
def _clear_aircraft_db(self):
|
|
if messagebox.askyesno("Confirm Action", "Are you sure you want to delete ALL records from the aircraft database? This action cannot be undone.", parent=self):
|
|
if self.controller and self.controller.clear_aircraft_database():
|
|
messagebox.showinfo("Success", "Aircraft database has been cleared.", parent=self)
|
|
else:
|
|
messagebox.showerror("Error", "Failed to clear the aircraft database.", parent=self)
|
|
|
|
def _clear_scan_history(self):
|
|
if messagebox.askyesno("Confirm Action", "Are you sure you want to delete ALL scan history records? This will not delete daily data.", parent=self):
|
|
if self.controller and self.controller.clear_scan_history():
|
|
messagebox.showinfo("Success", "Scan history has been cleared.", parent=self)
|
|
else:
|
|
messagebox.showerror("Error", "Failed to clear scan history.", parent=self)
|
|
|
|
def _clear_map_cache(self):
|
|
if messagebox.askyesno("Confirm Action", "Are you sure you want to delete all cached map tiles? They will be re-downloaded as needed.", parent=self):
|
|
if self.controller and self.controller.clear_map_tile_cache():
|
|
messagebox.showinfo("Success", "Map tile cache has been cleared.", parent=self)
|
|
else:
|
|
messagebox.showerror("Error", "Failed to clear the map tile cache.", parent=self)
|
|
|
|
def center_window(self):
|
|
"""Centers the window on the screen."""
|
|
self.update_idletasks()
|
|
width = self.winfo_width()
|
|
height = self.winfo_height()
|
|
x = (self.winfo_screenwidth() // 2) - (width // 2)
|
|
y = (self.winfo_screenheight() // 2) - (height // 2)
|
|
self.geometry(f"{width}x{height}+{x}+{y}") |