From 7ef200c13a689a5ef685f57aa1f6cbbd6c004806 Mon Sep 17 00:00:00 2001 From: dazedanon Date: Sun, 26 Oct 2025 11:44:08 -0500 Subject: [PATCH] Add separate logs per run and remove retry functionality that was making things more expensive --- START.bat | 1 - gui/log_viewer.py | 25 ++++++++++++++++++--- gui/translation_tab.py | 49 ++++++++++++++++++++++++++++++++++++++---- modules/main.py | 33 ++++++++++++++++++++++++++++ util/translation.py | 40 +++++++++++++++++++--------------- 5 files changed, 123 insertions(+), 25 deletions(-) diff --git a/START.bat b/START.bat index 1f3249f..f922b89 100644 --- a/START.bat +++ b/START.bat @@ -179,7 +179,6 @@ if errorlevel 1 ( echo. echo GUI closed successfully. -pause :: End of main flow - prevent falling through into subroutines below goto :eof diff --git a/gui/log_viewer.py b/gui/log_viewer.py index f74d8a2..1388278 100644 --- a/gui/log_viewer.py +++ b/gui/log_viewer.py @@ -73,7 +73,12 @@ class LogViewer(QWidget): self._tail_timer.timeout.connect(self._poll_tail) self._tail_interval = 300 # ms self._tail_f = None - self._tail_path = Path("log/translationHistory.txt") + # Default: prefer the most recent file in log/history, else legacy path + try: + latest = self._latest_history_file() + except Exception: + latest = None + self._tail_path = latest or Path("log/translationHistory.txt") def toggle_auto_refresh(self, enabled): """Toggle auto-refresh functionality.""" @@ -124,8 +129,9 @@ class LogViewer(QWidget): def start_tail(self, file_path: str | Path = None, interval_ms: int = None): """Start tailing the given log file and append new lines as they arrive. - By default tails `log/translationHistory.txt`. Seeks to the end so - only new lines after this call are shown. + By default tails the most recent file in `log/history/` (if present), + otherwise falls back to `log/translationHistory.txt`. + Seeks to the end so only new lines after this call are shown. """ if file_path: self._tail_path = Path(file_path) @@ -213,3 +219,16 @@ class LogViewer(QWidget): """Handle widget hide event - pause refresh to save resources.""" super().hideEvent(event) # No-op for simplified viewer + + def _latest_history_file(self): + """Return the most recent file in log/history or None if not found.""" + try: + hist_dir = Path("log") / "history" + # Ensure history directory exists so callers can rely on it + hist_dir.mkdir(parents=True, exist_ok=True) + files = [p for p in hist_dir.iterdir() if p.is_file()] + if not files: + return None + return max(files, key=lambda p: p.stat().st_mtime) + except Exception: + return None diff --git a/gui/translation_tab.py b/gui/translation_tab.py index d0e97f0..f7105c2 100644 --- a/gui/translation_tab.py +++ b/gui/translation_tab.py @@ -6,6 +6,7 @@ Simple file management and translation execution with console log display. """ import os +import datetime import subprocess import threading import sys @@ -1549,13 +1550,53 @@ class TranslationTab(QWidget): self.translation_worker.progress_signal.connect(self.update_file_progress) self.translation_worker.item_progress_signal.connect(self.update_item_progress) self.translation_worker.finished_signal.connect(self.on_translation_finished) - # Clear and start tailing the translation history so only new - # lines are shown in the right-hand log panel. + # Prepare a per-run log file in log/history and start tailing it so + # the right-hand log panel shows only this run's new lines. try: + history_dir = self.project_root / 'log' / 'history' + history_dir.mkdir(parents=True, exist_ok=True) + # Use timestamp (safe filename) for sorting + fname = datetime.datetime.now().strftime('translationHistory_%Y%m%d_%H%M%S.txt') + run_log_path = history_dir / fname + # Create/touch the file so tailer can open and seek to end + run_log_path.touch(exist_ok=True) + + # Export env var so subprocess workers inherit the path + try: + os.environ['TRANSLATION_RUN_LOG'] = str(run_log_path) + except Exception: + pass + + # Try to create a hard link at legacy location so modules that + # still write to log/translationHistory.txt end up in this file. + legacy = self.project_root / 'log' / 'translationHistory.txt' + try: + # Remove any existing legacy file and create hard link + if legacy.exists(): + try: + legacy.unlink() + except Exception: + pass + os.link(str(run_log_path), str(legacy)) + except Exception: + # If hard link fails (Windows permissions or cross-device), + # fallback to ensuring the legacy file exists but do not fail. + try: + legacy.parent.mkdir(parents=True, exist_ok=True) + legacy.touch(exist_ok=True) + except Exception: + pass + + # Clear UI log and start tailing the per-run file self.translation_log_viewer.clear_log() - self.translation_log_viewer.start_tail(self.project_root / 'log' / 'translationHistory.txt') + self.translation_log_viewer.start_tail(run_log_path) except Exception: - pass + # Fallback to legacy file if anything goes wrong + try: + self.translation_log_viewer.clear_log() + self.translation_log_viewer.start_tail(self.project_root / 'log' / 'translationHistory.txt') + except Exception: + pass # Start the worker self.translation_worker.start() diff --git a/modules/main.py b/modules/main.py index 5fac3cb..6387613 100644 --- a/modules/main.py +++ b/modules/main.py @@ -1,6 +1,8 @@ import sys import os import traceback +import datetime +from pathlib import Path from concurrent.futures import ThreadPoolExecutor, as_completed from colorama import Fore from tqdm import tqdm @@ -132,6 +134,37 @@ files to translate are in the /files folder and that you picked the right game e setSpeakerParseMode(True) # Open File (Threads) - recursively walk 'files' and preserve directory structure + # Prepare per-run log file so CLI runs also write to a run-specific history file + try: + hist_dir = Path("log") / "history" + hist_dir.mkdir(parents=True, exist_ok=True) + fname = datetime.datetime.now().strftime("translationHistory_%Y%m%d_%H%M%S.txt") + run_log_path = hist_dir / fname + run_log_path.touch(exist_ok=True) + # Export env var so other modules/util functions pick it up + try: + os.environ['TRANSLATION_RUN_LOG'] = str(run_log_path) + except Exception: + pass + + # Try to create a hard link from legacy path to this run file for compatibility + legacy = Path("log") / "translationHistory.txt" + try: + if legacy.exists(): + try: + legacy.unlink() + except Exception: + pass + os.link(str(run_log_path), str(legacy)) + except Exception: + # Fallback: ensure legacy file exists so modules writing to it won't fail + try: + legacy.parent.mkdir(parents=True, exist_ok=True) + legacy.touch(exist_ok=True) + except Exception: + pass + except Exception: + pass with ThreadPoolExecutor(max_workers=THREADS) as executor: futures = [] files_root = "files" diff --git a/util/translation.py b/util/translation.py index 671a2bf..1f33017 100644 --- a/util/translation.py +++ b/util/translation.py @@ -611,7 +611,23 @@ def translateAI(text, history, fullPromptFlag, config, filename=None, pbar=None, """ if not text: return [text, [0, 0]] - + + # If a per-run log file is specified via environment variable, prefer it. + run_log = os.getenv("TRANSLATION_RUN_LOG") + if run_log: + # Make sure parent dir exists + try: + Path(run_log).parent.mkdir(parents=True, exist_ok=True) + except Exception: + pass + config.logFilePath = run_log + + # Ensure log directory exists for the configured path + try: + Path(config.logFilePath).parent.mkdir(parents=True, exist_ok=True) + except Exception: + pass + with open(config.logFilePath, "a+", encoding="utf-8") as logFile: totalTokens = [0, 0] @@ -693,27 +709,17 @@ def translateAI(text, history, fullPromptFlag, config, filename=None, pbar=None, if cleaned_text: if isinstance(tItem, list): extracted = extractTranslation(cleaned_text, True, pbar) - - # Check 1: Mismatch in length + + # Check 1: Mismatch in length -> still a hard failure if extracted is None or len(tItem) != len(extracted): is_valid = False - - # Check 2: Untranslated content else: - # Set translations first (line count matches) + # Set translations (line count matches) final_translations = extracted - - # Then check for untranslated content as a warning, not a blocker - for line in extracted: - if re.search(config.langRegex, str(line)): - is_valid = False - break else: - # Check for untranslated content in single string - if re.search(config.langRegex, cleaned_text): - is_valid = False - else: - final_translations = cleaned_text.replace("Placeholder Text", "") + # Single string: accept output even if it contains characters + # matching langRegex (allow names or untranslated tokens). + final_translations = cleaned_text.replace("Placeholder Text", "") else: is_valid = False if pbar: pbar.write(f"AI Refused: {tItem}\n")