910 lines
30 KiB
Python
910 lines
30 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
설정 관리 모듈
|
|
애플리케이션의 설정을 관리합니다.
|
|
|
|
이 모듈은 다음 기능을 제공합니다:
|
|
- 설정 파일 읽기/쓰기
|
|
- 기본 설정 값 관리
|
|
- 런타임 설정 변경
|
|
- 설정 유효성 검사
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import configparser
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional, Union, List
|
|
from dataclasses import dataclass, field, asdict
|
|
|
|
from .constants import (
|
|
CONFIG_FILE,
|
|
DATA_DIR,
|
|
LAYOUT_RATIOS,
|
|
FONT_FAMILY,
|
|
FONT_SIZES,
|
|
UI_FONT_SETTINGS,
|
|
)
|
|
from .logger import get_logger
|
|
|
|
# 로거 설정
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
# ============================================================================
|
|
# 설정 데이터 클래스
|
|
# ============================================================================
|
|
|
|
@dataclass
|
|
class AppSettings:
|
|
"""애플리케이션 일반 설정"""
|
|
language: str = "ko"
|
|
theme: str = "dark"
|
|
font_family: str = FONT_FAMILY
|
|
font_size: int = FONT_SIZES["body"]
|
|
auto_save: bool = True
|
|
auto_save_interval: int = 30 # 초
|
|
check_updates: bool = True
|
|
update_check_interval: int = 3600 # 초
|
|
start_minimized: bool = False
|
|
minimize_to_tray: bool = True
|
|
show_notifications: bool = True
|
|
|
|
|
|
@dataclass
|
|
class LayoutSettings:
|
|
"""레이아웃 설정"""
|
|
info_bar_ratio: float = LAYOUT_RATIOS["info_bar"]
|
|
content_area_ratio: float = LAYOUT_RATIOS["content_area"]
|
|
status_bar_ratio: float = LAYOUT_RATIOS["status_bar"]
|
|
section_panel_ratio: float = LAYOUT_RATIOS["section_panel"]
|
|
todo_panel_ratio: float = LAYOUT_RATIOS["todo_panel"]
|
|
daily_inspection_ratio: float = LAYOUT_RATIOS["daily_inspection"]
|
|
todo_list_ratio: float = LAYOUT_RATIOS["todo_list"]
|
|
memo_ratio: float = LAYOUT_RATIOS["memo"]
|
|
window_width: int = 1600
|
|
window_height: int = 900
|
|
window_x: int = -1 # -1은 화면 중앙
|
|
window_y: int = -1
|
|
|
|
|
|
@dataclass
|
|
class DatabaseSettings:
|
|
"""데이터베이스 설정"""
|
|
db_path: str = str(DATA_DIR / "handover.db")
|
|
backup_enabled: bool = True
|
|
backup_interval: int = 86400 # 24시간
|
|
backup_count: int = 7
|
|
sync_enabled: bool = False
|
|
sync_url: str = ""
|
|
sync_key: str = ""
|
|
sync_interval: int = 300 # 5분
|
|
|
|
|
|
@dataclass
|
|
class WeatherSettings:
|
|
"""날씨 설정"""
|
|
enabled: bool = True
|
|
api_key: str = ""
|
|
location_lat: float = 35.1796 # 부산 기본값
|
|
location_lon: float = 129.0756
|
|
location_name: str = "부산"
|
|
update_interval: int = 1800 # 30분
|
|
forecast_unit: str = "1시간 단위" # 1시간 단위 또는 3시간 단위
|
|
|
|
|
|
@dataclass
|
|
class FieldSetting:
|
|
"""필드 설정 데이터 클래스"""
|
|
name: str = ""
|
|
visible: bool = True
|
|
width: int = 100
|
|
display_format: Optional[str] = None # "full", "short", "month_day" 등
|
|
|
|
|
|
@dataclass
|
|
class UserSettings:
|
|
"""사용자 설정 (런타임)"""
|
|
current_team: str = "1팀"
|
|
current_shift: str = "주간"
|
|
last_user_id: int = 0
|
|
remember_login: bool = True
|
|
# 필드 설정: 팀별로 섹션별 필드 설정 저장
|
|
# 형식: {"팀명": {"섹션명": [FieldSetting, ...], ...}, ...}
|
|
field_settings: Dict[str, Dict[str, List[Dict[str, Any]]]] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class TrainSettings:
|
|
"""편성 설정 (A: 구형, B: 신형)"""
|
|
# 기본값: 홀수=A(구형), 짝수=B(신형)
|
|
# 신평 차량: 6,7,8,9,13,16,32~48
|
|
train_1_type: str = "A"
|
|
train_2_type: str = "B"
|
|
train_3_type: str = "A"
|
|
train_4_type: str = "B"
|
|
train_5_type: str = "A"
|
|
train_6_type: str = "B"
|
|
train_7_type: str = "B"
|
|
train_8_type: str = "B"
|
|
train_9_type: str = "B"
|
|
train_10_type: str = "B"
|
|
train_11_type: str = "A"
|
|
train_12_type: str = "B"
|
|
train_13_type: str = "B"
|
|
train_14_type: str = "B"
|
|
train_15_type: str = "A"
|
|
train_16_type: str = "B"
|
|
train_17_type: str = "A"
|
|
train_18_type: str = "B"
|
|
train_19_type: str = "A"
|
|
train_20_type: str = "B"
|
|
train_21_type: str = "A"
|
|
train_22_type: str = "B"
|
|
train_23_type: str = "A"
|
|
train_24_type: str = "B"
|
|
train_25_type: str = "A"
|
|
train_26_type: str = "B"
|
|
train_27_type: str = "A"
|
|
train_28_type: str = "B"
|
|
train_29_type: str = "A"
|
|
train_30_type: str = "B"
|
|
train_31_type: str = "A"
|
|
train_32_type: str = "B"
|
|
train_33_type: str = "B"
|
|
train_34_type: str = "B"
|
|
train_35_type: str = "B"
|
|
train_36_type: str = "B"
|
|
train_37_type: str = "B"
|
|
train_38_type: str = "B"
|
|
train_39_type: str = "B"
|
|
train_40_type: str = "B"
|
|
train_41_type: str = "B"
|
|
train_42_type: str = "B"
|
|
train_43_type: str = "B"
|
|
train_44_type: str = "B"
|
|
train_45_type: str = "B"
|
|
train_46_type: str = "B"
|
|
train_47_type: str = "B"
|
|
train_48_type: str = "B"
|
|
train_49_type: str = "A"
|
|
train_50_type: str = "B"
|
|
train_51_type: str = "A"
|
|
|
|
|
|
@dataclass
|
|
class UIFontSettings:
|
|
"""UI 폰트 설정"""
|
|
# 인포바
|
|
info_bar_title_family: str = FONT_FAMILY
|
|
info_bar_title_size: int = 16
|
|
info_bar_title_weight: str = "bold"
|
|
info_bar_content_family: str = FONT_FAMILY
|
|
info_bar_content_size: int = 14
|
|
info_bar_content_weight: str = "normal"
|
|
|
|
# 섹션
|
|
section_title_family: str = FONT_FAMILY
|
|
section_title_size: int = 16
|
|
section_title_weight: str = "bold"
|
|
section_header_family: str = FONT_FAMILY
|
|
section_header_size: int = 13
|
|
section_header_weight: str = "bold"
|
|
section_content_family: str = FONT_FAMILY
|
|
section_content_size: int = 13
|
|
section_content_weight: str = "normal"
|
|
|
|
# 할일
|
|
todo_title_family: str = FONT_FAMILY
|
|
todo_title_size: int = 14
|
|
todo_title_weight: str = "bold"
|
|
todo_content_family: str = FONT_FAMILY
|
|
todo_content_size: int = 13
|
|
todo_content_weight: str = "normal"
|
|
|
|
# 메모
|
|
memo_title_family: str = FONT_FAMILY
|
|
memo_title_size: int = 14
|
|
memo_title_weight: str = "bold"
|
|
memo_content_family: str = FONT_FAMILY
|
|
memo_content_size: int = 13
|
|
memo_content_weight: str = "normal"
|
|
|
|
# 일상검수
|
|
daily_title_family: str = FONT_FAMILY
|
|
daily_title_size: int = 14
|
|
daily_title_weight: str = "bold"
|
|
daily_content_family: str = FONT_FAMILY
|
|
daily_content_size: int = 13
|
|
daily_content_weight: str = "normal"
|
|
daily_train_family: str = FONT_FAMILY
|
|
daily_train_size: int = 15
|
|
daily_train_weight: str = "bold"
|
|
|
|
# 상태바
|
|
status_content_family: str = FONT_FAMILY
|
|
status_content_size: int = 12
|
|
status_content_weight: str = "normal"
|
|
|
|
# 다이얼로그
|
|
dialog_title_family: str = FONT_FAMILY
|
|
dialog_title_size: int = 14
|
|
dialog_title_weight: str = "bold"
|
|
dialog_label_family: str = FONT_FAMILY
|
|
dialog_label_size: int = 12
|
|
dialog_label_weight: str = "normal"
|
|
dialog_input_family: str = FONT_FAMILY
|
|
dialog_input_size: int = 12
|
|
dialog_input_weight: str = "normal"
|
|
dialog_button_family: str = FONT_FAMILY
|
|
dialog_button_size: int = 12
|
|
dialog_button_weight: str = "medium"
|
|
|
|
|
|
@dataclass
|
|
class FieldSetting:
|
|
"""필드 설정 데이터 클래스"""
|
|
name: str
|
|
visible: bool = True
|
|
width: int = 100
|
|
display_format: Optional[str] = None # "full", "short", "month_day" 등
|
|
|
|
|
|
@dataclass
|
|
class AllSettings:
|
|
"""모든 설정을 통합하는 클래스"""
|
|
app: AppSettings = field(default_factory=AppSettings)
|
|
layout: LayoutSettings = field(default_factory=LayoutSettings)
|
|
database: DatabaseSettings = field(default_factory=DatabaseSettings)
|
|
weather: WeatherSettings = field(default_factory=WeatherSettings)
|
|
user: UserSettings = field(default_factory=UserSettings)
|
|
ui_font: UIFontSettings = field(default_factory=UIFontSettings)
|
|
train: TrainSettings = field(default_factory=TrainSettings)
|
|
|
|
|
|
# ============================================================================
|
|
# 설정 관리자 클래스
|
|
# ============================================================================
|
|
|
|
class ConfigManager:
|
|
"""
|
|
설정 관리자 클래스
|
|
|
|
싱글톤 패턴을 사용하여 애플리케이션 전역에서 하나의 인스턴스만 사용합니다.
|
|
설정 파일의 읽기/쓰기 및 런타임 설정 변경을 담당합니다.
|
|
|
|
Attributes:
|
|
config_path: 설정 파일 경로
|
|
settings: 현재 설정 객체
|
|
|
|
Examples:
|
|
>>> config = ConfigManager()
|
|
>>> config.get('app', 'theme')
|
|
'dark'
|
|
>>> config.set('app', 'theme', 'light')
|
|
>>> config.save()
|
|
"""
|
|
|
|
_instance: Optional['ConfigManager'] = None
|
|
|
|
def __new__(cls, config_path: Path = None):
|
|
"""싱글톤 패턴 구현"""
|
|
if cls._instance is None:
|
|
cls._instance = super().__new__(cls)
|
|
cls._instance._initialized = False
|
|
return cls._instance
|
|
|
|
def __init__(self, config_path: Path = None):
|
|
"""
|
|
설정 관리자 초기화
|
|
|
|
Args:
|
|
config_path: 설정 파일 경로 (기본값: CONFIG_FILE)
|
|
"""
|
|
# 이미 초기화된 경우 건너뛰기
|
|
if self._initialized:
|
|
return
|
|
|
|
self.config_path = config_path or CONFIG_FILE
|
|
self.settings = AllSettings()
|
|
self._parser = configparser.ConfigParser()
|
|
|
|
# 필드 설정 파일 경로 (레거시, 마이그레이션용)
|
|
self.field_settings_file = DATA_DIR / "field_settings.json"
|
|
|
|
# SettingsManager 인스턴스 (지연 로딩)
|
|
self._settings_manager = None
|
|
|
|
# 설정 파일 로드
|
|
self.load()
|
|
|
|
# 필드 설정 로드 (JSON -> SQLite 마이그레이션 포함)
|
|
self._load_field_settings()
|
|
|
|
self._initialized = True
|
|
logger.info(f"설정 관리자 초기화 완료: {self.config_path}")
|
|
|
|
def load(self) -> bool:
|
|
"""
|
|
설정 파일에서 설정을 로드합니다.
|
|
|
|
Returns:
|
|
로드 성공 여부
|
|
"""
|
|
try:
|
|
if self.config_path.exists():
|
|
self._parser.read(self.config_path, encoding='utf-8')
|
|
self._load_settings_from_parser()
|
|
logger.info("설정 파일 로드 완료")
|
|
return True
|
|
else:
|
|
logger.warning("설정 파일이 없습니다. 기본 설정을 사용합니다.")
|
|
self._create_default_config()
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"설정 파일 로드 실패: {e}")
|
|
self._create_default_config()
|
|
return False
|
|
|
|
def save(self) -> bool:
|
|
"""
|
|
현재 설정을 파일에 저장합니다.
|
|
|
|
Returns:
|
|
저장 성공 여부
|
|
"""
|
|
try:
|
|
self._save_settings_to_parser()
|
|
|
|
# 부모 디렉토리 생성
|
|
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
with open(self.config_path, 'w', encoding='utf-8') as f:
|
|
self._parser.write(f)
|
|
|
|
logger.info("설정 파일 저장 완료")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"설정 파일 저장 실패: {e}")
|
|
return False
|
|
|
|
def get(self, section: str, key: str, default: Any = None) -> Any:
|
|
"""
|
|
설정 값을 가져옵니다.
|
|
|
|
Args:
|
|
section: 설정 섹션 (app, layout, database, weather, user)
|
|
key: 설정 키
|
|
default: 기본값
|
|
|
|
Returns:
|
|
설정 값
|
|
|
|
Examples:
|
|
>>> config.get('app', 'theme')
|
|
'dark'
|
|
"""
|
|
try:
|
|
section_obj = getattr(self.settings, section, None)
|
|
if section_obj is None:
|
|
logger.warning(f"존재하지 않는 섹션: {section}")
|
|
return default
|
|
|
|
value = getattr(section_obj, key, default)
|
|
return value
|
|
except Exception as e:
|
|
logger.error(f"설정 값 가져오기 실패: {section}.{key} - {e}")
|
|
return default
|
|
|
|
def set(self, section: str, key: str, value: Any) -> bool:
|
|
"""
|
|
설정 값을 변경합니다.
|
|
|
|
Args:
|
|
section: 설정 섹션
|
|
key: 설정 키
|
|
value: 새로운 값
|
|
|
|
Returns:
|
|
변경 성공 여부
|
|
|
|
Examples:
|
|
>>> config.set('app', 'theme', 'light')
|
|
True
|
|
"""
|
|
try:
|
|
section_obj = getattr(self.settings, section, None)
|
|
if section_obj is None:
|
|
logger.warning(f"존재하지 않는 섹션: {section}")
|
|
return False
|
|
|
|
if not hasattr(section_obj, key):
|
|
logger.warning(f"존재하지 않는 키: {section}.{key}")
|
|
return False
|
|
|
|
setattr(section_obj, key, value)
|
|
logger.debug(f"설정 변경: {section}.{key} = {value}")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"설정 값 변경 실패: {section}.{key} - {e}")
|
|
return False
|
|
|
|
def get_section(self, section: str) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
설정 섹션 전체를 딕셔너리로 반환합니다.
|
|
|
|
Args:
|
|
section: 설정 섹션
|
|
|
|
Returns:
|
|
섹션 설정 딕셔너리
|
|
"""
|
|
try:
|
|
section_obj = getattr(self.settings, section, None)
|
|
if section_obj is None:
|
|
return None
|
|
return asdict(section_obj)
|
|
except Exception as e:
|
|
logger.error(f"섹션 가져오기 실패: {section} - {e}")
|
|
return None
|
|
|
|
def reset_to_default(self, section: str = None):
|
|
"""
|
|
설정을 기본값으로 초기화합니다.
|
|
|
|
Args:
|
|
section: 초기화할 섹션 (None이면 전체 초기화)
|
|
"""
|
|
if section:
|
|
if section == 'app':
|
|
self.settings.app = AppSettings()
|
|
elif section == 'layout':
|
|
self.settings.layout = LayoutSettings()
|
|
elif section == 'database':
|
|
self.settings.database = DatabaseSettings()
|
|
elif section == 'weather':
|
|
self.settings.weather = WeatherSettings()
|
|
elif section == 'user':
|
|
self.settings.user = UserSettings()
|
|
elif section == 'ui_font':
|
|
self.settings.ui_font = UIFontSettings()
|
|
elif section == 'train':
|
|
self.settings.train = TrainSettings()
|
|
logger.info(f"섹션 '{section}' 기본값으로 초기화")
|
|
else:
|
|
self.settings = AllSettings()
|
|
logger.info("전체 설정 기본값으로 초기화")
|
|
|
|
def _load_settings_from_parser(self):
|
|
"""파서에서 설정 객체로 로드"""
|
|
for section_name in ['app', 'layout', 'database', 'weather', 'user', 'ui_font', 'train']:
|
|
if self._parser.has_section(section_name):
|
|
section_obj = getattr(self.settings, section_name)
|
|
for key in self._parser.options(section_name):
|
|
if hasattr(section_obj, key):
|
|
raw_value = self._parser.get(section_name, key)
|
|
# 타입 변환
|
|
expected_type = type(getattr(section_obj, key))
|
|
converted_value = self._convert_value(raw_value, expected_type)
|
|
setattr(section_obj, key, converted_value)
|
|
|
|
def _save_settings_to_parser(self):
|
|
"""설정 객체에서 파서로 저장"""
|
|
for section_name in ['app', 'layout', 'database', 'weather', 'user', 'ui_font', 'train']:
|
|
if not self._parser.has_section(section_name):
|
|
self._parser.add_section(section_name)
|
|
|
|
section_obj = getattr(self.settings, section_name)
|
|
section_dict = asdict(section_obj)
|
|
|
|
for key, value in section_dict.items():
|
|
# 필드 설정은 JSON으로 저장
|
|
if key == 'field_settings':
|
|
self._parser.set(section_name, key, json.dumps(value, ensure_ascii=False))
|
|
else:
|
|
self._parser.set(section_name, key, str(value))
|
|
|
|
def _create_default_config(self):
|
|
"""기본 설정 파일 생성"""
|
|
self.settings = AllSettings()
|
|
self.save()
|
|
logger.info("기본 설정 파일 생성 완료")
|
|
|
|
@staticmethod
|
|
def _convert_value(value: str, target_type: type) -> Any:
|
|
"""
|
|
문자열 값을 대상 타입으로 변환합니다.
|
|
|
|
Args:
|
|
value: 변환할 문자열
|
|
target_type: 대상 타입
|
|
|
|
Returns:
|
|
변환된 값
|
|
"""
|
|
if target_type == bool:
|
|
return value.lower() in ('true', 'yes', '1', 'on')
|
|
elif target_type == int:
|
|
return int(value)
|
|
elif target_type == float:
|
|
return float(value)
|
|
else:
|
|
return value
|
|
|
|
# ========================================================================
|
|
# 편의 메서드
|
|
# ========================================================================
|
|
|
|
@property
|
|
def theme(self) -> str:
|
|
"""현재 테마 반환"""
|
|
return self.settings.app.theme
|
|
|
|
@theme.setter
|
|
def theme(self, value: str):
|
|
"""테마 설정"""
|
|
self.settings.app.theme = value
|
|
|
|
@property
|
|
def current_team(self) -> str:
|
|
"""현재 팀 반환"""
|
|
return self.settings.user.current_team
|
|
|
|
@current_team.setter
|
|
def current_team(self, value: str):
|
|
"""현재 팀 설정"""
|
|
self.settings.user.current_team = value
|
|
|
|
@property
|
|
def current_shift(self) -> str:
|
|
"""현재 근무 유형 반환"""
|
|
return self.settings.user.current_shift
|
|
|
|
@current_shift.setter
|
|
def current_shift(self, value: str):
|
|
"""현재 근무 유형 설정"""
|
|
self.settings.user.current_shift = value
|
|
|
|
def get_layout_sizes(self) -> Dict[str, float]:
|
|
"""레이아웃 크기 비율 반환"""
|
|
return asdict(self.settings.layout)
|
|
|
|
def get_ui_font(self, area: str, style: str) -> Dict[str, Any]:
|
|
"""
|
|
UI 영역별 폰트 설정 가져오기
|
|
|
|
Args:
|
|
area: 영역 (info_bar, section, todo, memo, daily, status, dialog)
|
|
style: 스타일 (title, content, header, label, input, button, train 등)
|
|
|
|
Returns:
|
|
{"family": str, "size": int, "weight": str}
|
|
|
|
Examples:
|
|
>>> config.get_ui_font("section", "title")
|
|
{"family": "GmarketSans", "size": 16, "weight": "bold"}
|
|
"""
|
|
ui_font = self.settings.ui_font
|
|
|
|
# 영역과 스타일 조합으로 속성 이름 생성
|
|
prefix = area.replace("_", "") # info_bar -> infobar
|
|
|
|
# 영역별 prefix 매핑
|
|
area_map = {
|
|
"info_bar": "info_bar",
|
|
"section": "section",
|
|
"todo": "todo",
|
|
"memo": "memo",
|
|
"daily": "daily",
|
|
"daily_inspection": "daily",
|
|
"status": "status",
|
|
"status_bar": "status",
|
|
"dialog": "dialog",
|
|
}
|
|
|
|
prefix = area_map.get(area, area)
|
|
|
|
# 속성 이름 생성
|
|
family_attr = f"{prefix}_{style}_family"
|
|
size_attr = f"{prefix}_{style}_size"
|
|
weight_attr = f"{prefix}_{style}_weight"
|
|
|
|
# 기본값
|
|
default = {"family": FONT_FAMILY, "size": 13, "weight": "normal"}
|
|
|
|
try:
|
|
return {
|
|
"family": getattr(ui_font, family_attr, default["family"]),
|
|
"size": getattr(ui_font, size_attr, default["size"]),
|
|
"weight": getattr(ui_font, weight_attr, default["weight"]),
|
|
}
|
|
except AttributeError:
|
|
logger.warning(f"UI 폰트 설정을 찾을 수 없음: {area}.{style}")
|
|
return default
|
|
|
|
def set_ui_font(self, area: str, style: str, family: str = None, size: int = None, weight: str = None):
|
|
"""
|
|
UI 영역별 폰트 설정 변경
|
|
|
|
Args:
|
|
area: 영역
|
|
style: 스타일
|
|
family: 폰트 패밀리 (None이면 변경 안함)
|
|
size: 폰트 크기 (None이면 변경 안함)
|
|
weight: 폰트 굵기 (None이면 변경 안함)
|
|
"""
|
|
ui_font = self.settings.ui_font
|
|
|
|
area_map = {
|
|
"info_bar": "info_bar",
|
|
"section": "section",
|
|
"todo": "todo",
|
|
"memo": "memo",
|
|
"daily": "daily",
|
|
"daily_inspection": "daily",
|
|
"status": "status",
|
|
"status_bar": "status",
|
|
"dialog": "dialog",
|
|
}
|
|
|
|
prefix = area_map.get(area, area)
|
|
|
|
if family is not None:
|
|
setattr(ui_font, f"{prefix}_{style}_family", family)
|
|
if size is not None:
|
|
setattr(ui_font, f"{prefix}_{style}_size", size)
|
|
if weight is not None:
|
|
setattr(ui_font, f"{prefix}_{style}_weight", weight)
|
|
|
|
logger.debug(f"UI 폰트 설정 변경: {area}.{style}")
|
|
|
|
@property
|
|
def ui_font_settings(self) -> 'UIFontSettings':
|
|
"""UI 폰트 설정 반환"""
|
|
return self.settings.ui_font
|
|
|
|
# ========================================================================
|
|
# 필드 설정 관리 메서드 (SettingsManager 사용)
|
|
# ========================================================================
|
|
|
|
def _get_settings_manager(self):
|
|
"""SettingsManager 인스턴스 반환 (지연 로딩)"""
|
|
if not hasattr(self, '_settings_manager') or self._settings_manager is None:
|
|
from .settings_manager import get_settings_manager
|
|
self._settings_manager = get_settings_manager()
|
|
return self._settings_manager
|
|
|
|
def _load_field_settings(self):
|
|
"""
|
|
필드 설정 로드 (레거시 JSON -> SQLite DB 마이그레이션)
|
|
|
|
기존 JSON 파일이 있으면 SQLite DB로 마이그레이션하고,
|
|
이후에는 DB에서 직접 로드합니다.
|
|
"""
|
|
try:
|
|
settings_mgr = self._get_settings_manager()
|
|
|
|
# 기존 JSON 파일 마이그레이션 체크
|
|
if self.field_settings_file.exists():
|
|
logger.info("기존 JSON 필드 설정을 SQLite DB로 마이그레이션합니다.")
|
|
self._migrate_json_to_db()
|
|
|
|
# 기존 section_field_settings.json 파일도 마이그레이션 체크
|
|
old_settings_file = DATA_DIR / "section_field_settings.json"
|
|
if old_settings_file.exists():
|
|
logger.info("기존 section_field_settings.json을 SQLite DB로 마이그레이션합니다.")
|
|
self._migrate_old_json_to_db(old_settings_file)
|
|
|
|
logger.info("필드 설정 로드 완료 (SQLite DB 사용)")
|
|
|
|
except Exception as e:
|
|
logger.error(f"필드 설정 로드 실패: {e}")
|
|
|
|
def _migrate_json_to_db(self):
|
|
"""JSON 파일을 SQLite DB로 마이그레이션"""
|
|
try:
|
|
with open(self.field_settings_file, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
|
|
settings_mgr = self._get_settings_manager()
|
|
from .settings_manager import FieldSetting as DBFieldSetting
|
|
|
|
for team, sections_data in data.items():
|
|
for section_name, fields_data in sections_data.items():
|
|
fields = []
|
|
for field_data in fields_data:
|
|
field = DBFieldSetting(
|
|
name=field_data.get('name', ''),
|
|
visible=field_data.get('visible', True),
|
|
width=field_data.get('width', 100),
|
|
display_format=field_data.get('display_format')
|
|
)
|
|
fields.append(field)
|
|
settings_mgr.save_field_settings(team, section_name, fields)
|
|
|
|
# 마이그레이션 완료 후 기존 파일 백업
|
|
backup_file = self.field_settings_file.with_suffix('.json.bak')
|
|
self.field_settings_file.rename(backup_file)
|
|
logger.info(f"JSON 파일을 SQLite DB로 마이그레이션 완료, 백업: {backup_file}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"JSON 마이그레이션 실패: {e}")
|
|
|
|
def _migrate_old_json_to_db(self, old_file):
|
|
"""기존 형식의 JSON 파일을 SQLite DB로 마이그레이션"""
|
|
try:
|
|
with open(old_file, 'r', encoding='utf-8') as f:
|
|
old_data = json.load(f)
|
|
|
|
settings_mgr = self._get_settings_manager()
|
|
from .settings_manager import FieldSetting as DBFieldSetting
|
|
|
|
for team, team_data in old_data.items():
|
|
sections = team_data.get("sections", {})
|
|
for section_name, section_data in sections.items():
|
|
fields_data = section_data.get("fields", [])
|
|
fields = []
|
|
for field_data in fields_data:
|
|
field = DBFieldSetting(
|
|
name=field_data.get('name', ''),
|
|
visible=field_data.get('visible', True),
|
|
width=field_data.get('width', 100),
|
|
display_format=field_data.get('display_format')
|
|
)
|
|
fields.append(field)
|
|
settings_mgr.save_field_settings(team, section_name, fields)
|
|
|
|
# 마이그레이션 완료 후 기존 파일 백업
|
|
backup_file = old_file.with_suffix('.json.bak')
|
|
old_file.rename(backup_file)
|
|
logger.info(f"기존 JSON 파일을 SQLite DB로 마이그레이션 완료, 백업: {backup_file}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"기존 JSON 마이그레이션 실패: {e}")
|
|
|
|
def save_field_settings(
|
|
self,
|
|
team: str,
|
|
section_name: str,
|
|
fields: List[FieldSetting]
|
|
):
|
|
"""
|
|
필드 설정 저장 (SQLite DB 사용)
|
|
|
|
Args:
|
|
team: 팀 이름 (예: "1팀")
|
|
section_name: 섹션 이름 (예: "고장")
|
|
fields: 필드 설정 리스트
|
|
"""
|
|
try:
|
|
settings_mgr = self._get_settings_manager()
|
|
from .settings_manager import FieldSetting as DBFieldSetting
|
|
|
|
# FieldSetting -> DBFieldSetting 변환
|
|
db_fields = []
|
|
for f in fields:
|
|
db_field = DBFieldSetting(
|
|
name=f.name,
|
|
visible=f.visible,
|
|
width=f.width,
|
|
display_format=f.display_format
|
|
)
|
|
db_fields.append(db_field)
|
|
|
|
settings_mgr.save_field_settings(team, section_name, db_fields)
|
|
logger.info(f"필드 설정 저장 완료: {team} - {section_name}")
|
|
except Exception as e:
|
|
logger.error(f"필드 설정 저장 실패: {e}")
|
|
raise
|
|
|
|
def load_field_settings(
|
|
self,
|
|
team: str,
|
|
section_name: str
|
|
) -> Optional[List[FieldSetting]]:
|
|
"""
|
|
필드 설정 로드 (SQLite DB 사용)
|
|
|
|
Args:
|
|
team: 팀 이름
|
|
section_name: 섹션 이름
|
|
|
|
Returns:
|
|
필드 설정 리스트 (없으면 None)
|
|
"""
|
|
try:
|
|
settings_mgr = self._get_settings_manager()
|
|
db_fields = settings_mgr.load_field_settings(team, section_name)
|
|
|
|
if not db_fields:
|
|
return None
|
|
|
|
# DBFieldSetting -> FieldSetting 변환
|
|
fields = []
|
|
for f in db_fields:
|
|
field = FieldSetting(
|
|
name=f.name,
|
|
visible=f.visible,
|
|
width=f.width,
|
|
display_format=f.display_format
|
|
)
|
|
fields.append(field)
|
|
|
|
return fields
|
|
except Exception as e:
|
|
logger.error(f"필드 설정 로드 실패: {e}")
|
|
return None
|
|
|
|
def get_field_setting(
|
|
self,
|
|
team: str,
|
|
section_name: str,
|
|
field_name: str
|
|
) -> Optional[FieldSetting]:
|
|
"""
|
|
특정 필드 설정 가져오기
|
|
|
|
Args:
|
|
team: 팀 이름
|
|
section_name: 섹션 이름
|
|
field_name: 필드 이름
|
|
|
|
Returns:
|
|
필드 설정 (없으면 None)
|
|
"""
|
|
fields = self.load_field_settings(team, section_name)
|
|
if not fields:
|
|
return None
|
|
|
|
for field_setting in fields:
|
|
if field_setting.name == field_name:
|
|
return field_setting
|
|
|
|
return None
|
|
|
|
def apply_field_settings_to_fields(
|
|
self,
|
|
team: str,
|
|
section_name: str,
|
|
fields: List[Any] # FieldConfig 리스트
|
|
):
|
|
"""
|
|
저장된 설정을 필드에 적용
|
|
|
|
Args:
|
|
team: 팀 이름
|
|
section_name: 섹션 이름
|
|
fields: FieldConfig 리스트
|
|
"""
|
|
settings_mgr = self._get_settings_manager()
|
|
settings_mgr.apply_field_settings_to_fields(team, section_name, fields)
|
|
logger.debug(f"필드 설정 적용 완료: {team} - {section_name}")
|
|
|
|
def reset_team_field_settings(self, team: str):
|
|
"""팀별 필드 설정 초기화"""
|
|
settings_mgr = self._get_settings_manager()
|
|
settings_mgr.reset_team_field_settings(team)
|
|
logger.info(f"팀 필드 설정 초기화: {team}")
|
|
|
|
def reset_all_field_settings(self):
|
|
"""모든 필드 설정 초기화"""
|
|
settings_mgr = self._get_settings_manager()
|
|
settings_mgr.reset_all_field_settings()
|
|
logger.info("모든 필드 설정 초기화 완료")
|
|
|
|
|
|
# ============================================================================
|
|
# 모듈 레벨 편의 함수
|
|
# ============================================================================
|
|
|
|
def get_config() -> ConfigManager:
|
|
"""
|
|
설정 관리자 인스턴스를 반환합니다.
|
|
|
|
Returns:
|
|
ConfigManager 인스턴스
|
|
"""
|
|
return ConfigManager()
|
|
|
|
|