modify log system qith queue, add async command, change status bar function with color

This commit is contained in:
VALLONGOL 2025-04-22 15:04:44 +02:00
parent 788334d969
commit daaf409be9
2 changed files with 229 additions and 93 deletions

View File

@ -1834,122 +1834,198 @@ class GitSvnSyncApp:
def _check_completion_queue(self, results_queue, context):
"""Checks result queue, updates GUI (incl. status bar color)."""
task_context = context.get('context', 'unknown')
# log_handler.log_debug(f"Checking completion queue for context: {task_context}", func_name="_check_completion_queue") # Mantenuto commentato per ora
# log_handler.log_debug(f"Checking completion queue for context: {task_context}", func_name="_check_completion_queue") # Mantenuto commentato
try:
# Tenta di ottenere un risultato dalla coda senza bloccare
result_data = results_queue.get_nowait()
log_handler.log_info(f"Result received for '{task_context}'. Status: {result_data.get('status')}", func_name="_check_completion_queue")
# --- 1. Re-enable GUI Widgets ---
# Riabilita i widget principali prima di processare il risultato
log_handler.log_debug("Re-enabling widgets.", func_name="_check_completion_queue")
self.main_frame.set_action_widgets_state(tk.NORMAL)
if hasattr(self, "main_frame") and self.main_frame.winfo_exists():
self.main_frame.set_action_widgets_state(tk.NORMAL)
else:
log_handler.log_warning("Cannot re-enable widgets, MainFrame missing.", func_name="_check_completion_queue")
# Potrebbe essere necessario uscire qui se la GUI non c'è più
return
# --- 2. Extract Details ---
# Estrae i dati dal dizionario del risultato, con fallback sicuri
status = result_data.get('status')
message = result_data.get('message')
result = result_data.get('result')
exception = result_data.get('exception')
committed = result_data.get('committed', False)
is_conflict = result_data.get('conflict', False)
repo_path_conflict = result_data.get('repo_path')
new_branch_context = context.get('new_branch_name')
committed = result_data.get('committed', False) # Flag per operazioni che potrebbero committare
is_conflict = result_data.get('conflict', False) # Flag specifico per conflitti merge
repo_path_conflict = result_data.get('repo_path') # Percorso repo in caso di conflitto
new_branch_context = context.get('new_branch_name') # Info dal contesto originale se si crea un branch
# --- 3. Update Status Bar (con colore e reset temporizzato) ---
# (Logica status bar invariata)
# Imposta colore e durata del messaggio nella status bar in base allo stato
status_color = None
reset_duration = 5000 # Resetta colore dopo 5 secondi
reset_duration = 5000 # Default reset 5 secondi
if status == 'success':
status_color = self.main_frame.STATUS_GREEN
elif status == 'warning':
status_color = self.main_frame.STATUS_YELLOW
reset_duration = 7000
reset_duration = 7000 # Warning dura un po' di più
elif status == 'error':
status_color = self.main_frame.STATUS_RED
reset_duration = 10000
reset_duration = 10000 # Errore dura di più
# Aggiorna la status bar tramite la funzione helper in MainFrame
self.main_frame.update_status_bar(message, bg_color=status_color, duration_ms=reset_duration)
# --- 4. Process Result & Trigger Updates ---
# Ottieni il percorso corrente del repo per eventuali refresh necessari
repo_path_for_updates = self._get_and_validate_svn_path("Post-Action Update")
# Gestione basata sullo stato del risultato ('success', 'warning', 'error')
if status == 'success':
refresh_list = []
# (Logica per popolare refresh_list invariata)
refresh_list = [] # Lista di funzioni di refresh da chiamare dopo
# --- Logica per determinare quali refresh avviare ---
# Se l'operazione ha modificato lo stato del repo (commit, fetch, prepare, checkout, ecc.)
if task_context in ['prepare_repo', 'fetch_bundle', 'commit', 'create_tag', 'checkout_tag', 'create_branch', 'checkout_branch', '_handle_gitignore_save_async', 'add_file']:
if committed or task_context in ['fetch_bundle','prepare_repo','create_tag','_handle_gitignore_save_async']: refresh_list.append(self.refresh_commit_history)
if task_context != 'refresh_changes': refresh_list.append(self.refresh_changed_files_list)
if task_context not in ['refresh_tags','checkout_tag'] or committed: refresh_list.append(self.refresh_tag_list)
if task_context != 'refresh_branches': refresh_list.append(self.refresh_branch_list)
# Aggiorna la history se c'è stato un commit o se l'operazione lo richiede implicitamente
if committed or task_context in ['fetch_bundle','prepare_repo','create_tag','_handle_gitignore_save_async']:
if self.refresh_commit_history not in refresh_list: refresh_list.append(self.refresh_commit_history)
# Aggiorna sempre la lista dei file modificati tranne se l'azione era proprio il refresh dei file
if task_context != 'refresh_changes':
if self.refresh_changed_files_list not in refresh_list: refresh_list.append(self.refresh_changed_files_list)
# Aggiorna i tag (tranne se si è fatto checkout di un tag o refresh dei tag stessi), o se c'è stato commit
if task_context not in ['refresh_tags','checkout_tag'] or committed:
if self.refresh_tag_list not in refresh_list: refresh_list.append(self.refresh_tag_list)
# Aggiorna i branch (tranne se si è fatto refresh dei branch)
if task_context != 'refresh_branches':
if self.refresh_branch_list not in refresh_list: refresh_list.append(self.refresh_branch_list)
# Gestione aggiornamenti diretti post-refresh
# --- Gestione aggiornamenti diretti post-operazioni di refresh ---
# Se l'azione era un refresh, aggiorna direttamente la GUI con i dati ricevuti
if task_context == 'refresh_tags':
self.main_frame.update_tag_list(result if result else [])
self.main_frame.update_tag_list(result if isinstance(result, list) else []) # Assicura sia una lista
elif task_context == 'refresh_branches':
branches, current = result if result else ([], None)
# Estrai i dati con un fallback sicuro
branches, current = result if isinstance(result, tuple) and len(result) == 2 else ([], None)
# Log di debug aggiunto qui
log_handler.log_debug(
f"Preparing to call update_branch_list. Task Context: '{task_context}'. "
f"Branches type: {type(branches)}, Count: {len(branches) if isinstance(branches, list) else 'N/A'}. Current: {repr(current)}. "
f"Raw result: {repr(result)}",
func_name="_check_completion_queue"
)
self.main_frame.update_branch_list(branches, current)
self.main_frame.update_history_branch_filter(branches)
# Aggiorna anche il filtro nella tab history
self.main_frame.update_history_branch_filter(branches) # Usa solo la lista branches
elif task_context == 'refresh_history':
self.main_frame.update_history_display(result if result else [])
# Assicura sia una lista
log_lines_result = result if isinstance(result, list) else []
# Log di debug aggiunto qui
log_handler.log_debug(
f"Preparing to call update_history_display. Task Context: '{task_context}'. "
f"Result type: {type(result)}. Lines count: {len(log_lines_result)}. "
f"Raw result sample: {repr(log_lines_result[:5])}", # Usa la variabile già controllata
func_name="_check_completion_queue"
)
self.main_frame.update_history_display(log_lines_result)
elif task_context == 'refresh_changes':
# ---<<< INIZIO MODIFICA DEBUG >>>---
# Logga esattamente cosa sta per essere passato a update_changed_files_list
# Log di debug aggiunto qui
log_handler.log_debug(
f"Preparing to call update_changed_files_list. "
f"Task Context: '{task_context}'. Result type: {type(result)}. Result value: {repr(result)}",
func_name="_check_completion_queue"
)
# ---<<< FINE MODIFICA DEBUG >>>---
self.main_frame.update_changed_files_list(result if result else [])
self.main_frame.update_changed_files_list(result if isinstance(result, list) else []) # Assicura sia lista
# (Altre gestioni di successo invariate: commit, create_branch checkout, etc.)
if task_context == 'commit' and committed: self.main_frame.clear_commit_message()
# --- Gestione azioni post-successo specifiche ---
if task_context == 'commit' and committed:
# Pulisce il messaggio di commit solo se è stato fatto un commit effettivo
self.main_frame.clear_commit_message()
if task_context == 'create_branch' and new_branch_context:
# Chiede se fare checkout del nuovo branch creato
if self.main_frame.ask_yes_no("Checkout?", f"Switch to new branch '{new_branch_context}'?"):
# Avvia un'altra operazione asincrona per il checkout
self.checkout_branch(branch_to_checkout=new_branch_context)
else: # Refresh history if not checking out
else:
# Se non fa checkout, assicurati che la history venga aggiornata
if self.refresh_commit_history not in refresh_list: refresh_list.append(self.refresh_commit_history)
# Trigger collected async refreshes
# --- Trigger finale dei refresh asincroni raccolti ---
if repo_path_for_updates and refresh_list:
log_handler.log_debug(f"Triggering {len(refresh_list)} async refreshes for '{task_context}'", func_name="_check_completion_queue")
# Avvia ogni funzione di refresh nella lista (sono anch'esse asincrone)
for refresh_func in refresh_list:
try: refresh_func()
except Exception as ref_e: log_handler.log_error(f"Error triggering {refresh_func.__name__}: {ref_e}", func_name="_check_completion_queue")
try:
refresh_func()
except Exception as ref_e:
log_handler.log_error(f"Error triggering {refresh_func.__name__}: {ref_e}", func_name="_check_completion_queue")
elif refresh_list:
# Se la lista refresh non è vuota ma manca il path, logga un warning
log_handler.log_warning("Cannot trigger UI refreshes: Repo path unavailable.", func_name="_check_completion_queue")
elif status == 'warning':
# (gestione warning invariata)
self.main_frame.show_warning("Operation Info", message)
if "already prepared" in message: self.refresh_changed_files_list()
# Gestione dei warning: mostra un popup informativo
if hasattr(self, "main_frame"):
self.main_frame.show_warning("Operation Info", message)
# Caso specifico: repo già preparato
if "already prepared" in message:
if self.refresh_changed_files_list not in refresh_list: refresh_list.append(self.refresh_changed_files_list)
if self.refresh_branch_list not in refresh_list: refresh_list.append(self.refresh_branch_list)
# Avvia refresh post warning
if repo_path_for_updates and refresh_list:
for refresh_func in refresh_list:
try: refresh_func()
except Exception as ref_e: log_handler.log_error(f"Error triggering refresh after warning: {ref_e}", func_name="_check_completion_queue")
elif status == 'error':
# (gestione errore invariata)
error_details = f"{message}\n({exception})" if exception else message
if is_conflict and repo_path_conflict: self.main_frame.show_error("Merge Conflict", f"Conflict occurred.\nResolve in:\n{repo_path_conflict}\nThen commit.")
elif "Uncommitted changes" in message: self.main_frame.show_warning("Action Blocked", f"{exception}\nCommit or stash first.")
else: self.main_frame.show_error("Error: Operation Failed", error_details)
# Aggiornamento liste con errore (opzionale, dipende dal task)
# Gestione degli errori: mostra un popup di errore
error_details = f"{message}\n({type(exception).__name__}: {exception})" if exception else message
if hasattr(self, "main_frame"):
# Mostra popup specifici per errori comuni
if is_conflict and repo_path_conflict:
self.main_frame.show_error("Merge Conflict", f"Conflict occurred.\nResolve in:\n{repo_path_conflict}\nThen commit.")
elif "Uncommitted changes" in str(exception): # Controlla messaggio eccezione
self.main_frame.show_warning("Action Blocked", f"{exception}\nCommit or stash first.")
else:
# Errore generico
self.main_frame.show_error("Error: Operation Failed", error_details)
# Aggiorna le liste della GUI per mostrare lo stato di errore (se applicabile)
if task_context == 'refresh_tags': self.main_frame.update_tag_list([("(Error)", "")])
elif task_context == 'refresh_branches': self.main_frame.update_branch_list([], None); self.main_frame.update_history_branch_filter([])
elif task_context == 'refresh_branches':
self.main_frame.update_branch_list([], None) # Pulisci lista branch
self.main_frame.update_history_branch_filter([]) # Pulisci anche il filtro history
elif task_context == 'refresh_history': self.main_frame.update_history_display(["(Error retrieving history)"])
elif task_context == 'refresh_changes': self.main_frame.update_changed_files_list(["(Error refreshing changes)"])
# Log finale per il processamento di questo risultato
log_handler.log_debug(f"Finished processing result for context '{task_context}'.", func_name="_check_completion_queue")
except queue.Empty:
# Reschedule check
self.master.after(self.ASYNC_QUEUE_CHECK_INTERVAL_MS, self._check_completion_queue, results_queue, context)
# La coda è vuota, significa che non c'erano risultati pronti.
# Pianifica un altro controllo in futuro.
if hasattr(self, "master") and self.master.winfo_exists():
self.master.after(self.ASYNC_QUEUE_CHECK_INTERVAL_MS, self._check_completion_queue, results_queue, context)
except Exception as e:
# Errore critico durante il processamento della coda stessa
log_handler.log_exception(f"Critical error processing completion queue for {task_context}: {e}", func_name="_check_completion_queue")
try: self.main_frame.set_action_widgets_state(tk.NORMAL) # Attempt recovery
except: pass
self.main_frame.update_status_bar("Error processing async result.", bg_color=self.main_frame.STATUS_RED, duration_ms=10000)
# Tenta di riabilitare i widget come misura di sicurezza
try:
if hasattr(self, "main_frame") and self.main_frame.winfo_exists():
self.main_frame.set_action_widgets_state(tk.NORMAL)
except: pass # Ignora errori nel tentativo di recupero
# Mostra errore generico nella status bar
if hasattr(self, "main_frame") and self.main_frame.winfo_exists():
self.main_frame.update_status_bar("Error processing async result.", bg_color=self.main_frame.STATUS_RED, duration_ms=10000)
# --- Punto di Ingresso (invariato) ---

