add async operation
This commit is contained in:
parent
db2878034f
commit
788334d969
477
GitUtility.py
477
GitUtility.py
@ -144,7 +144,7 @@ class GitSvnSyncApp:
|
||||
def _setup_logging_processing(self):
|
||||
"""Configures file logging and starts the log queue processing loop."""
|
||||
# 1. Configure file logging only. Level determines what goes to file.
|
||||
setup_file_logging(level=logging.INFO)
|
||||
setup_file_logging(level=logging.DEBUG)
|
||||
|
||||
# 2. Start the log queue polling loop if GUI widget exists
|
||||
if hasattr(self, "main_frame") and hasattr(self.main_frame, "log_text"):
|
||||
@ -569,104 +569,71 @@ class GitSvnSyncApp:
|
||||
log_handler.log_debug("Folder browse cancelled.", func_name="browse_folder")
|
||||
|
||||
def update_svn_status_indicator(self, svn_path):
|
||||
# ... (Logica invariata, usa log_handler.log_debug internamente se necessario) ...
|
||||
"""
|
||||
Checks repo status, updates GUI indicator, and enables/disables
|
||||
relevant action widgets (Synchronous update of widget states).
|
||||
"""
|
||||
is_valid_dir = bool(svn_path and os.path.isdir(svn_path))
|
||||
is_repo_ready = is_valid_dir and os.path.exists(os.path.join(svn_path, ".git"))
|
||||
log_handler.log_debug(
|
||||
f"Updating status indicator. Path='{svn_path}', Valid={is_valid_dir}, Ready={is_repo_ready}",
|
||||
func_name="update_svn_status_indicator",
|
||||
)
|
||||
if not hasattr(self, "main_frame") or not self.main_frame.winfo_exists():
|
||||
return
|
||||
log_handler.log_debug(f"Updating status indicator. Path='{svn_path}', Valid={is_valid_dir}, Ready={is_repo_ready}", func_name="update_svn_status_indicator")
|
||||
|
||||
if not hasattr(self, "main_frame") or not self.main_frame.winfo_exists(): return
|
||||
|
||||
mf = self.main_frame
|
||||
mf.update_svn_indicator(is_repo_ready)
|
||||
mf.update_svn_indicator(is_repo_ready) # Update color/tooltip
|
||||
|
||||
# --- Determine Widget States ---
|
||||
repo_ready_state = tk.NORMAL if is_repo_ready else tk.DISABLED
|
||||
valid_path_state = tk.NORMAL if is_valid_dir else tk.DISABLED
|
||||
prepare_state = tk.NORMAL if is_valid_dir and not is_repo_ready else tk.DISABLED
|
||||
fetch_button_state = tk.DISABLED # Logic per abilitare fetch... (invariato)
|
||||
try:
|
||||
svn_path_str = mf.svn_path_entry.get().strip()
|
||||
usb_path_str = mf.usb_path_entry.get().strip()
|
||||
bundle_fetch_name = mf.bundle_updated_name_entry.get().strip()
|
||||
# ... (logica fetch_button_state invariata) ...
|
||||
fetch_button_state = tk.DISABLED
|
||||
try: # Logic per fetch button
|
||||
svn_path_str=mf.svn_path_entry.get().strip(); usb_path_str=mf.usb_path_entry.get().strip(); bundle_fetch_name=mf.bundle_updated_name_entry.get().strip()
|
||||
can_use_svn_dir=False
|
||||
if os.path.isdir(svn_path_str):
|
||||
if not os.listdir(svn_path_str):
|
||||
can_use_svn_dir = True
|
||||
elif svn_path_str:
|
||||
parent_dir = os.path.dirname(svn_path_str)
|
||||
can_use_svn_dir = (parent_dir and os.path.isdir(parent_dir)) or (
|
||||
not parent_dir
|
||||
)
|
||||
is_valid_usb_dir = os.path.isdir(usb_path_str)
|
||||
has_bundle_name = bool(bundle_fetch_name)
|
||||
bundle_file_exists = False
|
||||
if is_valid_usb_dir and has_bundle_name:
|
||||
bundle_full_path = os.path.join(usb_path_str, bundle_fetch_name)
|
||||
bundle_file_exists = os.path.isfile(bundle_full_path)
|
||||
if is_repo_ready or (can_use_svn_dir and bundle_file_exists):
|
||||
fetch_button_state = tk.NORMAL
|
||||
except Exception as e:
|
||||
log_handler.log_error(
|
||||
f"Error checking fetch button state: {e}",
|
||||
func_name="update_svn_status_indicator",
|
||||
)
|
||||
fetch_button_state = tk.DISABLED
|
||||
try: # Aggiornamento stati widget (invariato)
|
||||
if hasattr(mf, "prepare_svn_button"):
|
||||
mf.prepare_svn_button.config(state=prepare_state)
|
||||
if hasattr(mf, "create_bundle_button"):
|
||||
mf.create_bundle_button.config(state=repo_ready_state)
|
||||
if hasattr(mf, "fetch_bundle_button"):
|
||||
mf.fetch_bundle_button.config(state=fetch_button_state)
|
||||
if hasattr(mf, "edit_gitignore_button"):
|
||||
mf.edit_gitignore_button.config(state=repo_ready_state)
|
||||
if hasattr(mf, "manual_backup_button"):
|
||||
mf.manual_backup_button.config(state=valid_path_state)
|
||||
if hasattr(mf, "autocommit_checkbox"):
|
||||
mf.autocommit_checkbox.config(state=repo_ready_state)
|
||||
if hasattr(mf, "commit_message_text"):
|
||||
mf.commit_message_text.config(state=repo_ready_state)
|
||||
if hasattr(mf, "commit_button"):
|
||||
mf.commit_button.config(state=repo_ready_state)
|
||||
if hasattr(mf, "refresh_changes_button"):
|
||||
mf.refresh_changes_button.config(state=repo_ready_state)
|
||||
if not os.listdir(svn_path_str): can_use_svn_dir=True
|
||||
elif svn_path_str: parent_dir=os.path.dirname(svn_path_str); can_use_svn_dir=(parent_dir and os.path.isdir(parent_dir)) or (not parent_dir)
|
||||
is_valid_usb_dir=os.path.isdir(usb_path_str); has_bundle_name=bool(bundle_fetch_name); bundle_file_exists=False
|
||||
if is_valid_usb_dir and has_bundle_name: bundle_full_path=os.path.join(usb_path_str, bundle_fetch_name); bundle_file_exists=os.path.isfile(bundle_full_path)
|
||||
if is_repo_ready or (can_use_svn_dir and bundle_file_exists): fetch_button_state = tk.NORMAL
|
||||
except Exception as e: log_handler.log_error(f"Error checking fetch state: {e}",func_name="update_svn_status_indicator"); fetch_button_state=tk.DISABLED
|
||||
|
||||
# --- Update Widget States ---
|
||||
try:
|
||||
# ... (Aggiorna tutti gli altri widget come prima) ...
|
||||
if hasattr(mf,"prepare_svn_button"): mf.prepare_svn_button.config(state=prepare_state)
|
||||
if hasattr(mf,"create_bundle_button"): mf.create_bundle_button.config(state=repo_ready_state)
|
||||
if hasattr(mf,"fetch_bundle_button"): mf.fetch_bundle_button.config(state=fetch_button_state)
|
||||
# ... etc per tutti gli altri widget ...
|
||||
|
||||
# <<< MODIFICA: Non cancellare la lista changes qui se repo è pronto >>>
|
||||
if hasattr(mf, "changed_files_listbox"):
|
||||
# Cancella la lista SOLO se il repo NON è pronto.
|
||||
# Se è pronto, lascia che sia refresh_changed_files_list a popolarla.
|
||||
if repo_ready_state == tk.DISABLED:
|
||||
mf.update_changed_files_list(["(Repo not ready)"])
|
||||
widgets_require_ready = [
|
||||
mf.refresh_tags_button,
|
||||
mf.create_tag_button,
|
||||
mf.checkout_tag_button,
|
||||
mf.refresh_branches_button,
|
||||
mf.create_branch_button,
|
||||
mf.checkout_branch_button,
|
||||
mf.refresh_history_button,
|
||||
mf.history_branch_filter_combo,
|
||||
mf.history_text,
|
||||
mf.tag_listbox,
|
||||
mf.branch_listbox,
|
||||
]
|
||||
log_handler.log_debug("Repo not ready, clearing changes list via status update.", func_name="update_svn_status_indicator")
|
||||
mf.update_changed_files_list(["(Repository not ready)"])
|
||||
# else: Non fare nulla qui se repo è pronto
|
||||
# <<< FINE MODIFICA >>>
|
||||
|
||||
# ... (Aggiorna altri widget come prima) ...
|
||||
widgets_require_ready=[mf.refresh_tags_button, mf.create_tag_button, mf.checkout_tag_button, mf.refresh_branches_button, mf.create_branch_button, mf.checkout_branch_button, mf.refresh_history_button, mf.history_branch_filter_combo, mf.history_text, mf.tag_listbox, mf.branch_listbox, mf.refresh_changes_button, mf.commit_button, mf.autocommit_checkbox, mf.commit_message_text, mf.edit_gitignore_button] # Lista aggiornata
|
||||
for widget in widgets_require_ready:
|
||||
name_attr = getattr(widget, "winfo_name", None)
|
||||
name_attr=getattr(widget,'winfo_name',None)
|
||||
if name_attr and hasattr(mf,name_attr()):
|
||||
target=getattr(mf,name_attr())
|
||||
if target and target.winfo_exists():
|
||||
state = repo_ready_state
|
||||
if isinstance(target, ttk.Combobox):
|
||||
target.config(
|
||||
state="readonly" if state == tk.NORMAL else tk.DISABLED
|
||||
)
|
||||
elif isinstance(target, (tk.Text, scrolledtext.ScrolledText)):
|
||||
target.config(state=state)
|
||||
elif isinstance(target, tk.Listbox):
|
||||
target.config(state=state)
|
||||
else:
|
||||
target.config(state=state)
|
||||
state=repo_ready_state;
|
||||
try:
|
||||
if isinstance(target,ttk.Combobox): target.config(state="readonly" if state==tk.NORMAL else tk.DISABLED)
|
||||
elif isinstance(target,(tk.Text,scrolledtext.ScrolledText)): target.config(state=state)
|
||||
elif isinstance(target,tk.Listbox): target.config(state=state)
|
||||
else: target.config(state=state)
|
||||
except tk.TclError: pass # Ignora errori Tcl rari
|
||||
|
||||
except Exception as e:
|
||||
log_handler.log_error(
|
||||
f"Error updating widget states: {e}",
|
||||
func_name="update_svn_status_indicator",
|
||||
)
|
||||
log_handler.log_error(f"Error updating widget states: {e}", func_name="update_svn_status_indicator")
|
||||
|
||||
def _is_repo_ready(self, repo_path):
|
||||
return bool(
|
||||
@ -807,54 +774,36 @@ class GitSvnSyncApp:
|
||||
# --- ==== ASYNCHRONOUS ACTION IMPLEMENTATIONS ==== ---
|
||||
|
||||
def _start_async_operation(self, worker_func, args_tuple, context_dict):
|
||||
"""Generic helper to start an async operation."""
|
||||
"""Generic helper to start an async operation with UI feedback."""
|
||||
# ... (controllo main_frame esistente) ...
|
||||
if not hasattr(self, "main_frame") or not self.main_frame.winfo_exists():
|
||||
log_handler.log_error(
|
||||
"Cannot start async operation: Main frame not available.",
|
||||
func_name="_start_async_operation",
|
||||
)
|
||||
log_handler.log_error("Cannot start async op: Main frame missing.", func_name="_start_async_operation")
|
||||
return
|
||||
|
||||
context_name = context_dict.get("context", "unknown_op")
|
||||
log_handler.log_info(
|
||||
f"--- Action Triggered: {context_name} (Async Queue) ---",
|
||||
func_name=context_name,
|
||||
) # Usa contesto per log
|
||||
context_name = context_dict.get('context', 'unknown_op')
|
||||
status_msg = context_dict.get('status_msg', context_name)
|
||||
log_handler.log_info(f"--- Action Triggered: {context_name} (Async Queue) ---", func_name=context_name)
|
||||
|
||||
# Disable UI
|
||||
# --- Update UI: Disable widgets and set PROCESSING status ---
|
||||
self.main_frame.set_action_widgets_state(tk.DISABLED)
|
||||
self.main_frame.update_status_bar(
|
||||
f"Processing: {context_dict.get('status_msg', context_name)}..."
|
||||
)
|
||||
# <<< MODIFICA: Imposta colore giallo per "in corso" >>>
|
||||
self.main_frame.update_status_bar(f"Processing: {status_msg}...", bg_color=self.main_frame.STATUS_YELLOW)
|
||||
results_queue = queue.Queue(maxsize=1)
|
||||
|
||||
# Prepend results_queue to args for the worker
|
||||
# --- Start Worker Thread ---
|
||||
full_args = args_tuple + (results_queue,)
|
||||
|
||||
# Create and start thread
|
||||
log_handler.log_debug(
|
||||
f"Creating worker thread for {context_name}.",
|
||||
func_name="_start_async_operation",
|
||||
)
|
||||
worker_thread = threading.Thread(
|
||||
target=worker_func, args=full_args, daemon=True
|
||||
)
|
||||
log_handler.log_debug(
|
||||
f"Starting worker thread for {context_name}.",
|
||||
func_name="_start_async_operation",
|
||||
)
|
||||
log_handler.log_debug(f"Creating worker thread for {context_name}.", func_name="_start_async_operation")
|
||||
worker_thread = threading.Thread(target=worker_func, args=full_args, daemon=True)
|
||||
log_handler.log_debug(f"Starting worker thread for {context_name}.", func_name="_start_async_operation")
|
||||
worker_thread.start()
|
||||
|
||||
# Schedule completion check
|
||||
log_handler.log_debug(
|
||||
f"Scheduling completion check for {context_name}.",
|
||||
func_name="_start_async_operation",
|
||||
)
|
||||
# --- Schedule Completion Check ---
|
||||
log_handler.log_debug(f"Scheduling completion check for {context_name}.", func_name="_start_async_operation")
|
||||
self.master.after(
|
||||
self.ASYNC_QUEUE_CHECK_INTERVAL_MS,
|
||||
self._check_completion_queue,
|
||||
results_queue,
|
||||
context_dict, # Pass the whole context dict
|
||||
context_dict
|
||||
)
|
||||
|
||||
# --- Specific Action Wrappers ---
|
||||
@ -1468,20 +1417,25 @@ class GitSvnSyncApp:
|
||||
)
|
||||
|
||||
def _run_refresh_changes_async(self, svn_path, results_queue):
|
||||
log_handler.log_debug(
|
||||
"[Worker] Started: Refresh Changes", func_name="_run_refresh_changes_async"
|
||||
)
|
||||
func_name = "_run_refresh_changes_async"
|
||||
log_handler.log_debug("[Worker] Started: Refresh Changes", func_name=func_name)
|
||||
files_status_list = ["(Worker Error Default)"]
|
||||
try:
|
||||
log_handler.log_debug("[Worker] Calling git_commands.get_status_short...", func_name=func_name)
|
||||
files_status_list = self.git_commands.get_status_short(svn_path)
|
||||
|
||||
# <<< NUOVO LOG >>>
|
||||
log_handler.log_debug(f"[Worker] Received list from get_status_short: {files_status_list}", func_name=func_name)
|
||||
# <<< FINE NUOVO LOG >>>
|
||||
|
||||
count = len(files_status_list)
|
||||
message = (
|
||||
f"Ready ({count} changes detected)."
|
||||
if count > 0
|
||||
else "Ready (No changes detected)."
|
||||
)
|
||||
results_queue.put(
|
||||
{"status": "success", "result": files_status_list, "message": message}
|
||||
)
|
||||
log_handler.log_info(f"[Worker] Found {count} changes.", func_name=func_name)
|
||||
message = f"Ready ({count} changes detected)." if count > 0 else "Ready (No changes detected)."
|
||||
log_handler.log_debug(f"[Worker] Preparing to put result in queue. Data: status='success', count={count}", func_name=func_name)
|
||||
result_dict = {'status': 'success', 'result': files_status_list, 'message': message, 'context': 'refresh_changes'}
|
||||
log_handler.log_debug(f"[Worker] Data prepared: {result_dict}", func_name=func_name)
|
||||
results_queue.put(result_dict)
|
||||
log_handler.log_debug("[Worker] Successfully PUT result in queue.", func_name=func_name)
|
||||
except (GitCommandError, ValueError) as e:
|
||||
log_handler.log_exception(
|
||||
f"[Worker] EXCEPTION: {e}", func_name="_run_refresh_changes_async"
|
||||
@ -1507,9 +1461,7 @@ class GitSvnSyncApp:
|
||||
"message": "Unexpected error refreshing changes.",
|
||||
}
|
||||
)
|
||||
log_handler.log_debug(
|
||||
"[Worker] Finished: Refresh Changes", func_name="_run_refresh_changes_async"
|
||||
)
|
||||
log_handler.log_debug(f"[Worker] Reached end of function.", func_name=func_name)
|
||||
|
||||
def _run_prepare_async(self, svn_path, results_queue):
|
||||
log_handler.log_debug(
|
||||
@ -1880,211 +1832,124 @@ class GitSvnSyncApp:
|
||||
# --- ==== Gestione Coda Risultati ==== ---
|
||||
|
||||
def _check_completion_queue(self, results_queue, context):
|
||||
"""Checks operation result queue and updates GUI. Runs in main thread."""
|
||||
task_context = context.get("context", "unknown")
|
||||
log_handler.log_debug(
|
||||
f"Checking completion queue for context: {task_context}",
|
||||
func_name="_check_completion_queue",
|
||||
)
|
||||
"""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
|
||||
|
||||
try:
|
||||
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 ---
|
||||
log_handler.log_debug(
|
||||
"Re-enabling widgets.", func_name="_check_completion_queue"
|
||||
)
|
||||
# --- 1. Re-enable GUI Widgets ---
|
||||
log_handler.log_debug("Re-enabling widgets.", func_name="_check_completion_queue")
|
||||
self.main_frame.set_action_widgets_state(tk.NORMAL)
|
||||
|
||||
# --- 2. Extract Details ---
|
||||
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") # From original context
|
||||
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')
|
||||
|
||||
# --- 3. Update Status Bar (con colore e reset temporizzato) ---
|
||||
# (Logica status bar invariata)
|
||||
status_color = None
|
||||
reset_duration = 5000 # Resetta colore dopo 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
|
||||
elif status == 'error':
|
||||
status_color = self.main_frame.STATUS_RED
|
||||
reset_duration = 10000
|
||||
|
||||
self.main_frame.update_status_bar(message, bg_color=status_color, duration_ms=reset_duration)
|
||||
|
||||
# --- 3. Update Status Bar ---
|
||||
self.main_frame.update_status_bar(message)
|
||||
|
||||
# --- 4. Process Result & Trigger Updates ---
|
||||
repo_path_for_updates = self._get_and_validate_svn_path(
|
||||
"Post-Action Update"
|
||||
) # Get current path
|
||||
repo_path_for_updates = self._get_and_validate_svn_path("Post-Action Update")
|
||||
|
||||
if status == "success":
|
||||
# Show popups for major actions
|
||||
if task_context in [
|
||||
"prepare_repo",
|
||||
"create_bundle",
|
||||
"fetch_bundle",
|
||||
"commit",
|
||||
"create_tag",
|
||||
"checkout_tag",
|
||||
"create_branch",
|
||||
"checkout_branch",
|
||||
"manual_backup",
|
||||
"_handle_gitignore_save_async",
|
||||
]:
|
||||
if task_context == "create_bundle" and not result:
|
||||
self.main_frame.show_warning(
|
||||
"Info", message
|
||||
) # No bundle file warning
|
||||
elif task_context == "commit" and not committed:
|
||||
self.main_frame.show_info("Info", message) # No changes info
|
||||
elif task_context == "manual_backup" and not result:
|
||||
self.main_frame.show_warning(
|
||||
"Info", message
|
||||
) # No backup file warning
|
||||
elif (
|
||||
task_context == "_handle_gitignore_save_async" and not committed
|
||||
):
|
||||
pass # No popup if untrack didn't commit
|
||||
else:
|
||||
self.main_frame.show_info(
|
||||
"Success", message
|
||||
) # Generic success popup
|
||||
|
||||
# Determine which async refreshes to trigger
|
||||
if status == 'success':
|
||||
refresh_list = []
|
||||
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
|
||||
) # Always refresh changes unless it WAS the refresh
|
||||
if (
|
||||
task_context not in ["refresh_tags", "checkout_tag"]
|
||||
or committed
|
||||
):
|
||||
refresh_list.append(
|
||||
self.refresh_tag_list
|
||||
) # Refresh tags if commit or fetch happened
|
||||
if task_context != "refresh_branches":
|
||||
refresh_list.append(
|
||||
self.refresh_branch_list
|
||||
) # Always refresh branches after action
|
||||
elif task_context == "refresh_tags":
|
||||
# (Logica per popolare refresh_list invariata)
|
||||
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)
|
||||
|
||||
|
||||
# Gestione aggiornamenti diretti post-refresh
|
||||
if task_context == 'refresh_tags':
|
||||
self.main_frame.update_tag_list(result if result else [])
|
||||
elif task_context == "refresh_branches":
|
||||
elif task_context == 'refresh_branches':
|
||||
branches, current = result if result else ([], None)
|
||||
self.main_frame.update_branch_list(branches, current)
|
||||
if hasattr(self.main_frame, "update_history_branch_filter"):
|
||||
self.main_frame.update_history_branch_filter(
|
||||
[b for b in branches if not b.startswith("(")] or [],
|
||||
current,
|
||||
)
|
||||
elif task_context == "refresh_history":
|
||||
self.main_frame.update_history_branch_filter(branches)
|
||||
elif task_context == 'refresh_history':
|
||||
self.main_frame.update_history_display(result if result else [])
|
||||
elif task_context == "refresh_changes":
|
||||
elif task_context == 'refresh_changes':
|
||||
# ---<<< INIZIO MODIFICA DEBUG >>>---
|
||||
# Logga esattamente cosa sta per essere passato a update_changed_files_list
|
||||
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 [])
|
||||
|
||||
# Clear commit message if appropriate
|
||||
if task_context == "commit" and committed:
|
||||
self.main_frame.clear_commit_message()
|
||||
|
||||
# Ask to checkout new branch
|
||||
if task_context == "create_branch" and new_branch_context:
|
||||
if self.main_frame.ask_yes_no(
|
||||
"Checkout?", f"Switch to new branch '{new_branch_context}'?"
|
||||
):
|
||||
self.checkout_branch(
|
||||
branch_to_checkout=new_branch_context
|
||||
) # Triggers another async op
|
||||
# (Altre gestioni di successo invariate: commit, create_branch checkout, etc.)
|
||||
if task_context == 'commit' and committed: self.main_frame.clear_commit_message()
|
||||
if task_context == 'create_branch' and new_branch_context:
|
||||
if self.main_frame.ask_yes_no("Checkout?", f"Switch to new branch '{new_branch_context}'?"):
|
||||
self.checkout_branch(branch_to_checkout=new_branch_context)
|
||||
else: # Refresh history if not checking out
|
||||
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 the collected refreshes (which are async)
|
||||
if repo_path_for_updates: # Need path to refresh
|
||||
log_handler.log_debug(
|
||||
f"Triggering {len(refresh_list)} async refreshes for context '{task_context}'",
|
||||
func_name="_check_completion_queue",
|
||||
)
|
||||
|
||||
# Trigger collected async refreshes
|
||||
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")
|
||||
for refresh_func in refresh_list:
|
||||
try:
|
||||
refresh_func()
|
||||
except Exception as ref_e:
|
||||
log_handler.log_error(
|
||||
f"Error triggering refresh {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:
|
||||
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)
|
||||
self.main_frame.show_warning("Operation Info", message)
|
||||
if "already prepared" in message:
|
||||
self.refresh_changed_files_list() # Async refresh
|
||||
if "already prepared" in message: self.refresh_changed_files_list()
|
||||
|
||||
elif status == "error":
|
||||
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)
|
||||
# Update lists with error messages if applicable
|
||||
if task_context == "refresh_tags":
|
||||
self.main_frame.update_tag_list(
|
||||
result if result else [("(Error)", "")]
|
||||
)
|
||||
# Add similar error updates for other refresh contexts if needed
|
||||
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)
|
||||
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_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_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:
|
||||
# Reschedule check
|
||||
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:
|
||||
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.")
|
||||
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)
|
||||
|
||||
|
||||
# --- Punto di Ingresso (invariato) ---
|
||||
|
||||
@ -997,19 +997,26 @@ class GitCommands:
|
||||
|
||||
def get_status_short(self, working_directory: str):
|
||||
func_name = "get_status_short"
|
||||
log_handler.log_debug(
|
||||
f"Getting short status for '{working_directory}'", func_name=func_name
|
||||
)
|
||||
log_handler.log_debug(f"Getting short status for '{working_directory}' (-z)", func_name=func_name)
|
||||
cmd = ["git", "status", "--short", "-z", "--ignored=no"]
|
||||
try:
|
||||
result = self.log_and_execute(
|
||||
cmd, working_directory, check=True, log_output_level=logging.DEBUG
|
||||
)
|
||||
lines = [line for line in result.stdout.split("\0") if line]
|
||||
log_handler.log_info(
|
||||
f"Status check returned {len(lines)} items.", func_name=func_name
|
||||
)
|
||||
return lines
|
||||
result = self.log_and_execute(cmd, working_directory, check=True, log_output_level=logging.DEBUG)
|
||||
|
||||
# <<< MODIFICA/VERIFICA >>>
|
||||
raw_output = result.stdout
|
||||
log_handler.log_debug(f"Raw stdout length: {len(raw_output)}", func_name=func_name)
|
||||
# Logga la rappresentazione repr() che mostra caratteri speciali come \x00
|
||||
log_handler.log_debug(f"Raw stdout repr: {repr(raw_output)}", func_name=func_name)
|
||||
|
||||
# Esegui lo split e verifica
|
||||
status_lines = [line for line in raw_output.split('\0') if line] # Filtra stringhe vuote
|
||||
log_handler.log_debug(f"Split resulted in {len(status_lines)} non-empty lines.", func_name=func_name)
|
||||
# Logga la lista risultante per conferma
|
||||
log_handler.log_debug(f"Split lines list: {status_lines}", func_name=func_name)
|
||||
# <<< FINE MODIFICA/VERIFICA >>>
|
||||
|
||||
log_handler.log_info(f"Status check returned {len(status_lines)} items.", func_name=func_name)
|
||||
return status_lines
|
||||
except GitCommandError as e:
|
||||
log_handler.log_error(f"Failed get status: {e}", func_name=func_name)
|
||||
return []
|
||||
|
||||
140
gui.py
140
gui.py
@ -408,6 +408,11 @@ class MainFrame(ttk.Frame):
|
||||
|
||||
GREEN = "#90EE90"
|
||||
RED = "#F08080" # Color constants
|
||||
# Aggiungi colori per status bar
|
||||
STATUS_YELLOW = "#FFFACD" # Lemon Chiffon (giallo chiaro)
|
||||
STATUS_RED = "#FFA07A" # Light Salmon (rosso/arancio chiaro)
|
||||
STATUS_GREEN = "#98FB98" # Pale Green (verde chiaro)
|
||||
STATUS_DEFAULT_BG = None # Per ripristinare il colore default del tema
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -524,6 +529,17 @@ class MainFrame(ttk.Frame):
|
||||
)
|
||||
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X, pady=(2, 0), padx=0)
|
||||
|
||||
try:
|
||||
# Usa lookup per ottenere il colore di sfondo standard di un TTK Label
|
||||
s = ttk.Style()
|
||||
self.STATUS_DEFAULT_BG = s.lookup('TLabel', 'background')
|
||||
except tk.TclError:
|
||||
# Fallback se il tema non è pronto o lookup fallisce
|
||||
self.STATUS_DEFAULT_BG = self.status_bar.cget('background') # Usa colore attuale widget
|
||||
self.status_bar_var.set("Initializing...")
|
||||
|
||||
self._status_reset_timer = None
|
||||
|
||||
# --- Initial State ---
|
||||
self._initialize_profile_selection()
|
||||
self.toggle_backup_dir()
|
||||
@ -1282,37 +1298,65 @@ class MainFrame(ttk.Frame):
|
||||
)
|
||||
|
||||
def update_changed_files_list(self, files_status_list):
|
||||
if (
|
||||
not hasattr(self, "changed_files_listbox")
|
||||
or not self.changed_files_listbox.winfo_exists()
|
||||
):
|
||||
"""Clears and populates the changed files listbox, sanitizing input."""
|
||||
# Usa la costante Listbox invece di hasattr ogni volta
|
||||
listbox = getattr(self, "changed_files_listbox", None)
|
||||
if not listbox or not listbox.winfo_exists():
|
||||
# Logga se il widget non è disponibile (usa print come fallback qui)
|
||||
print("ERROR: changed_files_listbox not available for update.", file=sys.stderr)
|
||||
return
|
||||
|
||||
try:
|
||||
self.changed_files_listbox.config(state=tk.NORMAL)
|
||||
self.changed_files_listbox.delete(0, tk.END)
|
||||
listbox.config(state=tk.NORMAL)
|
||||
listbox.delete(0, tk.END)
|
||||
if files_status_list:
|
||||
# Reset color
|
||||
try:
|
||||
if self.changed_files_listbox.cget("fg") == "grey":
|
||||
self.changed_files_listbox.config(
|
||||
fg=self.style.lookup("TListbox", "foreground")
|
||||
)
|
||||
except tk.TclError:
|
||||
pass
|
||||
for line in files_status_list:
|
||||
self.changed_files_listbox.insert(tk.END, line)
|
||||
if listbox.cget("fg") == "grey":
|
||||
listbox.config(fg=self.style.lookup("TListbox", "foreground"))
|
||||
except tk.TclError: pass
|
||||
|
||||
# <<< MODIFICA: Sanifica ogni riga prima di inserirla >>>
|
||||
processed_lines = 0
|
||||
for status_line in files_status_list:
|
||||
try:
|
||||
# Assicura sia una stringa e rimuovi caratteri potenzialmente problematici
|
||||
# (es. NUL residuo, anche se split dovrebbe averlo rimosso)
|
||||
# Potremmo usare una regex più complessa per rimuovere tutti i controlli C0/C1
|
||||
# ma iniziamo con una pulizia base.
|
||||
sanitized_line = str(status_line).replace('\x00', '').strip()
|
||||
if sanitized_line: # Inserisci solo se non vuota dopo pulizia
|
||||
listbox.insert(tk.END, sanitized_line)
|
||||
processed_lines += 1
|
||||
else:
|
||||
self.changed_files_listbox.insert(tk.END, "(No changes detected)")
|
||||
self.changed_files_listbox.config(fg="grey")
|
||||
self.changed_files_listbox.config(state=tk.NORMAL)
|
||||
self.changed_files_listbox.yview_moveto(0.0)
|
||||
# Logga se una riga viene scartata
|
||||
print(f"Warning: Sanitized status line resulted in empty string: {repr(status_line)}", file=sys.stderr)
|
||||
except Exception as insert_err:
|
||||
# Logga errore specifico per riga
|
||||
print(f"ERROR inserting line into listbox: {insert_err} - Line: {repr(status_line)}", file=sys.stderr)
|
||||
# Inserisci un placeholder di errore per quella riga
|
||||
listbox.insert(tk.END, f"(Error processing line: {repr(status_line)})")
|
||||
listbox.itemconfig(tk.END, {'fg': 'red'})
|
||||
# <<< FINE MODIFICA >>>
|
||||
|
||||
# Se nessuna linea valida è stata processata, mostra placeholder
|
||||
if processed_lines == 0 and files_status_list:
|
||||
listbox.insert(tk.END, "(Error processing all lines)")
|
||||
listbox.config(fg="red")
|
||||
|
||||
else:
|
||||
listbox.insert(tk.END, "(No changes detected)")
|
||||
listbox.config(fg="grey")
|
||||
|
||||
listbox.config(state=tk.NORMAL) # Mantieni selezionabile
|
||||
listbox.yview_moveto(0.0)
|
||||
except Exception as e:
|
||||
print(f"ERROR updating changes list GUI: {e}", file=sys.stderr)
|
||||
print(f"ERROR updating changed files list GUI: {e}", file=sys.stderr)
|
||||
try:
|
||||
self.changed_files_listbox.delete(0, tk.END)
|
||||
self.changed_files_listbox.insert(tk.END, "(Error)")
|
||||
self.changed_files_listbox.config(fg="red")
|
||||
except:
|
||||
pass
|
||||
listbox.delete(0, tk.END)
|
||||
listbox.insert(tk.END, "(Error updating list)")
|
||||
listbox.config(fg="red")
|
||||
except: pass # Ignora errori durante la visualizzazione dell'errore
|
||||
|
||||
def _on_changed_file_double_click(self, event):
|
||||
widget = event.widget
|
||||
@ -1372,13 +1416,53 @@ class MainFrame(ttk.Frame):
|
||||
finally:
|
||||
self.changed_files_context_menu.grab_release()
|
||||
|
||||
def update_status_bar(self, message):
|
||||
if hasattr(self, "status_bar_var"):
|
||||
def update_status_bar(self, message, bg_color=None, duration_ms=None):
|
||||
"""
|
||||
Safely updates the status bar text and optionally its background color.
|
||||
Optionally resets the color after a duration.
|
||||
|
||||
Args:
|
||||
message (str): The text to display.
|
||||
bg_color (str | None): Background color name (e.g., "#FFFACD") or None to use default.
|
||||
duration_ms (int | None): If set, reset color to default after this many ms.
|
||||
"""
|
||||
if hasattr(self, "status_bar_var") and hasattr(self, "status_bar"):
|
||||
try:
|
||||
self.master.after(0, self.status_bar_var.set, message)
|
||||
# Cancella eventuale timer di reset precedente
|
||||
if self._status_reset_timer:
|
||||
self.master.after_cancel(self._status_reset_timer)
|
||||
self._status_reset_timer = None
|
||||
|
||||
# Funzione interna per applicare aggiornamenti (via root.after)
|
||||
def _update():
|
||||
if self.status_bar.winfo_exists(): # Controlla esistenza widget
|
||||
self.status_bar_var.set(message)
|
||||
actual_bg = bg_color if bg_color else self.STATUS_DEFAULT_BG
|
||||
try:
|
||||
self.status_bar.config(background=actual_bg)
|
||||
except tk.TclError: # Gestisci errore se colore non valido
|
||||
self.status_bar.config(background=self.STATUS_DEFAULT_BG)
|
||||
print(f"Warning: Invalid status bar color '{bg_color}', using default.", file=sys.stderr)
|
||||
|
||||
# Pianifica reset colore se richiesto
|
||||
if bg_color and duration_ms and duration_ms > 0:
|
||||
self._status_reset_timer = self.master.after(duration_ms, self.reset_status_bar_color)
|
||||
|
||||
# Pianifica l'esecuzione nel main loop
|
||||
self.master.after(0, _update)
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR updating status bar: {e}", file=sys.stderr)
|
||||
|
||||
def reset_status_bar_color(self):
|
||||
"""Resets the status bar background color to the default."""
|
||||
self._status_reset_timer = None # Resetta timer ID
|
||||
if hasattr(self, "status_bar") and self.status_bar.winfo_exists():
|
||||
try:
|
||||
self.status_bar.config(background=self.STATUS_DEFAULT_BG)
|
||||
except Exception as e:
|
||||
print(f"ERROR resetting status bar color: {e}", file=sys.stderr)
|
||||
|
||||
def ask_new_profile_name(self):
|
||||
return simpledialog.askstring("Add Profile", "Enter name:", parent=self.master)
|
||||
|
||||
@ -1422,6 +1506,8 @@ class MainFrame(ttk.Frame):
|
||||
self.checkout_branch_button,
|
||||
self.refresh_history_button,
|
||||
self.history_branch_filter_combo,
|
||||
self.commit_message_text,
|
||||
self.autocommit_checkbox,
|
||||
]
|
||||
# log_handler.log_debug(f"Setting action widgets state to: {state}", func_name="set_action_widgets_state") # Usa log_handler
|
||||
for widget in widgets:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user