fix json export

This commit is contained in:
VALLONGOL 2025-06-24 08:06:17 +02:00
parent 01b30ccfb2
commit b8eb3ce9ad

View File

@ -6,12 +6,13 @@ Orchestrates the interaction between the GUI and the core processing logic.
""" """
import multiprocessing as mp import multiprocessing as mp
import csv import csv
import json
import os import os
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from typing import List, Any, Dict, Tuple, Optional from typing import List, Any, Dict, Tuple, Optional
from tkinter import filedialog from tkinter import filedialog, messagebox
import tkinter as tk import tkinter as tk
from ..utils.config_manager import ConfigManager from ..utils.config_manager import ConfigManager
@ -46,10 +47,6 @@ def _get_value_from_path(batch: DataBatch, field: ExportField) -> Any:
log.warning(f"Could not find attribute for path: {path}") log.warning(f"Could not find attribute for path: {path}")
return "N/A" return "N/A"
def _write_json_row(file_handle, row_dict: Dict[str, Any]):
# Helper to write a JSON object to a file, followed by a newline.
json.dump(row_dict, file_handle)
file_handle.write("\n")
class AppController: class AppController:
"""The main controller of the application.""" """The main controller of the application."""
@ -66,6 +63,9 @@ class AppController:
self.output_file_handles: Dict[str, Any] = {} self.output_file_handles: Dict[str, Any] = {}
self.csv_writers: Dict[str, Any] = {} self.csv_writers: Dict[str, Any] = {}
# Buffer for JSON data
self.json_data_buffer: List[Dict[str, Any]] = []
self.last_generated_out_file: Optional[Path] = None self.last_generated_out_file: Optional[Path] = None
def bind_view(self, view): def bind_view(self, view):
@ -132,10 +132,13 @@ class AppController:
self.output_file_handles.clear() self.output_file_handles.clear()
self.csv_writers.clear() self.csv_writers.clear()
self.active_export_profiles.clear() self.active_export_profiles.clear()
self.json_data_buffer.clear() # Clear JSON buffer
try: try:
output_dir = Path(self.view.out_output_dir_var.get()) output_dir = Path(self.view.out_output_dir_var.get())
basename = self.view.out_basename_var.get() basename = self.view.out_basename_var.get()
profiles = self.config_manager.get_export_profiles() profiles = self.config_manager.get_export_profiles()
if self.view.out_output_csv_var.get(): if self.view.out_output_csv_var.get():
profile = next((p for p in profiles if p.name == self.view.out_csv_profile_var.get()), None) profile = next((p for p in profiles if p.name == self.view.out_csv_profile_var.get()), None)
if not profile: raise ValueError(f"CSV profile '{self.view.out_csv_profile_var.get()}' not found.") if not profile: raise ValueError(f"CSV profile '{self.view.out_csv_profile_var.get()}' not found.")
@ -145,12 +148,13 @@ class AppController:
self.output_file_handles["csv"] = fh self.output_file_handles["csv"] = fh
self.csv_writers["csv"] = csv.writer(fh) self.csv_writers["csv"] = csv.writer(fh)
self.csv_writers["csv"].writerow([f.column_name for f in profile.fields]) self.csv_writers["csv"].writerow([f.column_name for f in profile.fields])
if self.view.out_output_json_var.get(): if self.view.out_output_json_var.get():
profile = next((p for p in profiles if p.name == self.view.out_json_profile_var.get()), None) profile = next((p for p in profiles if p.name == self.view.out_json_profile_var.get()), None)
if not profile: raise ValueError(f"JSON profile '{self.view.out_json_profile_var.get()}' not found.") if not profile: raise ValueError(f"JSON profile '{self.view.out_json_profile_var.get()}' not found.")
self.active_export_profiles["json"] = profile self.active_export_profiles["json"] = profile
path = (output_dir / basename).with_suffix(".json") # JSON file is no longer opened here, it's written at the end.
self.output_file_handles["json"] = open(path, "w", encoding="utf-8")
return True return True
except (IOError, ValueError) as e: except (IOError, ValueError) as e:
log.error(f"Failed to prepare output files: {e}") log.error(f"Failed to prepare output files: {e}")
@ -175,7 +179,6 @@ class AppController:
self.config_manager.set("active_out_export_profile_name", self.view.out_csv_profile_var.get()) self.config_manager.set("active_out_export_profile_name", self.view.out_csv_profile_var.get())
self.config_manager.save_config() self.config_manager.save_config()
# --- CORRECTED LINE ---
active_profile = self.active_export_profiles.get("csv") or self.active_export_profiles.get("json") active_profile = self.active_export_profiles.get("csv") or self.active_export_profiles.get("json")
worker_args = (Path(filepath_str), self.command_queue, self.result_queue, active_profile) worker_args = (Path(filepath_str), self.command_queue, self.result_queue, active_profile)
self._launch_worker(run_worker_process, worker_args) self._launch_worker(run_worker_process, worker_args)
@ -213,7 +216,7 @@ class AppController:
output_dir = self.view.rec_output_dir_var.get() output_dir = self.view.rec_output_dir_var.get()
except ValueError as e: except ValueError as e:
log.error(f"Configuration error: {e}") log.error(f"Configuration error: {e}")
tk.messagebox.showerror("Configuration Error", str(e), parent=self.view) messagebox.showerror("Configuration Error", str(e), parent=self.view)
return return
self.is_processing = True self.is_processing = True
@ -241,20 +244,42 @@ class AppController:
self.csv_writers.clear() self.csv_writers.clear()
def handle_data_batch(self, batch: DataBatch): def handle_data_batch(self, batch: DataBatch):
"""Writes a data batch to the currently open output files (CSV/JSON).""" """Writes a data batch to CSV and buffers it for JSON."""
if self.csv_writers.get("csv"): if self.csv_writers.get("csv"):
profile = self.active_export_profiles["csv"] profile = self.active_export_profiles["csv"]
row_values = [_get_value_from_path(batch, field) for field in profile.fields] row_values = [_get_value_from_path(batch, field) for field in profile.fields]
self.csv_writers["csv"].writerow(row_values) self.csv_writers["csv"].writerow(row_values)
if self.output_file_handles.get("json"): if "json" in self.active_export_profiles:
profile = self.active_export_profiles["json"] profile = self.active_export_profiles["json"]
row_dict = {field.column_name: _get_value_from_path(batch, field) for field in profile.fields} row_dict = {field.column_name: _get_value_from_path(batch, field) for field in profile.fields}
_write_json_row(self.output_file_handles["json"], row_dict) self.json_data_buffer.append(row_dict)
if batch.batch_id % 20 == 0: if batch.batch_id % 20 == 0:
for fh in self.output_file_handles.values(): if "csv" in self.output_file_handles:
fh.flush() self.output_file_handles["csv"].flush()
def _write_json_buffer_to_file(self):
"""Writes the buffered JSON data to a file."""
if not self.json_data_buffer:
if "json" in self.active_export_profiles:
log.info("JSON export was enabled, but no data batches were generated. Skipping file creation.")
return
try:
output_dir = Path(self.view.out_output_dir_var.get())
basename = self.view.out_basename_var.get()
path = (output_dir / basename).with_suffix(".json")
log.info(f"Writing {len(self.json_data_buffer)} records to JSON file: {path}")
with open(path, "w", encoding="utf-8") as f:
json.dump(self.json_data_buffer, f, indent=4)
log.info("JSON file written successfully.")
except (IOError, ValueError) as e:
log.error(f"Failed to write JSON output file: {e}")
finally:
self.json_data_buffer.clear()
def open_folder_from_path(self, folder_path_str: str): def open_folder_from_path(self, folder_path_str: str):
if not folder_path_str: return if not folder_path_str: return
@ -270,6 +295,11 @@ class AppController:
def handle_worker_completion(self, msg: Dict[str, Any]): def handle_worker_completion(self, msg: Dict[str, Any]):
status = "Interrupted" if msg.get("interrupted") else "Complete" status = "Interrupted" if msg.get("interrupted") else "Complete"
log.info(f"--- Process {status}. ---") log.info(f"--- Process {status}. ---")
# Write buffered JSON data before closing files
if self.view.out_output_json_var.get():
self._write_json_buffer_to_file()
self._close_all_files() self._close_all_files()
self.is_processing = False self.is_processing = False
self.worker_process = None self.worker_process = None