646 lines
30 KiB
Python
646 lines
30 KiB
Python
import tkinter as tk
|
|
from tkinter import filedialog, scrolledtext, ttk, messagebox
|
|
import re
|
|
import os
|
|
import shutil # Nova importação para operações de ficheiro (cópia)
|
|
from datetime import datetime # Nova importação para timestamp
|
|
from collections import Counter
|
|
|
|
# Importações dos novos módulos de padrões
|
|
from patterns.common_patterns import PATTERNS_COMMON, INITIAL_ANALYSIS_PATTERNS_KEYS
|
|
from patterns.landrover_jaguar_patterns import PATTERNS_LANDROVER_JAGUAR
|
|
from patterns.vag_patterns import PATTERNS_VAG
|
|
from patterns.bmw_patterns import PATTERNS_BMW # Importar PATTERNS_BMW
|
|
from Data.vag_ecu_group_handler import load_vag_ecu_list
|
|
from Data.jlr_ecu_group_handler import load_jlr_ecu_list
|
|
from Data.bmw_ecu_group_handler import load_bmw_ecu_list # Nova importação para BMW
|
|
# No futuro, adicionar importações para outros módulos de marca aqui:
|
|
# from patterns.bmw_patterns import PATTERNS_BMW
|
|
# etc.
|
|
|
|
def sanitize_name(name):
|
|
"""Remove caracteres inválidos de uma string para usar como nome de ficheiro/pasta."""
|
|
# Remove ou substitui caracteres que são tipicamente problemáticos em nomes de ficheiro/pasta
|
|
name = re.sub(r'[\/*?"<>|:]', '_', name)
|
|
name = name.replace('\n', '_').replace('\r', '_')
|
|
return name[:100] # Limita o comprimento para evitar nomes excessivamente longos
|
|
|
|
# --- Constantes Globais ---
|
|
BRAND_NAMES = ['VAG', 'LandRover/Jaguar', 'BMW', 'Mercedes', 'Renault/Dacia', 'Peugeot/Citroen', 'Opel', 'Fiat/Lancia/Alfa', 'Ford']
|
|
|
|
# Variáveis globais para armazenar referências a widgets e estado
|
|
global_root_window = None
|
|
global_file_path_var = None
|
|
global_results_text_widget = None
|
|
global_brand_buttons_frame = None
|
|
global_category_buttons_frame = None
|
|
global_ascii_text = None # Armazena o conteúdo ASCII do ficheiro lido
|
|
global_progress_bar = None
|
|
global_progress_label = None
|
|
|
|
# Função para ler o ficheiro em modo binário e tentar decodificar para ASCII
|
|
def read_file_content(file_path):
|
|
try:
|
|
with open(file_path, 'rb') as f:
|
|
binary_content = f.read()
|
|
# Tenta decodificar como ASCII, ignorando erros ou substituindo caracteres problemáticos
|
|
ascii_content = binary_content.decode('ascii', errors='replace')
|
|
return ascii_content
|
|
except FileNotFoundError:
|
|
print(f"Erro: Ficheiro não encontrado em {file_path}")
|
|
return None
|
|
except Exception as e:
|
|
print(f"Erro ao ler ou decodificar o ficheiro {file_path}: {e}")
|
|
return None
|
|
|
|
# Função principal de análise de padrões ASCII a partir de uma string de conteúdo
|
|
def analyze_ascii_patterns_from_file(file_content, current_patterns_to_search, progress_callback=None, target_pattern_name=None):
|
|
results = {}
|
|
search_dict = {}
|
|
if target_pattern_name:
|
|
if target_pattern_name in current_patterns_to_search:
|
|
search_dict = {target_pattern_name: current_patterns_to_search[target_pattern_name]}
|
|
else:
|
|
print(f"Aviso: Padrão alvo '{target_pattern_name}' não encontrado no dicionário de pesquisa fornecido.")
|
|
return {target_pattern_name: {}}
|
|
else:
|
|
search_dict = current_patterns_to_search
|
|
|
|
for pattern_name, regex_pattern in search_dict.items():
|
|
try:
|
|
flags = 0
|
|
# A flag re.IGNORECASE para 'Ecu Family' foi removida para restaurar o comportamento anterior.
|
|
# Se a insensibilidade a maiúsculas/minúsculas for necessária para os KEYWORDS,
|
|
# isso deve ser tratado diretamente no regex em common_patterns.py.
|
|
|
|
if pattern_name == 'Possible PIN':
|
|
pin_candidates_matches = re.finditer(r'\b([A-Z0-9]{4,8})\b', file_content)
|
|
uppercase_block_counts = Counter()
|
|
found_items_for_pin = {}
|
|
for match_obj in pin_candidates_matches:
|
|
block_match = match_obj.group(1)
|
|
if block_match.isalnum() and block_match == block_match.upper():
|
|
uppercase_block_counts[block_match] += 1
|
|
|
|
for block, count in uppercase_block_counts.items():
|
|
if 2 <= count <= 3:
|
|
found_items_for_pin[block] = count
|
|
results[pattern_name] = found_items_for_pin
|
|
else:
|
|
matches = re.finditer(regex_pattern, file_content, flags)
|
|
found_items = {}
|
|
for match_obj in matches:
|
|
item = match_obj.group(1) if match_obj.groups() else match_obj.group(0)
|
|
if item:
|
|
cleaned_item = ''.join(char for char in item if char.isprintable()).strip()
|
|
if cleaned_item:
|
|
found_items[cleaned_item] = found_items.get(cleaned_item, 0) + 1
|
|
results[pattern_name] = found_items
|
|
except re.error as e:
|
|
print(f"Erro de regex para o padrão '{pattern_name}' com regex '{regex_pattern}': {e}")
|
|
results[pattern_name] = {"ERRO DE REGEX": 1}
|
|
except Exception as e:
|
|
print(f"Erro inesperado ao processar o padrão '{pattern_name}': {e}")
|
|
results[pattern_name] = {"ERRO INESPERADO": 1}
|
|
return results
|
|
|
|
# Função para lidar com a seleção de ficheiro e realizar a análise inicial
|
|
def handle_file_selection_and_initial_analysis(file_path_var):
|
|
file_path = filedialog.askopenfilename(
|
|
title="Selecionar ficheiro ECU",
|
|
filetypes=(("Binary files", "*.bin"), ("All files", "*.*"))
|
|
)
|
|
if not file_path:
|
|
return
|
|
|
|
file_path_var.set(os.path.basename(file_path))
|
|
global global_ascii_text
|
|
global_ascii_text = read_file_content(file_path)
|
|
|
|
if global_ascii_text is None:
|
|
tk.messagebox.showerror("Erro de Leitura", f"Não foi possível ler ou decodificar o ficheiro: {file_path}", parent=global_root_window)
|
|
clear_ui_state()
|
|
return
|
|
|
|
if global_results_text_widget:
|
|
global_results_text_widget.config(state=tk.NORMAL)
|
|
global_results_text_widget.delete(1.0, tk.END)
|
|
global_results_text_widget.config(state=tk.DISABLED)
|
|
|
|
for widget in global_brand_buttons_frame.winfo_children():
|
|
widget.destroy()
|
|
for widget in global_category_buttons_frame.winfo_children():
|
|
widget.destroy()
|
|
|
|
initial_patterns_dict = {key: PATTERNS_COMMON[key] for key in INITIAL_ANALYSIS_PATTERNS_KEYS if key in PATTERNS_COMMON}
|
|
|
|
if not initial_patterns_dict:
|
|
tk.messagebox.showwarning("Configuração", "Nenhum padrão de análise inicial definido ou encontrado em common_patterns.py.", parent=global_root_window)
|
|
create_brand_buttons()
|
|
return
|
|
|
|
analysis_output = analyze_ascii_patterns_from_file(global_ascii_text, initial_patterns_dict)
|
|
|
|
display_initial_results(analysis_output, global_results_text_widget, global_root_window)
|
|
create_brand_buttons()
|
|
global_progress_label.config(text="Análise inicial concluída. Selecione uma marca.")
|
|
global_progress_bar['value'] = 100
|
|
global_root_window.update_idletasks()
|
|
|
|
# Nova função para exibir os resultados iniciais combinados
|
|
def display_initial_results(initial_results_map, results_text_widget, root_window):
|
|
results_text_widget.config(state=tk.NORMAL)
|
|
results_text_widget.delete(1.0, tk.END)
|
|
results_text_widget.insert(tk.END, "--- Análise Inicial Rápida ---\n")
|
|
found_any_overall = False
|
|
|
|
for pattern_name in INITIAL_ANALYSIS_PATTERNS_KEYS:
|
|
results_text_widget.insert(tk.END, f" --- {pattern_name} ---\n")
|
|
matches_for_pattern = initial_results_map.get(pattern_name)
|
|
|
|
if matches_for_pattern:
|
|
found_any_overall = True
|
|
for item, count in matches_for_pattern.items():
|
|
results_text_widget.insert(tk.END, f" {item}\n") # Removida a contagem de ocorrências
|
|
else:
|
|
results_text_widget.insert(tk.END, " Nenhum resultado encontrado para este padrão.\n")
|
|
|
|
if not found_any_overall:
|
|
results_text_widget.insert(tk.END, " Nenhum dos padrões de análise rápida encontrou correspondências no ficheiro.\n")
|
|
|
|
results_text_widget.insert(tk.END, "\n--- Fim da Análise Inicial Rápida ---\n")
|
|
|
|
# Adicionar nova secção para Grupos (VAG, JLR, etc.)
|
|
results_text_widget.insert(tk.END, "\n---- Centralina Usada nestes Marcas----\n")
|
|
vag_ecu_list = load_vag_ecu_list()
|
|
jlr_ecu_list = load_jlr_ecu_list()
|
|
bmw_ecu_list = load_bmw_ecu_list()
|
|
|
|
found_in_vag_group = False
|
|
found_in_jlr_group = False
|
|
found_in_bmw_group = False
|
|
any_group_found = False
|
|
|
|
ecu_families_found = set() # Para armazenar as famílias de ECU encontradas na análise inicial
|
|
if initial_results_map and 'Ecu Family' in initial_results_map:
|
|
for ecu_family, count in initial_results_map['Ecu Family'].items():
|
|
ecu_families_found.add(ecu_family)
|
|
|
|
if ecu_families_found:
|
|
for ecu_family in ecu_families_found:
|
|
if ecu_family in vag_ecu_list:
|
|
found_in_vag_group = True
|
|
if ecu_family in jlr_ecu_list:
|
|
found_in_jlr_group = True
|
|
if ecu_family in bmw_ecu_list:
|
|
found_in_bmw_group = True
|
|
|
|
if found_in_vag_group:
|
|
results_text_widget.insert(tk.END, " - Grupo VAG\n")
|
|
any_group_found = True
|
|
if found_in_jlr_group:
|
|
results_text_widget.insert(tk.END, " - Grupo Jaguar LandRover\n")
|
|
any_group_found = True
|
|
if found_in_bmw_group:
|
|
results_text_widget.insert(tk.END, " - Grupo BMW\n")
|
|
any_group_found = True
|
|
|
|
if not any_group_found:
|
|
results_text_widget.insert(tk.END, " - Nenhum grupo correspondente encontrado.\n")
|
|
else:
|
|
results_text_widget.insert(tk.END, " Nenhuma família de ECU encontrada na análise para verificar grupos.\n")
|
|
|
|
results_text_widget.config(state=tk.DISABLED)
|
|
if root_window: root_window.update_idletasks()
|
|
|
|
# Função a ser chamada pelo clique de um botão de categoria (sub-análise)
|
|
def run_single_analysis(pattern_name_key, ascii_content, results_widget, root, progress_bar, progress_label_widget, brand_context=None):
|
|
if not ascii_content:
|
|
tk.messagebox.showwarning("Aviso", "Conteúdo do ficheiro não está carregado.", parent=root)
|
|
return
|
|
|
|
progress_label_widget.config(text=f"A analisar para: {pattern_name_key}...")
|
|
progress_bar['value'] = 0
|
|
root.update_idletasks()
|
|
|
|
regex_to_use = None
|
|
if brand_context == 'VAG' and pattern_name_key in PATTERNS_VAG:
|
|
regex_to_use = PATTERNS_VAG[pattern_name_key]
|
|
elif brand_context == 'LandRover/Jaguar' and pattern_name_key in PATTERNS_LANDROVER_JAGUAR:
|
|
regex_to_use = PATTERNS_LANDROVER_JAGUAR[pattern_name_key]
|
|
elif brand_context == 'BMW' and pattern_name_key in PATTERNS_BMW:
|
|
regex_to_use = PATTERNS_BMW[pattern_name_key]
|
|
# Adicionar elif para outras marcas aqui
|
|
|
|
if regex_to_use is None and pattern_name_key in PATTERNS_COMMON:
|
|
regex_to_use = PATTERNS_COMMON[pattern_name_key]
|
|
|
|
if not regex_to_use:
|
|
tk.messagebox.showerror("Erro de Configuração", f"Padrão '{pattern_name_key}' não encontrado para o contexto '{brand_context or 'Comum'}'.", parent=root)
|
|
progress_label_widget.config(text=f"Erro: Padrão '{pattern_name_key}' não configurado.")
|
|
return
|
|
|
|
single_pattern_dict_to_search = {pattern_name_key: regex_to_use}
|
|
analysis_result = analyze_ascii_patterns_from_file(ascii_content, single_pattern_dict_to_search, target_pattern_name=pattern_name_key)
|
|
|
|
progress_bar['value'] = 50
|
|
root.update_idletasks()
|
|
|
|
results_widget.config(state=tk.NORMAL)
|
|
results_widget.delete(1.0, tk.END)
|
|
matches_dict = analysis_result.get(pattern_name_key, {})
|
|
|
|
if matches_dict:
|
|
results_widget.insert(tk.END, f"Resultados para '{pattern_name_key}':\n")
|
|
for item, count in matches_dict.items():
|
|
results_widget.insert(tk.END, f" - {item} (Ocorrências: {count})\n")
|
|
else:
|
|
results_widget.insert(tk.END, f"Nenhum resultado encontrado para '{pattern_name_key}'.\n")
|
|
|
|
results_widget.config(state=tk.DISABLED)
|
|
progress_bar['value'] = 100
|
|
progress_label_widget.config(text=f"Análise para '{pattern_name_key}' concluída.")
|
|
root.update_idletasks()
|
|
|
|
def run_vag_sw_analysis(ascii_content, results_widget, root, progress_bar, progress_label_widget):
|
|
if not ascii_content:
|
|
messagebox.showwarning("Aviso", "Nenhum ficheiro carregado ou conteúdo ASCII não disponível.")
|
|
return
|
|
|
|
# Check if required patterns are defined
|
|
missing_patterns = []
|
|
if 'PATTERNS_COMMON' not in globals() or not isinstance(PATTERNS_COMMON, dict) or \
|
|
'Possible Software Number' not in PATTERNS_COMMON:
|
|
missing_patterns.append("'Possible Software Number' de PATTERNS_COMMON")
|
|
if 'PATTERNS_VAG' not in globals() or not isinstance(PATTERNS_VAG, dict) or \
|
|
'VAG 10SW' not in PATTERNS_VAG:
|
|
missing_patterns.append("'VAG 10SW' de PATTERNS_VAG")
|
|
|
|
if missing_patterns:
|
|
messagebox.showerror("Erro de Configuração", f"Padrões não encontrados ou mal configurados: {', '.join(missing_patterns)}.")
|
|
return
|
|
|
|
progress_label_widget.config(text="A analisar VAG SW...")
|
|
progress_bar['value'] = 0
|
|
if root: root.update_idletasks()
|
|
|
|
patterns_for_vag_sw = {
|
|
'Possible Software Number': PATTERNS_COMMON['Possible Software Number'],
|
|
'VAG 10SW': PATTERNS_VAG['VAG 10SW']
|
|
}
|
|
|
|
all_results_map = {}
|
|
total_patterns_to_analyze = len(patterns_for_vag_sw)
|
|
|
|
for i, (name, pattern_regex) in enumerate(patterns_for_vag_sw.items()):
|
|
progress_val = int(((i + 0.5) / total_patterns_to_analyze) * 100)
|
|
progress_bar['value'] = progress_val
|
|
progress_label_widget.config(text=f"A analisar VAG SW: {name} ({progress_val}%)")
|
|
if root: root.update_idletasks()
|
|
|
|
current_search_dict = {name: pattern_regex}
|
|
# Pass progress_callback=None as analyze_ascii_patterns_from_file might not use it for single pattern
|
|
analysis_results = analyze_ascii_patterns_from_file(ascii_content, current_search_dict, progress_callback=None, target_pattern_name=name)
|
|
|
|
if name in analysis_results and analysis_results[name]:
|
|
all_results_map[name] = analysis_results[name]
|
|
|
|
results_widget.config(state=tk.NORMAL)
|
|
results_widget.delete('1.0', tk.END)
|
|
|
|
results_widget.insert(tk.END, "--- Análise VAG SW ---\n")
|
|
displayed_sw_numbers = set()
|
|
found_any_sw = False
|
|
|
|
# Processar 'Possible Software Number' primeiro
|
|
pattern_order = ['Possible Software Number', 'VAG 10SW']
|
|
|
|
for pattern_title in pattern_order:
|
|
if pattern_title in all_results_map and all_results_map[pattern_title]:
|
|
items_found = all_results_map[pattern_title]
|
|
# Filtrar itens já exibidos, especialmente para 'VAG 10SW'
|
|
unique_items_to_display = []
|
|
if items_found:
|
|
sorted_items = sorted(items_found.items(), key=lambda x: x[1], reverse=True)
|
|
for item, count in sorted_items:
|
|
if item not in displayed_sw_numbers:
|
|
unique_items_to_display.append(item)
|
|
displayed_sw_numbers.add(item)
|
|
|
|
if unique_items_to_display:
|
|
found_any_sw = True
|
|
results_widget.insert(tk.END, f" --- {pattern_title} ---\n")
|
|
for item_text in unique_items_to_display:
|
|
results_widget.insert(tk.END, f" {item_text}\n")
|
|
elif pattern_title == 'Possible Software Number' and not found_any_sw: # Se PSN não achou nada, e é a primeira vez
|
|
results_widget.insert(tk.END, f" --- {pattern_title} ---\n Nenhum resultado encontrado.\n")
|
|
|
|
if not found_any_sw:
|
|
results_widget.insert(tk.END, " Nenhum número de software VAG encontrado.\n")
|
|
|
|
results_widget.insert(tk.END, "\n--- Fim da Análise VAG SW ---\n")
|
|
|
|
results_widget.config(state=tk.DISABLED)
|
|
progress_label_widget.config(text="Análise VAG SW concluída.")
|
|
progress_bar['value'] = 100
|
|
if root: root.update_idletasks()
|
|
|
|
# Função para limpar o estado da UI
|
|
def clear_ui_state():
|
|
if global_file_path_var: global_file_path_var.set("Nenhum ficheiro selecionado")
|
|
if global_results_text_widget:
|
|
global_results_text_widget.config(state=tk.NORMAL)
|
|
global_results_text_widget.delete(1.0, tk.END)
|
|
global_results_text_widget.insert(tk.END, "Por favor, selecione um ficheiro para análise.")
|
|
global_results_text_widget.config(state=tk.DISABLED)
|
|
for frame in [global_brand_buttons_frame, global_category_buttons_frame]:
|
|
if frame:
|
|
for widget in frame.winfo_children():
|
|
widget.destroy()
|
|
if global_progress_label: global_progress_label.config(text="Pronto para iniciar.")
|
|
if global_progress_bar: global_progress_bar['value'] = 0
|
|
# Limpar o texto ASCII global para a próxima análise
|
|
global global_ascii_text
|
|
global_ascii_text = None
|
|
if global_root_window: global_root_window.update_idletasks()
|
|
|
|
# Função para criar os botões das marcas
|
|
def create_brand_buttons():
|
|
for widget in global_brand_buttons_frame.winfo_children():
|
|
widget.destroy()
|
|
for brand_name in BRAND_NAMES:
|
|
button = ttk.Button(global_brand_buttons_frame, text=brand_name,
|
|
command=lambda b_name=brand_name: show_brand_analysis_options(b_name))
|
|
button.pack(side=tk.LEFT, padx=2, pady=2, fill=tk.X, expand=True)
|
|
if global_root_window: global_root_window.update_idletasks()
|
|
|
|
# Função para mostrar opções de análise baseadas na marca
|
|
def show_brand_analysis_options(brand_name):
|
|
if not global_ascii_text:
|
|
tk.messagebox.showwarning("Aviso", "Conteúdo do ficheiro não está carregado.", parent=global_root_window)
|
|
return
|
|
|
|
for widget in global_category_buttons_frame.winfo_children():
|
|
widget.destroy()
|
|
|
|
patterns_to_show_for_brand_keys = []
|
|
for p_name in PATTERNS_COMMON.keys():
|
|
# Exclui 'Possible PIN' especificamente para a marca VAG ou BMW
|
|
if (brand_name == 'VAG' and p_name == 'Possible PIN') or \
|
|
(brand_name == 'BMW' and p_name == 'Possible PIN'):
|
|
continue
|
|
if p_name not in INITIAL_ANALYSIS_PATTERNS_KEYS:
|
|
patterns_to_show_for_brand_keys.append(p_name)
|
|
|
|
brand_specific_patterns_for_this_brand_keys = []
|
|
if brand_name == 'VAG':
|
|
brand_specific_patterns_for_this_brand_keys = list(PATTERNS_VAG.keys())
|
|
elif brand_name == 'LandRover/Jaguar':
|
|
brand_specific_patterns_for_this_brand_keys = list(PATTERNS_LANDROVER_JAGUAR.keys())
|
|
elif brand_name == 'BMW':
|
|
brand_specific_patterns_for_this_brand_keys = list(PATTERNS_BMW.keys())
|
|
# Adicionar elif para outras marcas aqui
|
|
|
|
combined_keys = patterns_to_show_for_brand_keys + brand_specific_patterns_for_this_brand_keys
|
|
patterns_to_show_for_brand_keys = sorted(list(set(combined_keys)))
|
|
|
|
global_progress_label.config(text=f"Marca: {brand_name}. Selecione um tipo de análise.")
|
|
global_progress_bar['value'] = 0
|
|
|
|
if not patterns_to_show_for_brand_keys:
|
|
label_no_patterns = ttk.Label(global_category_buttons_frame, text=f"Nenhum padrão de análise definido para a marca {brand_name}.")
|
|
label_no_patterns.pack(pady=10)
|
|
global_root_window.update_idletasks()
|
|
return
|
|
|
|
for pattern_name_key in patterns_to_show_for_brand_keys:
|
|
button_text = pattern_name_key
|
|
current_command = None
|
|
|
|
if brand_name == 'VAG':
|
|
if pattern_name_key == 'Bosch SW': # Esta é a chave original que queremos substituir por 'VAG SW'
|
|
button_text = 'VAG SW'
|
|
current_command = lambda: run_vag_sw_analysis( # run_vag_sw_analysis agora só faz 10SW
|
|
global_ascii_text,
|
|
global_results_text_widget,
|
|
global_root_window,
|
|
global_progress_bar,
|
|
global_progress_label
|
|
)
|
|
elif pattern_name_key == 'VAG 10SW': # Esta chave vem de PATTERNS_VAG.keys(), não queremos um botão para ela.
|
|
continue # Pula a criação de um botão dedicado 'VAG 10SW'
|
|
elif pattern_name_key == 'VAG Specific PartNumbers':
|
|
# Para este, queremos um botão que chame run_single_analysis como normal
|
|
button_text = 'VAG Specific PartNumbers'
|
|
current_command = lambda p_name=pattern_name_key: run_single_analysis(
|
|
p_name,
|
|
global_ascii_text,
|
|
global_results_text_widget,
|
|
global_root_window,
|
|
global_progress_bar,
|
|
global_progress_label,
|
|
brand_context=brand_name
|
|
)
|
|
else:
|
|
# Para outros padrões comuns ou específicos de VAG que não são os acima
|
|
current_command = lambda p_name=pattern_name_key: run_single_analysis(
|
|
p_name,
|
|
global_ascii_text,
|
|
global_results_text_widget,
|
|
global_root_window,
|
|
global_progress_bar,
|
|
global_progress_label,
|
|
brand_context=brand_name
|
|
)
|
|
else: # Para marcas que não são VAG
|
|
current_command = lambda p_name=pattern_name_key: run_single_analysis(
|
|
p_name,
|
|
global_ascii_text,
|
|
global_results_text_widget,
|
|
global_root_window,
|
|
global_progress_bar,
|
|
global_progress_label,
|
|
brand_context=brand_name
|
|
)
|
|
|
|
if current_command: # Só cria o botão se um comando foi definido
|
|
button = ttk.Button(global_category_buttons_frame, text=button_text, command=current_command)
|
|
button.pack(side=tk.LEFT, padx=2, pady=2, fill=tk.X, expand=True)
|
|
if global_root_window: global_root_window.update_idletasks()
|
|
|
|
# Função principal para criar a janela e os widgets
|
|
def create_main_window():
|
|
global global_root_window, global_file_path_var, global_results_text_widget
|
|
global global_brand_buttons_frame, global_category_buttons_frame, global_progress_bar, global_progress_label
|
|
|
|
root = tk.Tk()
|
|
root.title("Analisador ASCII de Ficheiros ECU - Refatorado")
|
|
global_root_window = root
|
|
|
|
style = ttk.Style(root)
|
|
try:
|
|
style.theme_use('clam') # 'clam' é uma boa opção multiplataforma
|
|
except tk.TclError:
|
|
# Fallback para um tema padrão se 'clam' não estiver disponível
|
|
# Isto pode acontecer em algumas instalações mínimas de Tk
|
|
print("Tema 'clam' não encontrado, usando tema padrão do Tk.")
|
|
pass # Usa o tema padrão do sistema
|
|
|
|
file_path_var = tk.StringVar()
|
|
global_file_path_var = file_path_var
|
|
file_label = ttk.Label(root, textvariable=file_path_var)
|
|
file_label.pack(pady=5, padx=10, fill=tk.X)
|
|
|
|
select_button = ttk.Button(root, text="Selecionar Ficheiro e Iniciar Análise",
|
|
command=lambda: handle_file_selection_and_initial_analysis(file_path_var))
|
|
select_button.pack(pady=5, padx=10, fill=tk.X) # pady ajustado para consistência
|
|
|
|
progress_frame = ttk.Frame(root)
|
|
progress_frame.pack(pady=5, padx=10, fill=tk.X)
|
|
progress_label = ttk.Label(progress_frame, text="Pronto para iniciar.")
|
|
progress_label.pack(side=tk.LEFT, padx=5)
|
|
progress_bar = ttk.Progressbar(progress_frame, orient='horizontal', mode='determinate', length=200)
|
|
progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
|
global_progress_bar = progress_bar
|
|
global_progress_label = progress_label
|
|
|
|
brand_buttons_frame = ttk.Frame(root)
|
|
brand_buttons_frame.pack(pady=5, padx=5, fill=tk.X)
|
|
global_brand_buttons_frame = brand_buttons_frame
|
|
|
|
# Botão Global File Organizer
|
|
file_organizer_button = ttk.Button(root, text="File Organizer", command=initiate_bmw_file_organizer)
|
|
file_organizer_button.pack(pady=5, padx=10, fill=tk.X)
|
|
|
|
category_buttons_frame = ttk.Frame(root)
|
|
category_buttons_frame.pack(pady=5, padx=10, fill=tk.X)
|
|
global_category_buttons_frame = category_buttons_frame
|
|
|
|
results_text_widget = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=100, height=20, state=tk.DISABLED)
|
|
results_text_widget.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
|
|
global_results_text_widget = results_text_widget
|
|
|
|
clear_ui_state() # Limpa a UI antes de iniciar
|
|
root.mainloop()
|
|
|
|
def process_folder_for_bmw_organization(source_folder, base_destination_folder, progress_callback=None):
|
|
"""Processa uma pasta, analisa ficheiros BMW e organiza-os em subpastas nomeadas."""
|
|
processed_files = 0
|
|
failed_files = 0
|
|
total_files = sum([len(files) for r, d, files in os.walk(source_folder)])
|
|
current_file_count = 0
|
|
|
|
if progress_callback:
|
|
progress_callback(0, total_files, "Iniciando organização...")
|
|
|
|
for root, _, files in os.walk(source_folder):
|
|
for filename in files:
|
|
current_file_count += 1
|
|
if progress_callback:
|
|
progress_callback(current_file_count, total_files, f"Processando: {filename}")
|
|
|
|
file_path = os.path.join(root, filename)
|
|
try:
|
|
ascii_content = read_file_content(file_path)
|
|
if not ascii_content:
|
|
print(f"Não foi possível ler o conteúdo ASCII de: {filename}")
|
|
failed_files += 1
|
|
continue
|
|
|
|
# Realiza uma análise inicial para 'Ecu Family' e 'Possible Software Number'
|
|
initial_patterns = {key: PATTERNS_COMMON[key] for key in INITIAL_ANALYSIS_PATTERNS_KEYS if key in PATTERNS_COMMON}
|
|
analysis_results = analyze_ascii_patterns_from_file(ascii_content, initial_patterns)
|
|
|
|
ecu_family_data = analysis_results.get('Ecu Family', {})
|
|
sw_number_data = analysis_results.get('Possible Software Number', {})
|
|
|
|
# Pega o primeiro (ou mais comum) Ecu Family e Software Number
|
|
# Pode ser necessário refinar esta lógica para escolher o "melhor" se houver múltiplos
|
|
main_ecu_family = next(iter(ecu_family_data.keys()), "UnknownECU")
|
|
main_sw_number = next(iter(sw_number_data.keys()), "UnknownSW")
|
|
|
|
# Sanitiza os nomes para criar nomes de pasta válidos
|
|
s_ecu_family = sanitize_name(main_ecu_family)
|
|
s_sw_number = sanitize_name(main_sw_number)
|
|
s_filename = sanitize_name(os.path.splitext(filename)[0]) # Nome do ficheiro sem extensão
|
|
|
|
# Cria o nome da pasta da família da ECU
|
|
ecu_family_folder_name = s_ecu_family
|
|
ecu_family_path = os.path.join(base_destination_folder, ecu_family_folder_name)
|
|
os.makedirs(ecu_family_path, exist_ok=True)
|
|
|
|
# Cria o nome da subpasta de destino final dentro da pasta da família da ECU
|
|
# Ex: BMW_EDC17C41_10SW012345_NomeOriginalDoFicheiro
|
|
subfolder_name = f"BMW_{s_ecu_family}_{s_sw_number}_{s_filename}"
|
|
destination_subfolder = os.path.join(ecu_family_path, subfolder_name)
|
|
|
|
os.makedirs(destination_subfolder, exist_ok=True)
|
|
|
|
# Copia o ficheiro original
|
|
shutil.copy2(file_path, os.path.join(destination_subfolder, filename))
|
|
|
|
# Cria o ficheiro de texto com os dados recolhidos
|
|
info_file_path = os.path.join(destination_subfolder, f"info_{s_filename}.txt")
|
|
with open(info_file_path, 'w', encoding='utf-8') as info_f:
|
|
info_f.write(f"Ficheiro Original: {filename}\n")
|
|
info_f.write(f"Caminho Original: {file_path}\n")
|
|
info_f.write(f"Data da Análise: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
|
info_f.write(f"Ecu Family Encontrada: {main_ecu_family}\n")
|
|
if ecu_family_data:
|
|
for ecu, count in ecu_family_data.items():
|
|
info_f.write(f" - {ecu} (Contagem: {count})\n")
|
|
info_f.write(f"\nSoftware Number Encontrado: {main_sw_number}\n")
|
|
if sw_number_data:
|
|
for sw, count in sw_number_data.items():
|
|
info_f.write(f" - {sw} (Contagem: {count})\n")
|
|
# Adicionar mais dados se necessário
|
|
|
|
processed_files += 1
|
|
except PermissionError as e_perm:
|
|
print(f"Erro de permissão ao processar o ficheiro {filename}: {e_perm}. O ficheiro pode estar em uso.")
|
|
failed_files += 1
|
|
except Exception as e:
|
|
print(f"Erro geral ao processar o ficheiro {filename}: {e}")
|
|
failed_files += 1
|
|
# Considerar logar o erro para um ficheiro de log
|
|
|
|
if progress_callback:
|
|
progress_callback(total_files, total_files, "Organização concluída!")
|
|
|
|
return processed_files, failed_files
|
|
|
|
def initiate_bmw_file_organizer():
|
|
source_dir = filedialog.askdirectory(title="Selecione a Pasta de Origem com Ficheiros BMW")
|
|
if not source_dir:
|
|
messagebox.showinfo("Cancelado", "Operação de organização cancelada.")
|
|
return
|
|
|
|
dest_dir = filedialog.askdirectory(title="Selecione a Pasta de Destino Base para Organização")
|
|
if not dest_dir:
|
|
messagebox.showinfo("Cancelado", "Operação de organização cancelada.")
|
|
return
|
|
|
|
if global_progress_bar and global_progress_label:
|
|
def progress_update(current, total, message):
|
|
global_progress_label.config(text=message)
|
|
if total > 0:
|
|
percentage = (current / total) * 100
|
|
global_progress_bar['value'] = percentage
|
|
else:
|
|
global_progress_bar['value'] = 0
|
|
if global_root_window: global_root_window.update_idletasks()
|
|
|
|
try:
|
|
processed, failed = process_folder_for_bmw_organization(source_dir, dest_dir, progress_update)
|
|
summary_message = f"Organização Concluída.\nFicheiros Processados com Sucesso: {processed}\nFicheiros com Falha: {failed}"
|
|
messagebox.showinfo("Concluído", summary_message)
|
|
except Exception as e:
|
|
messagebox.showerror("Erro na Organização", f"Ocorreu um erro: {e}")
|
|
if global_progress_label: global_progress_label.config(text="Erro na organização.")
|
|
if global_progress_bar: global_progress_bar['value'] = 0
|
|
else:
|
|
messagebox.showerror("Erro", "Componentes de progresso não inicializados.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
create_main_window() |