From 9287889fdf764a7381af2367527e85ca28282806 Mon Sep 17 00:00:00 2001 From: projectdx Date: Thu, 26 Mar 2026 22:23:06 +0900 Subject: [PATCH] fix(ohli24): refresh queue progress without manual reload - fix the queue socket namespace and framework event name for ohli24 - listen to GDM download_status updates and patch rendered rows in place - re-render silently when polling detects progress-only changes - bump patch version to 0.7.22 --- docs/history/2026-03-26.history.md | 24 ++++++++++ info.yaml | 2 +- templates/anime_downloader_ohli24_queue.html | 49 ++++++++++++++++---- 3 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 docs/history/2026-03-26.history.md diff --git a/docs/history/2026-03-26.history.md b/docs/history/2026-03-26.history.md new file mode 100644 index 0000000..fb6478b --- /dev/null +++ b/docs/history/2026-03-26.history.md @@ -0,0 +1,24 @@ +# 2026-03-26 작업 이력 + +## ohli24 큐 탭 초기화 버튼 오류 수정 + +- `ohli24` 큐 탭의 `초기화` 버튼 클릭 시 확인 모달 뒤에 `{ret: 'error'}`가 떨어지고 실제 초기화가 진행되지 않는 문제를 점검했다. +- 원인은 [mod_ohli24.py](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/mod_ohli24.py) 의 `queue_command` 처리에서 `req.form["entity_id"]`를 무조건 읽는 방식이었다. +- 큐 템플릿은 `reset`, `delete_completed` 요청 시 `entity_id` 없이 `globalSendCommand('reset', null, ...)` 형태로 보내므로, 서버가 `reset` 분기까지 내려가기 전에 실패할 수 있었다. +- `queue_command`를 [mod_anilife.py](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/mod_anilife.py) 와 같은 안전 패턴으로 정리해 `command`, `entity_id`를 `req.form.get(...)`로 읽도록 수정했다. +- `reset` 처리 시 GDM 다운로드 취소만 하지 않고, [ffmpeg_queue_v1.py](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/lib/ffmpeg_queue_v1.py) 의 `reset` 경로를 통해 `self.queue` 런타임 큐와 `entity_list`도 함께 비우도록 보강했다. +- 일반 큐 명령 fallback도 `entity_id`가 비어 있을 수 있는 경우를 고려해 `int(entity_id) if digit else 0` 패턴으로 안전화했다. + +## 2026-03-26 검증 + +- `python -m py_compile /Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/mod_ohli24.py`: success +- 수정 후 [mod_ohli24.py](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/mod_ohli24.py) 의 `queue_command` 분기에서 `reset` 요청이 `entity_id` 없이도 처리 가능한지 코드 경로를 재확인했다. + +## ohli24 큐 진행률 실시간 반영 보강 + +- [anime_downloader_ohli24_queue.html](/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader/templates/anime_downloader_ohli24_queue.html) 에서 큐 페이지 Socket.IO 네임스페이스가 `ohli24`가 아닌 `linkkf`로 하드코딩되어 있어, 모듈 전용 큐 이벤트를 정상 수신하지 못하는 문제를 확인했다. +- 같은 템플릿이 GDM 이벤트를 `status`로 수신하고 있었는데, 실제 [gommi_downloader_manager/mod_queue.py](/Volumes/WD/Users/Work/python/ff_dev_plugins/gommi_downloader_manager/mod_queue.py) 는 `/gommi_downloader_manager` 네임스페이스에 `download_status` 이벤트를 emit하고 있었다. +- 추가로 `autoRefreshList()`가 항목 개수 변화가 있을 때만 `renderList()`를 호출하고 있어, 진행률/속도만 변하는 구간에서는 폴링 fallback이 있어도 화면이 갱신되지 않았다. +- 템플릿을 `PACKAGE_NAME`, `MODULE_NAME` 기반 동적 네임스페이스로 바꾸고, framework 이벤트명도 `MODULE_NAME + '_status'`를 쓰도록 정리했다. +- GDM 소켓은 `download_status`를 수신하도록 맞췄고, 이미 렌더된 항목은 전체 새로고침 대신 progress/status/button 영역만 즉시 갱신하게 보강했다. +- 소켓 이벤트를 놓치더라도 현재 DOM과 서버 목록을 비교해 진행률 차이가 있으면 조용히 다시 렌더하도록 fallback을 추가했다. diff --git a/info.yaml b/info.yaml index f075549..374c595 100644 --- a/info.yaml +++ b/info.yaml @@ -1,5 +1,5 @@ title: "애니 다운로더" -version: 0.7.21 +version: 0.7.22 package_name: "anime_downloader" developer: "projectdx" description: "anime downloader" diff --git a/templates/anime_downloader_ohli24_queue.html b/templates/anime_downloader_ohli24_queue.html index 0cc4c85..0744059 100644 --- a/templates/anime_downloader_ohli24_queue.html +++ b/templates/anime_downloader_ohli24_queue.html @@ -150,11 +150,12 @@ $(document).ready(function(){ var protocol = location.protocol; var socketUrl = protocol + "//" + document.domain + ":" + location.port; + var frameworkStatusEvent = MODULE_NAME + '_status'; // Queue 전용 소켓 시도 var queueSocket = null; try { - queueSocket = io.connect(socketUrl + '/anime_downloader/linkkf/queue'); + queueSocket = io.connect(socketUrl + '/' + PACKAGE_NAME + '/' + MODULE_NAME + '/queue'); } catch (e) { console.error('Queue socket error:', e); } @@ -162,7 +163,7 @@ $(document).ready(function(){ var frameworkSocket = null; try { frameworkSocket = io.connect(socketUrl + '/framework'); - frameworkSocket.on('linkkf_status', function(data) { + frameworkSocket.on(frameworkStatusEvent, function(data) { status_html(data); }); } catch (e) { @@ -180,12 +181,16 @@ $(document).ready(function(){ var gdmSocket = null; try { gdmSocket = io.connect(socketUrl + '/gommi_downloader_manager'); - gdmSocket.on('status', function(data) { + gdmSocket.on('download_status', function(data) { // 이 모듈과 관련된 작업만 처리 if (data.caller_plugin === PACKAGE_NAME + '_' + MODULE_NAME) { - // GDM 데이터를 큐 형식으로 변환하여 UI 업데이트 - // 수동 새로고침 없이 실시간 반영을 위해 renderList 호출 주기를 짧게 하거나 직접 UI 갱신 + if (document.getElementById("progress_" + data.callback_id)) { + status_html(convertGdmStatus(data)); + button_html(convertGdmStatus(data)); + return; + } silentFetchList(function(newList) { + current_list_length = newList.length; renderList(newList); }); } @@ -214,8 +219,9 @@ function silentFetchList(callback) { function autoRefreshList() { silentFetchList(function(data) { - if (data.length !== current_list_length) { - current_list_length = data.length; + var shouldRender = data.length !== current_list_length || hasRenderableChanges(data); + current_list_length = data.length; + if (shouldRender) { renderList(data); } @@ -246,6 +252,34 @@ function on_start() { }); } +function convertGdmStatus(data) { + return { + idx: data.callback_id, + percent: data.progress, + status_str: data.status_str, + status_kor: data.status_kor, + current_pf_count: data.current_pf_count || 0, + current_speed: data.speed || '', + download_time: data.eta || '' + }; +} + +function hasRenderableChanges(data) { + if (!data || data.length === 0) return false; + for (var i = 0; i < data.length; i++) { + var item = data[i]; + var progressEl = document.getElementById("progress_" + item.idx); + if (!progressEl) return true; + var currentPercent = parseInt(progressEl.style.width || progressEl.textContent || '0', 10); + if (currentPercent !== parseInt(item.percent || 0, 10)) return true; + var speedEl = document.getElementById("current_speed_" + item.idx); + if (speedEl && speedEl.textContent !== String(item.current_speed || '')) return true; + var statusEl = document.getElementById("status_" + item.idx); + if (statusEl && statusEl.textContent.indexOf(item.status_kor) === -1) return true; + } + return false; +} + function renderList(data) { $("#list").html(''); if (!data || data.length == 0) { @@ -408,4 +442,3 @@ function status_html(data) { {% endblock %} -