diff --git a/.env b/.env index d43bde4..862e8b5 100644 --- a/.env +++ b/.env @@ -20,3 +20,4 @@ IMGWK_REMBG_PROVIDER=auto # auto|dml|cpu IMGWK_NO_TRAY = 0 # IMGWK_NO_TRAY=1 설정 시 트레이 비활성화 +IMGWK_CLEAN_OLDER_THAN_SEC = 600 # 5분이 지난 파일만 정리하기 diff --git a/build/exe.win-amd64-3.11/.env b/build/exe.win-amd64-3.11/.env new file mode 100644 index 0000000..862e8b5 --- /dev/null +++ b/build/exe.win-amd64-3.11/.env @@ -0,0 +1,23 @@ +# 큐 대기 한도(가득 차면 429) +IMGWK_MAX_PENDING=400 + +IMGWK_WORKER_READY_TIMEOUT_SEC = 120 # 워커 작시 READY 타임아웃 + +# 워커 롤링 임계치 +IMGWK_ROLL_MAX_RSS_MB=3600 +IMGWK_ROLL_MAX_JOBS=500 +IMGWK_ROLL_MAX_UPTIME_SEC=3600 + +# 잡 타임아웃(초) +IMGWK_JOB_TIMEOUT_SEC=600 + +# 테스트: 2건마다 워커 재시작 (1=활성화) +IMGWK_TEST_ROLL_EVERY_2=0 + +IMGWK_OCR_PROVIDER=auto # auto|dml|cpu +IMGWK_MIGAN_PROVIDER=auto # auto|dml|cpu +IMGWK_REMBG_PROVIDER=auto # auto|dml|cpu + +IMGWK_NO_TRAY = 0 # IMGWK_NO_TRAY=1 설정 시 트레이 비활성화 + +IMGWK_CLEAN_OLDER_THAN_SEC = 600 # 5분이 지난 파일만 정리하기 diff --git a/build/exe.win-amd64-3.11/ImgWorker.zip b/build/exe.win-amd64-3.11/ImgWorker.zip index 5f5bedb..813e6c4 100644 Binary files a/build/exe.win-amd64-3.11/ImgWorker.zip and b/build/exe.win-amd64-3.11/ImgWorker.zip differ diff --git a/main.py b/main.py index 384b6b7..7e77da1 100644 --- a/main.py +++ b/main.py @@ -238,7 +238,12 @@ def _cleanup_runtime_files(): pass -def _safe_rmtree_contents(target_dir: str): +def _safe_rmtree_contents(target_dir: str, older_than_sec: int = 0): + """대상 디렉터리 하위의 항목을 삭제. + + older_than_sec > 0 이면, 현재시각 기준 해당 초보다 오래된 항목만 삭제한다. + 디렉터리는 내부 파일을 조건에 따라 정리한 뒤 비어 있으면 삭제한다. + """ try: if not target_dir: return @@ -251,13 +256,51 @@ def _safe_rmtree_contents(target_dir: str): except Exception: return os.makedirs(td, exist_ok=True) + + now_ts = time.time() + threshold = max(0, int(older_than_sec or 0)) + + def _remove_file_if_old(fp: str): + try: + if threshold <= 0: + os.remove(fp) + return + mt = os.path.getmtime(fp) + if (now_ts - mt) >= threshold: + os.remove(fp) + except Exception: + pass + + def _cleanup_dir(dp: str): + try: + # 파일은 조건부 삭제 + for root, dirs, files in os.walk(dp, topdown=False): + for fn in files: + _remove_file_if_old(os.path.join(root, fn)) + # 하위 폴더는 비어 있으면 삭제(항상 안전) + for dn in dirs: + full = os.path.join(root, dn) + try: + if not os.listdir(full): + os.rmdir(full) + except Exception: + pass + # 최상위 폴더도 비어 있으면 삭제 + try: + if os.path.isdir(dp) and not os.listdir(dp): + os.rmdir(dp) + except Exception: + pass + except Exception: + pass + for name in os.listdir(td): p = os.path.join(td, name) try: if os.path.isdir(p): - shutil.rmtree(p, ignore_errors=True) + _cleanup_dir(p) else: - os.remove(p) + _remove_file_if_old(p) except Exception: pass except Exception: @@ -271,8 +314,12 @@ def _purge_temp_dirs(): work_dir = os.path.join(APP_DATA_DIR, "work") output_dir = os.path.join(APP_DATA_DIR, "output") outputs_dir = os.path.join(APP_DATA_DIR, "outputs") + try: + ttl = int(os.environ.get("IMGWK_CLEAN_OLDER_THAN_SEC", "300")) + except Exception: + ttl = 300 for d in (incoming_dir, work_dir, output_dir, outputs_dir): - _safe_rmtree_contents(d) + _safe_rmtree_contents(d, older_than_sec=max(0, ttl)) except Exception: pass @@ -442,6 +489,8 @@ class WorkerManager: # 실행 상태/성능 지표 self._running_jobs = 0 self._recent_total_ms = [] # 최근 작업 total_ms 집계 + # 최근 추론 장치(DML/GPU/CPU) 요약 + self._last_device = None # 기본 토글/설정(필요 시 요청에서 오버라이드) self._toggle_states: Dict[str, Any] = { @@ -621,6 +670,17 @@ class WorkerManager: self._recent_total_ms.append(total_ms) if len(self._recent_total_ms) > 100: self._recent_total_ms = self._recent_total_ms[-100:] + # 최근 장치 정보 업데이트 + try: + rr = job.result if isinstance(job.result, dict) else {} + dev = (rr.get("inpaint_device") or rr.get("device") or rr.get("provider") or "").lower() + if dev: + if ("dml" in dev) or ("directml" in dev) or ("gpu" in dev): + self._last_device = "dml" + elif "cpu" in dev: + self._last_device = "cpu" + except Exception: + pass except Exception: pass except Exception: @@ -1079,12 +1139,23 @@ def worker_status(): ready = _worker.is_ready if _worker else False avg_sec = None active = False + provider = None + running_jobs = 0 + pending_jobs = 0 try: if _worker is not None: - active = bool(getattr(_worker, "_running_jobs", 0) > 0) + running_jobs = int(getattr(_worker, "_running_jobs", 0) or 0) + try: + pending_jobs = int(_worker.pending_jobs_count()) + except Exception: + pending_jobs = 0 + # 대기중이거나 실행중이면 ACTIVE + active = bool((running_jobs + pending_jobs) > 0) arr = getattr(_worker, "_recent_total_ms", []) if arr: avg_sec = (sum(arr) / len(arr)) / 1000.0 + # 최근 장치 표시(dml|cpu) + provider = getattr(_worker, "_last_device", None) except Exception: pass return { @@ -1092,6 +1163,9 @@ def worker_status(): "pid": _worker.pid if _worker else None, "active": active, "avg_sec_per_image": avg_sec, + "provider": provider, + "running_jobs": running_jobs, + "pending_jobs": pending_jobs, } diff --git a/modules/__pycache__/image_worker.cpython-311.pyc b/modules/__pycache__/image_worker.cpython-311.pyc index 179517c..e080ff4 100644 Binary files a/modules/__pycache__/image_worker.cpython-311.pyc and b/modules/__pycache__/image_worker.cpython-311.pyc differ diff --git a/modules/__pycache__/tray_app.cpython-311.pyc b/modules/__pycache__/tray_app.cpython-311.pyc index 6414474..6423bda 100644 Binary files a/modules/__pycache__/tray_app.cpython-311.pyc and b/modules/__pycache__/tray_app.cpython-311.pyc differ diff --git a/modules/image_worker.py b/modules/image_worker.py index 7e3ec61..e23876e 100644 --- a/modules/image_worker.py +++ b/modules/image_worker.py @@ -114,7 +114,7 @@ def worker_main( try: base_program = os.environ.get("PROGRAMDATA", r"C:\\ProgramData") app_data_dir = os.path.join(base_program, "ImgWorker") - def _safe_rmtree_contents(target_dir: str): + def _safe_rmtree_contents(target_dir: str, older_than_sec: int = 300): try: if not target_dir: return @@ -123,20 +123,34 @@ def worker_main( if os.path.commonpath([base, td]) != base: return os.makedirs(td, exist_ok=True) - for name in os.listdir(td): - p = os.path.join(td, name) + now_ts = time.time() + thr = max(0, int(older_than_sec or 0)) + + def _remove_if_old(fp: str): try: - if os.path.isdir(p): - import shutil as _sh - _sh.rmtree(p, ignore_errors=True) - else: - os.remove(p) + if thr <= 0: + os.remove(fp) + return + mt = os.path.getmtime(fp) + if (now_ts - mt) >= thr: + os.remove(fp) except Exception: pass + + for root, dirs, files in os.walk(td, topdown=False): + for fn in files: + _remove_if_old(os.path.join(root, fn)) + for dn in dirs: + full = os.path.join(root, dn) + try: + if not os.listdir(full): + os.rmdir(full) + except Exception: + pass except Exception: pass for d in (os.path.join(app_data_dir, "incoming"), os.path.join(app_data_dir, "work"), os.path.join(app_data_dir, "output"), os.path.join(app_data_dir, "outputs")): - _safe_rmtree_contents(d) + _safe_rmtree_contents(d, older_than_sec=int(os.environ.get("IMGWK_CLEAN_OLDER_THAN_SEC", "300"))) except Exception: pass @@ -290,20 +304,37 @@ def worker_main( if task is None: logger.info("Shutdown signal 수신 → 종료") try: - # 종료 시 임시 폴더 정리 + # 종료 시 임시 폴더 정리(5분 경과 파일만) base_program = os.environ.get("PROGRAMDATA", r"C:\\ProgramData") app_data_dir = os.path.join(base_program, "ImgWorker") - for d in (os.path.join(app_data_dir, "incoming"), os.path.join(app_data_dir, "work"), os.path.join(app_data_dir, "output"), os.path.join(app_data_dir, "outputs")): + def _cleanup_dir(dp: str, older_than_sec: int = 300): try: - for n in os.listdir(d): - p = os.path.join(d, n) - if os.path.isdir(p): - import shutil as _sh - _sh.rmtree(p, ignore_errors=True) - else: - os.remove(p) + now_ts = time.time() + thr = max(0, int(older_than_sec or 0)) + for root, dirs, files in os.walk(dp, topdown=False): + for fn in files: + fp = os.path.join(root, fn) + try: + if thr <= 0: + os.remove(fp) + else: + mt = os.path.getmtime(fp) + if (now_ts - mt) >= thr: + os.remove(fp) + except Exception: + pass + for dn in dirs: + full = os.path.join(root, dn) + try: + if not os.listdir(full): + os.rmdir(full) + except Exception: + pass except Exception: pass + ttl = int(os.environ.get("IMGWK_CLEAN_OLDER_THAN_SEC", "300")) + for d in (os.path.join(app_data_dir, "incoming"), os.path.join(app_data_dir, "work"), os.path.join(app_data_dir, "output"), os.path.join(app_data_dir, "outputs")): + _cleanup_dir(d, older_than_sec=ttl) except Exception: pass break diff --git a/modules/tray_app.py b/modules/tray_app.py index d7e6525..e1a5071 100644 --- a/modules/tray_app.py +++ b/modules/tray_app.py @@ -49,11 +49,13 @@ class TrayController: ready = bool(st.get("ready", False)) active = bool(st.get("active", False)) avg_sec = st.get("avg_sec_per_image") + provider = (st.get("provider") or "").upper() pid = st.get("pid") self._last_ready = ready status_label = 'ACTIVE' if active else ('READY' if ready else 'STOP') perf = f" - avg. {avg_sec:.1f}(sec/장)" if isinstance(avg_sec, (int, float)) else '' - title = f"ImgWorker [{status_label}]{perf}" + prov = f" [{provider}]" if provider else '' + title = f"ImgWorker [{status_label}]{perf}{prov}" if self.icon: self.icon.title = title # 메뉴를 상태에 맞게 재구성 diff --git a/setup.py b/setup.py index a526606..ab65bff 100644 --- a/setup.py +++ b/setup.py @@ -148,14 +148,11 @@ class BuildWithBytecode(_build_exe): except Exception: pass -# 추가 파일들: README, .env(있다면), 스크립트 -# EXTRA_FILES = [ -# (os.path.join(ROOT_DIR, "README.md"), "README.md"), -# (os.path.join(ROOT_DIR, "requirements.txt"), "requirements.txt"), -# ] -# for src, dst in list(EXTRA_FILES): -# if os.path.isfile(src): -# DATA_FILES.append((src, dst)) +# 추가 파일들: .env가 있으면 실행 파일과 같은 폴더에 포함 +ENV_FILE = os.path.join(ROOT_DIR, ".env") +if os.path.isfile(ENV_FILE): + # exe가 위치한 루트에 배치 → main.py의 load_dotenv(ROOT_DIR/.env)와 일치 + DATA_FILES.append((ENV_FILE, ".env")) # PowerShell 스크립트 포함 # 배포 편의를 위해 modules/scripts 경로 사용