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,121 +1834,197 @@ class GitSvnSyncApp:
def _check_completion_queue(self, results_queue, context): def _check_completion_queue(self, results_queue, context):
"""Checks result queue, updates GUI (incl. status bar color).""" """Checks result queue, updates GUI (incl. status bar color)."""
task_context = context.get('context', 'unknown') 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: try:
# Tenta di ottenere un risultato dalla coda senza bloccare
result_data = results_queue.get_nowait() 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") 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 --- # --- 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") log_handler.log_debug("Re-enabling widgets.", func_name="_check_completion_queue")
if hasattr(self, "main_frame") and self.main_frame.winfo_exists():
self.main_frame.set_action_widgets_state(tk.NORMAL) 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 --- # --- 2. Extract Details ---
# Estrae i dati dal dizionario del risultato, con fallback sicuri
status = result_data.get('status') status = result_data.get('status')
message = result_data.get('message') message = result_data.get('message')
result = result_data.get('result') result = result_data.get('result')
exception = result_data.get('exception') exception = result_data.get('exception')
committed = result_data.get('committed', False) committed = result_data.get('committed', False) # Flag per operazioni che potrebbero committare
is_conflict = result_data.get('conflict', False) is_conflict = result_data.get('conflict', False) # Flag specifico per conflitti merge
repo_path_conflict = result_data.get('repo_path') repo_path_conflict = result_data.get('repo_path') # Percorso repo in caso di conflitto
new_branch_context = context.get('new_branch_name') 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) --- # --- 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 status_color = None
reset_duration = 5000 # Resetta colore dopo 5 secondi reset_duration = 5000 # Default reset 5 secondi
if status == 'success': if status == 'success':
status_color = self.main_frame.STATUS_GREEN status_color = self.main_frame.STATUS_GREEN
elif status == 'warning': elif status == 'warning':
status_color = self.main_frame.STATUS_YELLOW status_color = self.main_frame.STATUS_YELLOW
reset_duration = 7000 reset_duration = 7000 # Warning dura un po' di più
elif status == 'error': elif status == 'error':
status_color = self.main_frame.STATUS_RED 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) self.main_frame.update_status_bar(message, bg_color=status_color, duration_ms=reset_duration)
# --- 4. Process Result & Trigger Updates --- # --- 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") 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': if status == 'success':
refresh_list = [] refresh_list = [] # Lista di funzioni di refresh da chiamare dopo
# (Logica per popolare refresh_list invariata)
# --- 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 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) # Aggiorna la history se c'è stato un commit o se l'operazione lo richiede implicitamente
if task_context != 'refresh_changes': refresh_list.append(self.refresh_changed_files_list) if committed or task_context in ['fetch_bundle','prepare_repo','create_tag','_handle_gitignore_save_async']:
if task_context not in ['refresh_tags','checkout_tag'] or committed: refresh_list.append(self.refresh_tag_list) if self.refresh_commit_history not in refresh_list: refresh_list.append(self.refresh_commit_history)
if task_context != 'refresh_branches': refresh_list.append(self.refresh_branch_list) # 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': 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': 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_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': 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': elif task_context == 'refresh_changes':
# ---<<< INIZIO MODIFICA DEBUG >>>--- # Log di debug aggiunto qui
# Logga esattamente cosa sta per essere passato a update_changed_files_list
log_handler.log_debug( log_handler.log_debug(
f"Preparing to call update_changed_files_list. " f"Preparing to call update_changed_files_list. "
f"Task Context: '{task_context}'. Result type: {type(result)}. Result value: {repr(result)}", f"Task Context: '{task_context}'. Result type: {type(result)}. Result value: {repr(result)}",
func_name="_check_completion_queue" func_name="_check_completion_queue"
) )
# ---<<< FINE MODIFICA DEBUG >>>--- self.main_frame.update_changed_files_list(result if isinstance(result, list) else []) # Assicura sia lista
self.main_frame.update_changed_files_list(result if result else [])
# (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: 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}'?"): 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) 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) 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: 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") 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: for refresh_func in refresh_list:
try: refresh_func() try:
except Exception as ref_e: log_handler.log_error(f"Error triggering {refresh_func.__name__}: {ref_e}", func_name="_check_completion_queue") 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: 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") log_handler.log_warning("Cannot trigger UI refreshes: Repo path unavailable.", func_name="_check_completion_queue")
elif status == 'warning': elif status == 'warning':
# (gestione warning invariata) # Gestione dei warning: mostra un popup informativo
if hasattr(self, "main_frame"):
self.main_frame.show_warning("Operation Info", message) self.main_frame.show_warning("Operation Info", message)
if "already prepared" in message: self.refresh_changed_files_list() # 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': elif status == 'error':
# (gestione errore invariata) # Gestione degli errori: mostra un popup di errore
error_details = f"{message}\n({exception})" if exception else message error_details = f"{message}\n({type(exception).__name__}: {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.") if hasattr(self, "main_frame"):
elif "Uncommitted changes" in message: self.main_frame.show_warning("Action Blocked", f"{exception}\nCommit or stash first.") # Mostra popup specifici per errori comuni
else: self.main_frame.show_error("Error: Operation Failed", error_details) if is_conflict and repo_path_conflict:
# Aggiornamento liste con errore (opzionale, dipende dal task) 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)", "")]) 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_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)"]) 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") log_handler.log_debug(f"Finished processing result for context '{task_context}'.", func_name="_check_completion_queue")
except queue.Empty: except queue.Empty:
# Reschedule check # 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) self.master.after(self.ASYNC_QUEUE_CHECK_INTERVAL_MS, self._check_completion_queue, results_queue, context)
except Exception as e: 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") 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 # Tenta di riabilitare i widget come misura di sicurezza
except: pass 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) self.main_frame.update_status_bar("Error processing async result.", bg_color=self.main_frame.STATUS_RED, duration_ms=10000)