154
gui.py
View File

@ -1181,44 +1181,68 @@ class MainFrame(ttk.Frame):
pass
def update_branch_list(self, branches, current_branch):
if (
not hasattr(self, "branch_listbox")
or not self.branch_listbox.winfo_exists()
):
"""Clears and populates the branch listbox."""
func_name = "update_branch_list (GUI)" # Nome specifico per i log
# ---<<< INIZIO MODIFICA DEBUG & ERRORE >>>---
log_handler.log_debug(
f"Received branches type={type(branches)}, count={len(branches) if isinstance(branches, list) else 'N/A'}, "
f"current={repr(current_branch)}",
func_name=func_name
)
listbox = getattr(self, "branch_listbox", None)
if not listbox or not listbox.winfo_exists():
log_handler.log_error("branch_listbox not available for update.", func_name=func_name)
return
try:
self.branch_listbox.config(state=tk.NORMAL)
self.branch_listbox.delete(0, tk.END)
listbox.config(state=tk.NORMAL)
listbox.delete(0, tk.END)
sel_idx = -1
if branches:
# Assicurati che 'branches' sia una lista prima di iterare
if isinstance(branches, list) and branches:
# Resetta colore (se era grigio o rosso)
try:
if self.branch_listbox.cget("fg") == "grey":
self.branch_listbox.config(
fg=self.style.lookup("TListbox", "foreground")
)
except tk.TclError:
pass
default_fg = self.style.lookup("TListbox", "foreground")
if listbox.cget("fg") != default_fg:
listbox.config(fg=default_fg)
except tk.TclError: pass # Ignora errori di stile
# Popola la lista
for i, branch in enumerate(branches):
prefix = "* " if branch == current_branch else " "
self.branch_listbox.insert(tk.END, f"{prefix}{branch}")
if branch == current_branch:
sel_idx = i
else:
self.branch_listbox.insert(tk.END, "(No local branches)")
self.branch_listbox.config(fg="grey")
# Assicura che branch sia una stringa prima di inserire
listbox.insert(tk.END, f"{prefix}{str(branch)}")
if branch == current_branch:
sel_idx = i
elif isinstance(branches, list) and not branches: # Lista vuota valida
listbox.insert(tk.END, "(No local branches)")
listbox.config(fg="grey")
else: # Caso in cui branches non è una lista (errore?)
log_handler.log_warning(f"Invalid data received for branches: {repr(branches)}", func_name=func_name)
listbox.insert(tk.END, "(Invalid data received)")
listbox.config(fg="orange")
# Imposta selezione e vista
if sel_idx >= 0:
self.branch_listbox.selection_set(sel_idx)
self.branch_listbox.see(sel_idx)
self.branch_listbox.config(state=tk.NORMAL)
self.branch_listbox.yview_moveto(0.0)
listbox.selection_set(sel_idx)
listbox.see(sel_idx)
listbox.config(state=tk.NORMAL) # Mantieni selezionabile
listbox.yview_moveto(0.0)
except Exception as e:
print(f"ERROR updating branch list GUI: {e}", file=sys.stderr)
try:
self.branch_listbox.delete(0, tk.END)
self.branch_listbox.insert(tk.END, "(Error)")
self.branch_listbox.config(fg="red")
except:
pass
log_handler.log_exception(f"Error updating branch list GUI: {e}", func_name=func_name)
# Fallback: Mostra errore nella listbox
try:
if listbox.winfo_exists():
listbox.config(state=tk.NORMAL)
listbox.delete(0, tk.END)
listbox.insert(tk.END, "(Error)")
listbox.config(fg="red", state=tk.DISABLED) # Disabilita su errore
except Exception as fallback_e:
log_handler.log_error(f"Error displaying fallback error in branch listbox: {fallback_e}", func_name=func_name)
# ---<<< FINE MODIFICA DEBUG & ERRORE >>>---
def get_selected_tag(self):
if hasattr(self, "tag_listbox") and self.tag_listbox.winfo_exists():
@ -1264,26 +1288,62 @@ class MainFrame(ttk.Frame):
pass
def update_history_display(self, log_lines):
if not hasattr(self, "history_text") or not self.history_text.winfo_exists():
"""Clears and populates the history ScrolledText widget."""
func_name = "update_history_display (GUI)" # Nome specifico per i log
# ---<<< INIZIO MODIFICA DEBUG & ERRORE >>>---
log_handler.log_debug(
f"Received log_lines type={type(log_lines)}, count={len(log_lines) if isinstance(log_lines, list) else 'N/A'}. "
f"Sample: {repr(log_lines[:5]) if isinstance(log_lines, list) else repr(log_lines)}",
func_name=func_name
)
history_widget = getattr(self, "history_text", None)
if not history_widget or not history_widget.winfo_exists():
log_handler.log_error("history_text widget not available for update.", func_name=func_name)
return
try:
self.history_text.config(state=tk.NORMAL)
self.history_text.delete("1.0", tk.END)
self.history_text.insert(
tk.END, "\n".join(log_lines) if log_lines else "(No history found)"
)
self.history_text.config(state=tk.DISABLED)
self.history_text.yview_moveto(0.0)
self.history_text.xview_moveto(0.0)
history_widget.config(state=tk.NORMAL)
history_widget.delete("1.0", tk.END)
# Assicurati che log_lines sia una lista prima di fare join
if isinstance(log_lines, list):
if log_lines:
# Resetta colore (se era rosso o altro)
try:
# Potrebbe non esserci un colore foreground specifico per ScrolledText nello stile
# Potremmo impostare a nero o lasciare il default del widget
default_fg = 'black' # Assumiamo nero come default sicuro
if history_widget.cget("fg") != default_fg:
history_widget.config(fg=default_fg)
except tk.TclError: pass
# Unisci le linee (assicurati siano stringhe)
text_to_insert = "\n".join(map(str, log_lines))
history_widget.insert(tk.END, text_to_insert)
else: # Lista vuota valida
history_widget.insert(tk.END, "(No history found)")
history_widget.config(fg="grey") # Colore grigio per indicare vuoto
else: # log_lines non è una lista (errore?)
log_handler.log_warning(f"Invalid data received for history: {repr(log_lines)}", func_name=func_name)
history_widget.insert(tk.END, f"(Invalid data received: {repr(log_lines)})")
history_widget.config(fg="orange") # Arancione per dato inatteso
history_widget.config(state=tk.DISABLED) # Rendi read-only
history_widget.yview_moveto(0.0)
history_widget.xview_moveto(0.0)
except Exception as e:
print(f"ERROR updating history GUI: {e}", file=sys.stderr)
try:
self.history_text.config(state=tk.NORMAL)
self.history_text.delete("1.0", tk.END)
self.history_text.insert(tk.END, "(Error)")
self.history_text.config(state=tk.DISABLED, fg="red")
except:
pass
log_handler.log_exception(f"Error updating history GUI: {e}", func_name=func_name)
# Fallback: Mostra errore nel widget di testo
try:
if history_widget.winfo_exists():
history_widget.config(state=tk.NORMAL)
history_widget.delete("1.0", tk.END)
history_widget.insert(tk.END, "(Error displaying history)")
history_widget.config(state=tk.DISABLED, fg="red") # Rosso e disabilitato
except Exception as fallback_e:
log_handler.log_error(f"Error displaying fallback error in history widget: {fallback_e}", func_name=func_name)
# ---<<< FINE MODIFICA DEBUG & ERRORE >>>---
def update_history_branch_filter(self, branches_tags, current_ref=None):
if (