Coverage for graphica/gui/crash_handler.py: 100.0%

40 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-12 16:38 +0000

1"""未処理の例外で、ログの場所とオートセーブからの復元の手順を案内する(exe ではコンソールが無く、何も見えない)。""" 

2import sys 

3import os 

4import logging 

5import traceback 

6 

7from PySide6.QtWidgets import QMessageBox 

8 

9from graphica.core.version import APP_NAME, LOG_FILE_NAME 

10from graphica.core.app_paths import get_app_data_dir 

11from graphica.gui import app_settings 

12 

13logger = logging.getLogger(__name__) 

14 

15_original_excepthook = sys.excepthook 

16 

17 

18def install_crash_handler(): 

19 sys.excepthook = _handle_uncaught_exception 

20 

21 

22def _handle_uncaught_exception(exc_type, exc_value, exc_traceback): 

23 """Ctrl+C(KeyboardInterrupt)は普通の終了なので案内しない。""" 

24 if issubclass(exc_type, KeyboardInterrupt): 

25 _original_excepthook(exc_type, exc_value, exc_traceback) 

26 return 

27 

28 logger.critical("未処理の例外が発生しました。", exc_info=(exc_type, exc_value, exc_traceback)) 

29 

30 log_path = os.path.join(get_app_data_dir(), LOG_FILE_NAME) 

31 detail = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback)) 

32 

33 try: 

34 box = QMessageBox() 

35 box.setIcon(QMessageBox.Icon.Critical) 

36 box.setWindowTitle(f"{APP_NAME} - 予期しないエラー") 

37 box.setText( 

38 "予期しないエラーが発生しました。\n\n" 

39 "これまでの作業内容はオートセーブされている可能性があります。" 

40 f"このまま{APP_NAME}を再起動すると、自動保存からの復元を提案する画面が表示されます。\n\n" 

41 f"詳細はログファイルを確認してください:\n{log_path}" 

42 ) 

43 box.setDetailedText(detail) 

44 box.setStandardButtons(QMessageBox.StandardButton.Ok) 

45 box.exec() 

46 except Exception: 

47 logger.exception("クラッシュ案内ダイアログの表示に失敗しました。") 

48 

49 

50# セーフモードで起動するかの確認。clean_exit を読むだけで、書き換えるのは PlotterApp だけ 

51 

52 

53def should_prompt_safe_mode(settings): 

54 """前回が正常に終わらなかったか。PlotterApp が clean_exit を False にする前に呼ぶこと(後だと自分を異常終了と見誤る)。""" 

55 return not app_settings.CLEAN_EXIT.read(settings) 

56 

57 

58def prompt_safe_mode_and_apply(settings, parent=None): 

59 """前回が異常終了なら、プラグインなしで起動するか尋ねる。セーフモードにしたら True。 

60 

61 既定のボタンは「いいえ」(いつものプラグインが知らないうちに無効になる方が驚く)。 

62 """ 

63 if not should_prompt_safe_mode(settings): 

64 return False 

65 

66 reply = QMessageBox.question( 

67 parent, f"{APP_NAME} - セーフモード起動", 

68 "前回異常終了を検出しました。プラグインを無効にして起動しますか?", 

69 QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, 

70 QMessageBox.StandardButton.No, 

71 ) 

72 if reply != QMessageBox.StandardButton.Yes: 

73 return False 

74 

75 from graphica.core.plugin_api import set_safe_mode 

76 set_safe_mode(True) 

77 return True