RAG_AgenticServer / app /app_device_routes.py
jeongsoo's picture
reset
baca89e
"""
RAG 검색 챗봇 μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜ - μž₯치 관리 API 라우트 μ •μ˜ (μ‚¬μš©μž μ •μ˜ μ‹€ν–‰, f-string 였λ₯˜ μˆ˜μ •λ¨)
"""
import logging
import requests
import uuid # μ‚¬μš©μž μ •μ˜ 싀행을 μœ„ν•΄ μΆ”κ°€
import time # μ‚¬μš©μž μ •μ˜ 싀행을 μœ„ν•΄ μΆ”κ°€
import shlex # μ‚¬μš©μž μ •μ˜ 싀행을 μœ„ν•΄ μΆ”κ°€
import os # 경둜 처리λ₯Ό μœ„ν•΄ μΆ”κ°€ (ν•„μš”μ‹œ)
from flask import request, jsonify
# 둜거 κ°€μ Έμ˜€κΈ°
logger = logging.getLogger(__name__)
def register_device_routes(app, login_required, DEVICE_SERVER_URL):
"""Flask μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μž₯치 관리 κ΄€λ ¨ 라우트 등둝"""
# μ‚¬μš©μž μ§€μ • μž₯치 μ„œλ²„ URL λ³€μˆ˜
custom_device_url = None
# URL μ„€μ • ν•¨μˆ˜
def get_device_url():
# μ‚¬μš©μž μ§€μ • URL이 있으면 μ‚¬μš©, μ—†μœΌλ©΄ ν™˜κ²½λ³€μˆ˜ κ°’ μ‚¬μš©
return custom_device_url or DEVICE_SERVER_URL
@app.route('/api/device/connect', methods=['POST'])
@login_required
def connect_device_server():
"""μ‚¬μš©μž μ§€μ • μž₯치 μ„œλ²„ URL μ—°κ²° API"""
nonlocal custom_device_url # μƒμœ„ μŠ€μ½”ν”„μ˜ λ³€μˆ˜ μ°Έμ‘°
try:
# μš”μ²­μ—μ„œ URL κ°€μ Έμ˜€κΈ°
request_data = request.get_json()
if not request_data or 'url' not in request_data:
logger.error("URL이 μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
return jsonify({
"success": False,
"error": "URL이 μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€."
}), 400 # Bad Request
new_url = request_data['url'].strip()
if not new_url:
logger.error("URL이 λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€.")
return jsonify({
"success": False,
"error": "URL이 λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€."
}), 400 # Bad Request
# URL μ„€μ •
logger.info(f"μ‚¬μš©μž μ§€μ • μž₯치 μ„œλ²„ URL μ„€μ •: {new_url}")
custom_device_url = new_url
# μ„€μ •λœ URL둜 μƒνƒœ 확인 μ‹œλ„
try:
api_path = "/api/status"
logger.info(f"μž₯치 μ„œλ²„ μƒνƒœ 확인 μš”μ²­: {custom_device_url}{api_path}")
response = requests.get(f"{custom_device_url}{api_path}", timeout=5)
if response.status_code == 200:
try:
data = response.json()
logger.info(f"μ‚¬μš©μž μ§€μ • μž₯치 μ„œλ²„ μ—°κ²° 성곡. 응닡 데이터: {data}")
return jsonify({
"success": True,
"message": "μž₯치 μ„œλ²„ 연결에 μ„±κ³΅ν–ˆμŠ΅λ‹ˆλ‹€.",
"server_status": data.get("status", "정상") # μ„œλ²„ 응닡 ꡬ쑰에 따라 ν‚€ μ‘°μ • ν•„μš”
})
except requests.exceptions.JSONDecodeError:
logger.error("μž₯치 μ„œλ²„ 응닡 JSON νŒŒμ‹± μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "μž₯치 μ„œλ²„λ‘œλΆ€ν„° μœ νš¨ν•˜μ§€ μ•Šμ€ JSON 응닡을 λ°›μ•˜μŠ΅λ‹ˆλ‹€."
}), 502 # Bad Gateway
else:
error_message = f"μž₯치 μ„œλ²„ 응닡 였λ₯˜: {response.status_code}"
try:
error_detail = response.json().get("error", response.text)
error_message += f" - {error_detail}"
except Exception:
error_message += f" - {response.text}"
logger.warning(error_message)
custom_device_url = None # μ—°κ²° μ‹€νŒ¨ μ‹œ URL μ΄ˆκΈ°ν™”
return jsonify({
"success": False,
"error": error_message
}), 502 # Bad Gateway
except requests.exceptions.Timeout:
logger.error(f"μž₯치 μ„œλ²„ μ—°κ²° νƒ€μž„μ•„μ›ƒ ({custom_device_url})")
custom_device_url = None # μ—°κ²° μ‹€νŒ¨ μ‹œ URL μ΄ˆκΈ°ν™”
return jsonify({
"success": False,
"error": "μž₯치 μ„œλ²„ μ—°κ²° νƒ€μž„μ•„μ›ƒ. μ„œλ²„ 응닡이 λ„ˆλ¬΄ λŠλ¦½λ‹ˆλ‹€."
}), 504 # Gateway Timeout
except requests.exceptions.ConnectionError:
logger.error(f"μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨ ({custom_device_url})")
custom_device_url = None # μ—°κ²° μ‹€νŒ¨ μ‹œ URL μ΄ˆκΈ°ν™”
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지, URL이 μ •ν™•ν•œμ§€ ν™•μΈν•΄μ£Όμ„Έμš”."
}), 502 # Bad Gateway
except Exception as e:
logger.error(f"μž₯치 μ„œλ²„ μ—°κ²° 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
custom_device_url = None # μ—°κ²° μ‹€νŒ¨ μ‹œ URL μ΄ˆκΈ°ν™”
return jsonify({
"success": False,
"error": f"μž₯치 μ„œλ²„ μ—°κ²° 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500 # Internal Server Error
except Exception as e:
logger.error(f"/api/device/connect 처리 쀑 λ‚΄λΆ€ μ„œλ²„ 였λ₯˜: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"λ‚΄λΆ€ μ„œλ²„ 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500 # Internal Server Error
@app.route('/api/device/status', methods=['GET'])
@login_required
def device_status():
"""μž₯치 관리 μ„œλ²„μ˜ μƒνƒœλ₯Ό ν™•μΈν•˜λŠ” API μ—”λ“œν¬μΈνŠΈ"""
try:
# μ—°κ²° νƒ€μž„μ•„μ›ƒ μ„€μ •
timeout = 5 # 5초둜 νƒ€μž„μ•„μ›ƒ μ„€μ •
try:
# μž₯치 μ„œλ²„ μƒνƒœ 확인 - 경둜: /api/status
current_device_url = get_device_url()
api_path = "/api/status"
logger.info(f"μž₯치 μ„œλ²„ μƒνƒœ 확인 μš”μ²­: {current_device_url}{api_path}")
response = requests.get(f"{current_device_url}{api_path}", timeout=timeout)
# 응닡 μƒνƒœ μ½”λ“œ 및 λ‚΄μš© λ‘œκΉ… (디버깅 κ°•ν™”)
logger.debug(f"μž₯치 μ„œλ²„ 응닡 μƒνƒœ μ½”λ“œ: {response.status_code}")
try:
logger.debug(f"μž₯치 μ„œλ²„ 응닡 λ‚΄μš©: {response.text[:200]}...") # λ„ˆλ¬΄ κΈΈλ©΄ μž˜λΌμ„œ λ‘œκΉ…
except Exception:
logger.debug("μž₯치 μ„œλ²„ 응닡 λ‚΄μš© λ‘œκΉ… μ‹€νŒ¨ (ν…μŠ€νŠΈ ν˜•μ‹ μ•„λ‹˜?)")
if response.status_code == 200:
# 성곡 μ‹œ 응닡 데이터 ꡬ쑰 확인 및 λ‘œκΉ…
try:
data = response.json()
logger.info(f"μž₯치 μ„œλ²„ μƒνƒœ 확인 성곡. 응닡 데이터: {data}")
return jsonify({"success": True, "status": "connected", "data": data})
except requests.exceptions.JSONDecodeError:
logger.error("μž₯치 μ„œλ²„ 응닡 JSON νŒŒμ‹± μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "μž₯치 μ„œλ²„λ‘œλΆ€ν„° μœ νš¨ν•˜μ§€ μ•Šμ€ JSON 응닡을 λ°›μ•˜μŠ΅λ‹ˆλ‹€."
}), 502 # Bad Gateway (μ—…μŠ€νŠΈλ¦Ό μ„œλ²„ 였λ₯˜)
else:
# μ‹€νŒ¨ μ‹œ 였λ₯˜ λ©”μ‹œμ§€ 포함 λ‘œκΉ…
error_message = f"μž₯치 μ„œλ²„ 응닡 였λ₯˜: {response.status_code}"
try:
# μ„œλ²„μ—μ„œ 였λ₯˜ λ©”μ‹œμ§€λ₯Ό json으둜 λ³΄λ‚΄λŠ” 경우 포함
error_detail = response.json().get("error", response.text)
error_message += f" - {error_detail}"
except Exception:
error_message += f" - {response.text}" # JSON νŒŒμ‹± μ‹€νŒ¨ μ‹œ 원본 ν…μŠ€νŠΈ
logger.warning(error_message) # κ²½κ³  레벨둜 λ‘œκΉ…
return jsonify({
"success": False,
"error": error_message
}), 502 # Bad Gateway
except requests.exceptions.Timeout:
logger.error(f"μž₯치 μ„œλ²„ μ—°κ²° νƒ€μž„μ•„μ›ƒ ({get_device_url()})")
return jsonify({
"success": False,
"error": "μž₯치 μ„œλ²„ μ—°κ²° νƒ€μž„μ•„μ›ƒ. μ„œλ²„ 응닡이 λ„ˆλ¬΄ λŠλ¦½λ‹ˆλ‹€."
}), 504 # Gateway Timeout
except requests.exceptions.ConnectionError:
current_device_url = get_device_url()
logger.error(f"μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨ ({current_device_url})")
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지, URL이 μ •ν™•ν•œμ§€ ν™•μΈν•΄μ£Όμ„Έμš”."
}), 502 # Bad Gateway (μ—°κ²° μ‹€νŒ¨λ„ μ—…μŠ€νŠΈλ¦Ό 문제둜 κ°„μ£Ό)
except Exception as e:
logger.error(f"μž₯치 μ„œλ²„ μ—°κ²° 쀑 μ˜ˆμƒμΉ˜ λͺ»ν•œ 였λ₯˜ λ°œμƒ: {e}", exc_info=True) # 상세 μŠ€νƒ 트레이슀 λ‘œκΉ…
return jsonify({
"success": False,
"error": f"μž₯치 μ„œλ²„ μ—°κ²° 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500 # Internal Server Error (Flask μ•± λ‚΄λΆ€ λ˜λŠ” requests 라이브러리 문제 κ°€λŠ₯μ„±)
except Exception as e:
# 이 try-except 블둝은 /api/device/status 라우트 자체의 λ‚΄λΆ€ 였λ₯˜ 처리
logger.error(f"/api/device/status 처리 쀑 λ‚΄λΆ€ μ„œλ²„ 였λ₯˜: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"λ‚΄λΆ€ μ„œλ²„ 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500 # Internal Server Error
@app.route('/api/device/list', methods=['GET'])
@login_required
def device_list():
"""μž₯치 λͺ©λ‘ 쑰회 API"""
logger.info("μž₯치 λͺ©λ‘ 쑰회 μš”μ²­")
try:
current_device_url = get_device_url()
api_path = "/api/devices" # LocalPCAgent API λ¬Έμ„œμ— 따라 확인 ν•„μš”
logger.info(f"μž₯치 λͺ©λ‘ 쑰회 μš”μ²­: {current_device_url}{api_path}")
response = requests.get(f"{current_device_url}{api_path}", timeout=5)
logger.debug(f"μž₯치 λͺ©λ‘ 응닡 μƒνƒœ μ½”λ“œ: {response.status_code}")
if response.status_code == 200:
try:
data = response.json()
devices = data.get("devices", []) # LocalPCAgent 응닡 ν˜•μ‹μ— 맞게 ν‚€ μ‘°μ •
logger.info(f"μž₯치 λͺ©λ‘ 쑰회 성곡: {len(devices)}개 μž₯치")
return jsonify({
"success": True,
"devices": devices
})
except requests.exceptions.JSONDecodeError:
logger.error("μž₯치 λͺ©λ‘ 응닡 JSON νŒŒμ‹± μ‹€νŒ¨")
return jsonify({"success": False, "error": "μž₯치 μ„œλ²„λ‘œλΆ€ν„° μœ νš¨ν•˜μ§€ μ•Šμ€ JSON 응닡"}), 502
else:
error_message = f"μž₯치 λͺ©λ‘ 쑰회 μ‹€νŒ¨: {response.status_code}"
try: error_message += f" - {response.json().get('error', response.text)}"
except Exception: error_message += f" - {response.text}"
logger.warning(error_message)
return jsonify({
"success": False,
"error": error_message
}), 502
except requests.exceptions.Timeout:
logger.error("μž₯치 λͺ©λ‘ 쑰회 μ‹œκ°„ 초과")
return jsonify({
"success": False,
"error": "μž₯치 λͺ©λ‘ 쑰회 μ‹œκ°„μ΄ μ΄ˆκ³Όλ˜μ—ˆμŠ΅λ‹ˆλ‹€."
}), 504
except requests.exceptions.ConnectionError:
logger.error("μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지 ν™•μΈν•΄μ£Όμ„Έμš”."
}), 503 # Service Unavailable
except Exception as e:
logger.error(f"μž₯치 λͺ©λ‘ 쑰회 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"μž₯치 λͺ©λ‘ 쑰회 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500
@app.route('/api/device/programs', methods=['GET'])
@login_required
def device_programs():
"""μ‹€ν–‰ κ°€λŠ₯ν•œ ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 API"""
logger.info("ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 μš”μ²­")
try:
current_device_url = get_device_url()
api_path = "/api/programs"
logger.info(f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 μš”μ²­: {current_device_url}{api_path}")
response = requests.get(f"{current_device_url}{api_path}", timeout=5)
logger.debug(f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 응닡 μƒνƒœ μ½”λ“œ: {response.status_code}")
if response.status_code == 200:
try:
data = response.json()
programs = data.get("programs", [])
logger.info(f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 성곡: {len(programs)}개 ν”„λ‘œκ·Έλž¨")
return jsonify({
"success": True,
"programs": programs
})
except requests.exceptions.JSONDecodeError:
logger.error("ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 응닡 JSON νŒŒμ‹± μ‹€νŒ¨")
return jsonify({"success": False, "error": "μž₯치 μ„œλ²„λ‘œλΆ€ν„° μœ νš¨ν•˜μ§€ μ•Šμ€ JSON 응닡"}), 502
else:
error_message = f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 μ‹€νŒ¨: {response.status_code}"
try: error_message += f" - {response.json().get('error', response.text)}"
except Exception: error_message += f" - {response.text}"
logger.warning(error_message)
return jsonify({
"success": False,
"error": error_message
}), 502
except requests.exceptions.Timeout:
logger.error("ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 μ‹œκ°„ 초과")
return jsonify({
"success": False,
"error": "ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 μ‹œκ°„μ΄ μ΄ˆκ³Όλ˜μ—ˆμŠ΅λ‹ˆλ‹€."
}), 504
except requests.exceptions.ConnectionError:
logger.error("μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지 ν™•μΈν•΄μ£Όμ„Έμš”."
}), 503
except Exception as e:
logger.error(f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"ν”„λ‘œκ·Έλž¨ λͺ©λ‘ 쑰회 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500
@app.route('/api/device/programs/<program_id>/execute', methods=['POST'])
@login_required
def execute_program(program_id):
"""λ“±λ‘λœ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ API"""
logger.info(f"λ“±λ‘λœ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μš”μ²­: {program_id}")
try:
current_device_url = get_device_url()
api_path = f"/api/programs/{program_id}/execute"
logger.info(f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μš”μ²­: {current_device_url}{api_path}")
response = requests.post(
f"{current_device_url}{api_path}",
json={}, # ν•„μš”μ‹œ 여기에 νŒŒλΌλ―Έν„° μΆ”κ°€ κ°€λŠ₯
timeout=10 # ν”„λ‘œκ·Έλž¨ μ‹€ν–‰μ—λŠ” 더 κΈ΄ μ‹œκ°„ λΆ€μ—¬
)
logger.debug(f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 응닡 μƒνƒœ μ½”λ“œ: {response.status_code}")
if response.status_code == 200:
try:
data = response.json()
# LocalPCAgent μ‘λ‹΅μ—μ„œ ν•„μš”ν•œ 정보 μΆ”μΆœ (예: success, message, output λ“±)
logger.info(f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 응닡: {data}")
return jsonify(data) # μ„œλ²„ 응닡 ꡬ쑰에 맞좰 λ°˜ν™˜
except requests.exceptions.JSONDecodeError:
logger.error("ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 응닡 JSON νŒŒμ‹± μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ„œλ²„λ‘œλΆ€ν„° μœ νš¨ν•˜μ§€ μ•Šμ€ JSON 응닡"
}), 502
else:
error_message = f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μš”μ²­ μ‹€νŒ¨: {response.status_code}"
try: error_message += f" - {response.json().get('error', response.text)}"
except Exception: error_message += f" - {response.text}"
logger.warning(error_message)
return jsonify({
"success": False,
"error": error_message
}), 502 # λ˜λŠ” response.status_code λ₯Ό κ·ΈλŒ€λ‘œ λ°˜ν™˜ν•˜λŠ” 것도 κ³ λ €
except requests.exceptions.Timeout:
logger.error("ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μš”μ²­ μ‹œκ°„ 초과")
return jsonify({
"success": False,
"error": "ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μš”μ²­ μ‹œκ°„μ΄ μ΄ˆκ³Όλ˜μ—ˆμŠ΅λ‹ˆλ‹€."
}), 504
except requests.exceptions.ConnectionError:
logger.error("μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨")
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지 ν™•μΈν•΄μ£Όμ„Έμš”."
}), 503
except Exception as e:
logger.error(f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 쀑 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500
# ================== μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ—”λ“œν¬μΈνŠΈ μΆ”κ°€ ==================
@app.route('/api/device/execute-custom', methods=['POST'])
@login_required
def execute_custom_program():
"""μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ API - μž„μ‹œ IDλ₯Ό μƒμ„±ν•˜μ—¬ 등둝 ν›„ μ‹€ν–‰"""
logger.info("μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μž„μ‹œ 등둝 및 μ‹€ν–‰ μš”μ²­")
# LocalPCAgent μ„œλ²„ 응닡을 μ €μž₯ν•  λ³€μˆ˜
execute_data = None
final_response = None
temp_id = None # μž„μ‹œ ID μ €μž₯ λ³€μˆ˜
try:
# μš”μ²­ 데이터 확인
request_data = request.get_json()
if not request_data or 'command' not in request_data:
logger.error("λͺ…λ Ήμ–΄κ°€ μ œκ³΅λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
return jsonify({
"success": False,
"error": "μ‹€ν–‰ν•  λͺ…λ Ήμ–΄λ₯Ό μ œκ³΅ν•΄μ£Όμ„Έμš”."
}), 400 # Bad Request
command = request_data['command'].strip()
if not command:
logger.error("λͺ…λ Ήμ–΄κ°€ λΉ„μ–΄ μžˆμŠ΅λ‹ˆλ‹€.")
return jsonify({
"success": False,
"error": "μ‹€ν–‰ν•  λͺ…λ Ήμ–΄λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."
}), 400 # Bad Request
# ν˜„μž¬ μž₯치 μ„œλ²„ URL κ°€μ Έμ˜€κΈ°
current_device_url = get_device_url()
if not current_device_url:
logger.error("μž₯치 μ„œλ²„ URL이 μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
return jsonify({"success": False, "error": "μž₯치 μ„œλ²„ URL이 μ„€μ •λ˜μ§€ μ•Šμ•„ μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€."}), 503
# --- 1. μž„μ‹œ ν”„λ‘œκ·Έλž¨ 등둝 ---
# μž„μ‹œ ν”„λ‘œκ·Έλž¨ ID 생성 (μ‹œκ°„ 기반 uuid)
temp_id = f"temp_program_{int(time.time())}_{uuid.uuid4().hex[:8]}"
# λͺ…λ Ήμ–΄ κ²½λ‘œμ™€ 인수 뢄리 (shlex μ‚¬μš©)
try:
# λ”°μ˜΄ν‘œλ‘œ 묢인 λͺ…λ Ήμ–΄ 처리 (Windows 경둜 λ“±)
parts = shlex.split(command)
except Exception as parse_err:
# shlex νŒŒμ‹± 였λ₯˜ μ‹œ κ°„λ‹¨ν•œ λ°©μ‹μœΌλ‘œ 뢄리 (μ˜ˆμ™Έ μΌ€μ΄μŠ€ 처리)
logger.warning(f"shlex νŒŒμ‹± 였λ₯˜ ({parse_err}), λ‹¨μˆœ 뢄리 μ‹œλ„: {command}")
parts = command.split(maxsplit=1) # μ‹€ν–‰νŒŒμΌκ³Ό λ‚˜λ¨Έμ§€ 인수둜 뢄리 μ‹œλ„
if not parts: # 뢄리 κ²°κ³Όκ°€ μ—†μœΌλ©΄ 였λ₯˜
logger.error(f"λͺ…λ Ήμ–΄ νŒŒμ‹± μ‹€νŒ¨: {command}")
return jsonify({"success": False, "error": "λͺ…λ Ήμ–΄ ν˜•μ‹μ„ 인식할 수 μ—†μŠ΅λ‹ˆλ‹€."}), 400
path = parts[0]
args = parts[1:] if len(parts) > 1 else []
# ================== μˆ˜μ •λœ λΆ€λΆ„ μ‹œμž‘ ==================
# κ²½λ‘œμ—μ„œ 파일λͺ…λ§Œ μΆ”μΆœ (λ°±μŠ¬λž˜μ‹œ 문제 ν•΄κ²°)
# os.path.basename μ‚¬μš© λ˜λŠ” λ¬Έμžμ—΄ 처리 방식 μ‚¬μš©
try:
# λͺ¨λ“  λ°±μŠ¬λž˜μ‹œλ₯Ό μŠ¬λž˜μ‹œλ‘œ λ³€κ²½ ν›„ λ§ˆμ§€λ§‰ λΆ€λΆ„ μΆ”μΆœ
filename = path.replace('\\', '/').split('/')[-1]
if not filename: # κ²½λ‘œκ°€ '/'λ‚˜ '\\'둜 λλ‚˜λŠ” 경우 λŒ€λΉ„
filename = path # 원본 경둜λ₯Ό μ‚¬μš©ν•˜κ±°λ‚˜ λ‹€λ₯Έ κΈ°λ³Έκ°’ μ„€μ •
except Exception:
filename = "unknown" # μ˜ˆμ™Έ λ°œμƒ μ‹œ κΈ°λ³Έκ°’
# ================== μˆ˜μ •λœ λΆ€λΆ„ 끝 ====================
# ν”„λ‘œκ·Έλž¨ 등둝 API 호좜 데이터 ꡬ성
logger.info(f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ 등둝 μ‹œλ„: ID={temp_id}, 경둜='{path}', 인수={args}")
register_data = {
"id": temp_id,
# μˆ˜μ •λœ filename μ‚¬μš©
"name": f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ ({filename})",
"path": path,
"args": args,
"ui_required": True, # UIκ°€ ν•„μš”ν•œ κ²ƒμœΌλ‘œ κ°€μ • (ν•„μš”μ‹œ μˆ˜μ •)
"description": f"μž„μ‹œ λ“±λ‘λœ ν”„λ‘œκ·Έλž¨: {command}"
}
register_response = requests.post(
f"{current_device_url}/api/programs", # LocalPCAgent의 ν”„λ‘œκ·Έλž¨ 등둝 μ—”λ“œν¬μΈνŠΈ
json=register_data,
timeout=5
)
if not register_response.ok:
# 등둝 μ‹€νŒ¨ μ‹œ 였λ₯˜ 처리
logger.error(f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ 등둝 μ‹€νŒ¨: {register_response.status_code}")
try:
error_data = register_response.json()
error_message = error_data.get('error', register_response.text)
except:
error_message = register_response.text
return jsonify({
"success": False,
"error": f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ 등둝 μ‹€νŒ¨({register_response.status_code}): {error_message}"
}), 502 # Bad Gateway (Upstream μ‹€νŒ¨)
# --- 2. μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ ---
logger.info(f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ‹œλ„: {temp_id}")
execute_response = requests.post(
f"{current_device_url}/api/programs/{temp_id}/execute", # LocalPCAgent의 ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ—”λ“œν¬μΈνŠΈ
json={},
timeout=15 # 싀행은 더 κΈ΄ μ‹œκ°„ λΆ€μ—¬
)
# μ‹€ν–‰ κ²°κ³Όλ₯Ό μš°μ„  μ €μž₯ (μ‚­μ œ ν›„ λ°˜ν™˜ν•˜κΈ° μœ„ν•΄)
if execute_response.ok:
try:
execute_data = execute_response.json()
# 성곡 응닡을 κΈ°λ³Έ μ‘λ‹΅μœΌλ‘œ μ„€μ •
final_response = jsonify(execute_data)
logger.info(f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 성곡 (응닡 μ½”λ“œ {execute_response.status_code}): {execute_data}")
except Exception as parse_error:
logger.error(f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 성곡 응닡 νŒŒμ‹± μ‹€νŒ¨: {parse_error}")
# νŒŒμ‹± μ‹€νŒ¨ μ‹œ 였λ₯˜ 응닡 μ„€μ •
final_response = jsonify({
"success": False,
"error": f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 응닡(성곡:{execute_response.status_code}) νŒŒμ‹± μ‹€νŒ¨: {str(parse_error)}"
}), 502
else:
# μ‹€ν–‰ μ‹€νŒ¨ μ‹œ 였λ₯˜ 처리
logger.error(f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ‹€νŒ¨: {execute_response.status_code}")
try:
error_data = execute_response.json()
error_message = error_data.get('error', execute_response.text)
except:
error_message = execute_response.text
# μ‹€ν–‰ μ‹€νŒ¨ 응닡을 κΈ°λ³Έ μ‘λ‹΅μœΌλ‘œ μ„€μ •
final_response = jsonify({
"success": False,
"error": f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ μ‹€νŒ¨({execute_response.status_code}): {error_message}"
}), 502
# --- 3. μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‚­μ œ (try-finally λΈ”λ‘μœΌλ‘œ 이동) ---
# μ‹€ν–‰ 성곡/μ‹€νŒ¨ 여뢀와 관계없이 μ‚­μ œ μ‹œλ„
return final_response # μ €μž₯된 μ΅œμ’… 응닡 λ°˜ν™˜
except requests.exceptions.Timeout:
logger.error("μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μš”μ²­ μ‹œκ°„ 초과 (등둝 λ˜λŠ” μ‹€ν–‰ 단계)")
return jsonify({
"success": False,
"error": "ν”„λ‘œκ·Έλž¨ μš”μ²­ μ‹œκ°„μ΄ μ΄ˆκ³Όλ˜μ—ˆμŠ΅λ‹ˆλ‹€ (LocalPCAgent 응닡 μ—†μŒ)."
}), 504 # Gateway Timeout
except requests.exceptions.ConnectionError:
logger.error("μž₯치 관리 μ„œλ²„ μ—°κ²° μ‹€νŒ¨ (등둝 λ˜λŠ” μ‹€ν–‰ 단계)")
return jsonify({
"success": False,
"error": "μž₯치 관리 μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ„œλ²„κ°€ μ‹€ν–‰ 쀑인지 ν™•μΈν•΄μ£Όμ„Έμš”."
}), 503 # Service Unavailable
except Exception as e:
logger.error(f"μ‚¬μš©μž μ •μ˜ ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 쀑 Flask μΈ‘ 였λ₯˜ λ°œμƒ: {e}", exc_info=True)
return jsonify({
"success": False,
"error": f"ν”„λ‘œκ·Έλž¨ μ‹€ν–‰ 쀑 λ‚΄λΆ€ 였λ₯˜ λ°œμƒ: {str(e)}"
}), 500 # Internal Server Error
finally:
# --- 3. μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‚­μ œ (finally λΈ”λ‘μ—μ„œ μ‹€ν–‰) ---
if temp_id and get_device_url(): # μž„μ‹œ IDκ°€ μƒμ„±λ˜μ—ˆκ³  URL이 μœ νš¨ν•  λ•Œλ§Œ μ‚­μ œ μ‹œλ„
current_device_url = get_device_url() # URL λ‹€μ‹œ κ°€μ Έμ˜€κΈ° (ν•„μš”μ‹œ)
logger.info(f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‚­μ œ μ‹œλ„: {temp_id}")
try:
delete_response = requests.delete(
f"{current_device_url}/api/programs/{temp_id}", # LocalPCAgent의 ν”„λ‘œκ·Έλž¨ μ‚­μ œ μ—”λ“œν¬μΈνŠΈ
timeout=3
)
if delete_response.ok:
logger.info(f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‚­μ œ 성곡: {temp_id}")
else:
logger.warning(f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‚­μ œ μ‹€νŒ¨ ({delete_response.status_code}): {temp_id} - {delete_response.text[:100]}")
except Exception as delete_error:
logger.warning(f"μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‚­μ œ 쀑 였λ₯˜ λ°œμƒ (λ¬΄μ‹œλ¨): {delete_error}")
elif not temp_id:
logger.debug("μž„μ‹œ IDκ°€ μƒμ„±λ˜μ§€ μ•Šμ•„ μ‚­μ œ κ±΄λ„ˆ<0xEB>λœ€.")
elif not get_device_url():
logger.warning("μž₯치 μ„œλ²„ URL이 μ—†μ–΄ μž„μ‹œ ν”„λ‘œκ·Έλž¨ μ‚­μ œ κ±΄λ„ˆ<0xEB>λœ€.")
# register_device_routes ν•¨μˆ˜μ˜ 끝