import os import json import time from pathlib import Path from typing import List, Optional import boto3 import cups import yaml from dotenv import load_dotenv from loguru import logger class Config: def __init__(self): load_dotenv() with open('config.yaml', 'r') as f: self.config = yaml.safe_load(f) # 環境変数で設定値を上書き self.config['s3']['bucket'] = os.getenv('S3_BUCKET_NAME', self.config['s3']['bucket']) self.config['s3']['prefix'] = self.config['s3']['prefix'].replace('${LOCATION}', os.getenv('LOCATION', '')) self.config['printer']['name'] = os.getenv('PRINTER_NAME', self.config['printer']['name']) class S3Manager: def __init__(self, config: Config): self.config = config self.s3_client = boto3.client('s3') def list_files(self) -> List[str]: """S3バケットのファイル一覧を取得""" try: files = [] paginator = self.s3_client.get_paginator('list_objects_v2') for page in paginator.paginate( Bucket=self.config.config['s3']['bucket'], Prefix=self.config.config['s3']['prefix'] ): if 'Contents' in page: files.extend([obj['Key'] for obj in page['Contents']]) return files except Exception as e: logger.error(f"S3ファイル一覧の取得に失敗: {e}") return [] def download_file(self, key: str, local_path: str) -> bool: """S3からファイルをダウンロード""" try: self.s3_client.download_file( self.config.config['s3']['bucket'], key, local_path ) return True except Exception as e: logger.error(f"ファイルのダウンロードに失敗 {key}: {e}") return False class PrinterManager: def __init__(self, config: Config): self.config = config self.conn = cups.Connection() def print_file(self, filepath: str) -> bool: """ファイルを印刷""" printer_config = self.config.config['printer'] max_attempts = printer_config['retry']['max_attempts'] delay = printer_config['retry']['delay_seconds'] for attempt in range(max_attempts): try: job_id = self.conn.printFile( printer_config['name'], filepath, "Rogaining Score", printer_config['options'] ) logger.info(f"印刷ジョブを送信: {filepath} (Job ID: {job_id})") return True except Exception as e: logger.error(f"印刷に失敗 (試行 {attempt + 1}/{max_attempts}): {e}") if attempt < max_attempts - 1: time.sleep(delay) else: return False class FileManager: def __init__(self, config: Config): self.config = config self.file_log_path = Path(self.config.config['app']['save_path']) / self.config.config['app']['file_log'] self.processed_files = self._load_processed_files() def _load_processed_files(self) -> List[str]: """処理済みファイルの一覧を読み込み""" try: if self.file_log_path.exists(): with open(self.file_log_path, 'r') as f: return json.load(f) return [] except Exception as e: logger.error(f"処理済みファイル一覧の読み込みに失敗: {e}") return [] def save_processed_files(self): """処理済みファイルの一覧を保存""" try: self.file_log_path.parent.mkdir(parents=True, exist_ok=True) with open(self.file_log_path, 'w') as f: json.dump(self.processed_files, f) except Exception as e: logger.error(f"処理済みファイル一覧の保存に失敗: {e}") def get_new_files(self, current_files: List[str]) -> List[str]: """新規ファイルを特定""" return list(set(current_files) - set(self.processed_files)) def setup_logging(config: Config): """ロギングの設定""" log_path = Path(config.config['app']['log_path']) log_path.mkdir(parents=True, exist_ok=True) logger.add( log_path / "app.log", rotation="1 day", retention="30 days", level="INFO" ) logger.add( log_path / config.config['app']['error_log'], level="ERROR" ) def main(): config = Config() setup_logging(config) s3_manager = S3Manager(config) printer_manager = PrinterManager(config) file_manager = FileManager(config) logger.info("スコアボード監視システムを開始") while True: try: # S3のファイル一覧を取得 current_files = s3_manager.list_files() # 新規ファイルを特定 new_files = file_manager.get_new_files(current_files) for file_key in new_files: if not file_key.endswith('.pdf'): file_manager.processed_files.append(file_key) continue # 保存先パスの設定 local_path = Path(config.config['app']['save_path']) / Path(file_key).name local_path.parent.mkdir(parents=True, exist_ok=True) # ファイルのダウンロードと印刷 if s3_manager.download_file(file_key, str(local_path)): if printer_manager.print_file(str(local_path)): file_manager.processed_files.append(file_key) file_manager.save_processed_files() time.sleep(10) # ポーリング間隔 except Exception as e: logger.error(f"予期せぬエラーが発生: {e}") time.sleep(30) # エラー時は長めの待機 if __name__ == "__main__": main()