Files
ECU-Analyse/ecu_ascii_analyzer.py

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()