156
gui.py
View File

@ -1181,44 +1181,68 @@ class MainFrame(ttk.Frame):
pass pass
def update_branch_list(self, branches, current_branch): def update_branch_list(self, branches, current_branch):
if ( """Clears and populates the branch listbox."""
not hasattr(self, "branch_listbox") func_name = "update_branch_list (GUI)" # Nome specifico per i log
or not self.branch_listbox.winfo_exists() # ---<<< INIZIO MODIFICA DEBUG & ERRORE >>>---
): log_handler.log_debug(
return f"Received branches type={type(branches)}, count={len(branches) if isinstance(branches, list) else 'N/A'}, "
try: f"current={repr(current_branch)}",
self.branch_listbox.config(state=tk.NORMAL) func_name=func_name
self.branch_listbox.delete(0, tk.END)
sel_idx = -1
if branches:
try:
if self.branch_listbox.cget("fg") == "grey":
self.branch_listbox.config(
fg=self.style.lookup("TListbox", "foreground")
) )
except tk.TclError: listbox = getattr(self, "branch_listbox", None)
pass if not listbox or not listbox.winfo_exists():
log_handler.log_error("branch_listbox not available for update.", func_name=func_name)
return
try:
listbox.config(state=tk.NORMAL)
listbox.delete(0, tk.END)
sel_idx = -1
# Assicurati che 'branches' sia una lista prima di iterare
if isinstance(branches, list) and branches:
# Resetta colore (se era grigio o rosso)
try:
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): for i, branch in enumerate(branches):
prefix = "* " if branch == current_branch else " " prefix = "* " if branch == current_branch else " "
self.branch_listbox.insert(tk.END, f"{prefix}{branch}") # Assicura che branch sia una stringa prima di inserire
listbox.insert(tk.END, f"{prefix}{str(branch)}")
if branch == current_branch: if branch == current_branch:
sel_idx = i sel_idx = i
else: elif isinstance(branches, list) and not branches: # Lista vuota valida
self.branch_listbox.insert(tk.END, "(No local branches)") listbox.insert(tk.END, "(No local branches)")
self.branch_listbox.config(fg="grey") 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: if sel_idx >= 0:
self.branch_listbox.selection_set(sel_idx) listbox.selection_set(sel_idx)
self.branch_listbox.see(sel_idx) listbox.see(sel_idx)
self.branch_listbox.config(state=tk.NORMAL)
self.branch_listbox.yview_moveto(0.0) listbox.config(state=tk.NORMAL) # Mantieni selezionabile
listbox.yview_moveto(0.0)
except Exception as e: except Exception as e:
print(f"ERROR updating branch list GUI: {e}", file=sys.stderr) log_handler.log_exception(f"Error updating branch list GUI: {e}", func_name=func_name)
# Fallback: Mostra errore nella listbox
try: try:
self.branch_listbox.delete(0, tk.END) if listbox.winfo_exists():
self.branch_listbox.insert(tk.END, "(Error)") listbox.config(state=tk.NORMAL)
self.branch_listbox.config(fg="red") listbox.delete(0, tk.END)
except: listbox.insert(tk.END, "(Error)")
pass 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): def get_selected_tag(self):
if hasattr(self, "tag_listbox") and self.tag_listbox.winfo_exists(): if hasattr(self, "tag_listbox") and self.tag_listbox.winfo_exists():
@ -1264,26 +1288,62 @@ class MainFrame(ttk.Frame):
pass pass
def update_history_display(self, log_lines): 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."""
return func_name = "update_history_display (GUI)" # Nome specifico per i log
try: # ---<<< INIZIO MODIFICA DEBUG & ERRORE >>>---
self.history_text.config(state=tk.NORMAL) log_handler.log_debug(
self.history_text.delete("1.0", tk.END) f"Received log_lines type={type(log_lines)}, count={len(log_lines) if isinstance(log_lines, list) else 'N/A'}. "
self.history_text.insert( f"Sample: {repr(log_lines[:5]) if isinstance(log_lines, list) else repr(log_lines)}",
tk.END, "\n".join(log_lines) if log_lines else "(No history found)" func_name=func_name
) )
self.history_text.config(state=tk.DISABLED) history_widget = getattr(self, "history_text", None)
self.history_text.yview_moveto(0.0) if not history_widget or not history_widget.winfo_exists():
self.history_text.xview_moveto(0.0) log_handler.log_error("history_text widget not available for update.", func_name=func_name)
except Exception as e: return
print(f"ERROR updating history GUI: {e}", file=sys.stderr)
try: try:
self.history_text.config(state=tk.NORMAL) history_widget.config(state=tk.NORMAL)
self.history_text.delete("1.0", tk.END) history_widget.delete("1.0", tk.END)
self.history_text.insert(tk.END, "(Error)")
self.history_text.config(state=tk.DISABLED, fg="red") # Assicurati che log_lines sia una lista prima di fare join
except: if isinstance(log_lines, list):
pass 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:
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): def update_history_branch_filter(self, branches_tags, current_ref=None):
if ( if (