feat: add waiting status and clear-completed queue action
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
title: "GDM"
|
title: "GDM"
|
||||||
package_name: gommi_downloader_manager
|
package_name: gommi_downloader_manager
|
||||||
version: '0.2.35'
|
version: '0.2.37'
|
||||||
description: FlaskFarm 범용 다운로더 큐 - YouTube, 애니24, 링크애니, Anilife 지원
|
description: FlaskFarm 범용 다운로더 큐 - YouTube, 애니24, 링크애니, Anilife 지원
|
||||||
developer: projectdx
|
developer: projectdx
|
||||||
home: https://gitea.yommi.duckdns.org/projectdx/gommi_downloader_manager
|
home: https://gitea.yommi.duckdns.org/projectdx/gommi_downloader_manager
|
||||||
|
|||||||
30
mod_queue.py
30
mod_queue.py
@@ -19,6 +19,7 @@ from framework import F, socketio
|
|||||||
class DownloadStatus(str, Enum):
|
class DownloadStatus(str, Enum):
|
||||||
PENDING = "pending"
|
PENDING = "pending"
|
||||||
EXTRACTING = "extracting" # 메타데이터 추출 중
|
EXTRACTING = "extracting" # 메타데이터 추출 중
|
||||||
|
WAITING = "waiting" # 동시 다운로드 슬롯 대기 중
|
||||||
DOWNLOADING = "downloading"
|
DOWNLOADING = "downloading"
|
||||||
PAUSED = "paused"
|
PAUSED = "paused"
|
||||||
COMPLETED = "completed"
|
COMPLETED = "completed"
|
||||||
@@ -203,6 +204,33 @@ class ModuleQueue(PluginModuleBase):
|
|||||||
self.P.logger.error(f'DB Delete Error: {e}')
|
self.P.logger.error(f'DB Delete Error: {e}')
|
||||||
|
|
||||||
ret['msg'] = '항목이 삭제되었습니다.'
|
ret['msg'] = '항목이 삭제되었습니다.'
|
||||||
|
|
||||||
|
elif command == 'delete_completed':
|
||||||
|
# 완료된 항목 일괄 삭제 (메모리 + DB)
|
||||||
|
removed_memory = 0
|
||||||
|
with self._queue_lock:
|
||||||
|
remove_ids = [
|
||||||
|
task_id
|
||||||
|
for task_id, task in self._downloads.items()
|
||||||
|
if task.status == DownloadStatus.COMPLETED
|
||||||
|
]
|
||||||
|
for task_id in remove_ids:
|
||||||
|
del self._downloads[task_id]
|
||||||
|
removed_memory = len(remove_ids)
|
||||||
|
|
||||||
|
removed_db = 0
|
||||||
|
try:
|
||||||
|
from .model import ModelDownloadItem
|
||||||
|
with F.app.app_context():
|
||||||
|
removed_db = F.db.session.query(ModelDownloadItem).filter(
|
||||||
|
ModelDownloadItem.status == DownloadStatus.COMPLETED
|
||||||
|
).delete(synchronize_session=False)
|
||||||
|
F.db.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
self.P.logger.error(f'DB Delete Completed Error: {e}')
|
||||||
|
|
||||||
|
ret['msg'] = f'완료 항목 삭제: 메모리 {removed_memory}개, DB {removed_db}개'
|
||||||
|
ret['data'] = {'memory': removed_memory, 'db': removed_db}
|
||||||
|
|
||||||
# ===== YouTube API for Chrome Extension =====
|
# ===== YouTube API for Chrome Extension =====
|
||||||
|
|
||||||
@@ -747,6 +775,8 @@ class DownloadTask:
|
|||||||
ModuleQueue._ensure_concurrency_limit()
|
ModuleQueue._ensure_concurrency_limit()
|
||||||
sem = ModuleQueue._concurrency_sem
|
sem = ModuleQueue._concurrency_sem
|
||||||
if sem is not None:
|
if sem is not None:
|
||||||
|
self.status = DownloadStatus.WAITING
|
||||||
|
self._emit_status()
|
||||||
while not self._cancelled:
|
while not self._cancelled:
|
||||||
if sem.acquire(timeout=0.5):
|
if sem.acquire(timeout=0.5):
|
||||||
slot_acquired = True
|
slot_acquired = True
|
||||||
|
|||||||
@@ -323,6 +323,10 @@
|
|||||||
background: linear-gradient(135deg, rgba(168, 85, 247, 0.1), rgba(30, 41, 59, 0.95));
|
background: linear-gradient(135deg, rgba(168, 85, 247, 0.1), rgba(30, 41, 59, 0.95));
|
||||||
border-color: rgba(168, 85, 247, 0.25);
|
border-color: rgba(168, 85, 247, 0.25);
|
||||||
}
|
}
|
||||||
|
.dl-card.status-waiting {
|
||||||
|
background: linear-gradient(135deg, rgba(245, 158, 11, 0.1), rgba(30, 41, 59, 0.95));
|
||||||
|
border-color: rgba(245, 158, 11, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
/* ID & Meta Row */
|
/* ID & Meta Row */
|
||||||
.dl-meta {
|
.dl-meta {
|
||||||
@@ -440,6 +444,9 @@
|
|||||||
|
|
||||||
.status-downloading { color: var(--accent-primary); }
|
.status-downloading { color: var(--accent-primary); }
|
||||||
.status-downloading .status-dot { background-color: var(--accent-primary); box-shadow: 0 0 8px var(--accent-primary); animation: pulse 1.5s infinite; }
|
.status-downloading .status-dot { background-color: var(--accent-primary); box-shadow: 0 0 8px var(--accent-primary); animation: pulse 1.5s infinite; }
|
||||||
|
|
||||||
|
.status-waiting { color: var(--warning); }
|
||||||
|
.status-waiting .status-dot { background-color: var(--warning); box-shadow: 0 0 8px var(--warning); animation: pulse 1.5s infinite; }
|
||||||
|
|
||||||
.status-completed { color: var(--success); }
|
.status-completed { color: var(--success); }
|
||||||
.status-completed .status-dot { background-color: var(--success); }
|
.status-completed .status-dot { background-color: var(--success); }
|
||||||
@@ -658,6 +665,8 @@
|
|||||||
.dl-status-pill.status-pending .status-dot { background: var(--text-muted); opacity: 0.5; }
|
.dl-status-pill.status-pending .status-dot { background: var(--text-muted); opacity: 0.5; }
|
||||||
.dl-status-pill.status-extracting { background: rgba(168, 85, 247, 0.2); color: #c084fc; }
|
.dl-status-pill.status-extracting { background: rgba(168, 85, 247, 0.2); color: #c084fc; }
|
||||||
.dl-status-pill.status-extracting .status-dot { background: #c084fc; animation: pulse 1s infinite; }
|
.dl-status-pill.status-extracting .status-dot { background: #c084fc; animation: pulse 1s infinite; }
|
||||||
|
.dl-status-pill.status-waiting { background: rgba(240, 173, 78, 0.2); color: var(--warning); }
|
||||||
|
.dl-status-pill.status-waiting .status-dot { background: var(--warning); animation: pulse 1s infinite; }
|
||||||
.dl-status-pill.status-error { background: rgba(217, 83, 79, 0.2); color: var(--danger); }
|
.dl-status-pill.status-error { background: rgba(217, 83, 79, 0.2); color: var(--danger); }
|
||||||
.dl-status-pill.status-error .status-dot { background: var(--danger); }
|
.dl-status-pill.status-error .status-dot { background: var(--danger); }
|
||||||
.dl-status-pill.status-cancelled { background: rgba(107, 114, 128, 0.2); color: #9ca3af; }
|
.dl-status-pill.status-cancelled { background: rgba(107, 114, 128, 0.2); color: #9ca3af; }
|
||||||
@@ -882,6 +891,9 @@
|
|||||||
<button type="button" class="btn-premium" onclick="deleteSelected()" id="delete_selected_btn" style="display: none;">
|
<button type="button" class="btn-premium" onclick="deleteSelected()" id="delete_selected_btn" style="display: none;">
|
||||||
<i class="fa fa-trash-o"></i> 선택삭제 (<span id="selected_count">0</span>)
|
<i class="fa fa-trash-o"></i> 선택삭제 (<span id="selected_count">0</span>)
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="btn-premium" onclick="deleteCompleted()">
|
||||||
|
<i class="fa fa-check"></i> 완료 삭제
|
||||||
|
</button>
|
||||||
<button type="button" class="btn-premium danger" onclick="resetList()">
|
<button type="button" class="btn-premium danger" onclick="resetList()">
|
||||||
<i class="fa fa-trash"></i> Reset All
|
<i class="fa fa-trash"></i> Reset All
|
||||||
</button>
|
</button>
|
||||||
@@ -1168,7 +1180,7 @@
|
|||||||
<i class="fa fa-clock-o"></i> ${startTime !== '-' ? startTime.split(' ')[1] || startTime : '-'}
|
<i class="fa fa-clock-o"></i> ${startTime !== '-' ? startTime.split(' ')[1] || startTime : '-'}
|
||||||
</div>
|
</div>
|
||||||
<div class="dl-actions">
|
<div class="dl-actions">
|
||||||
<button class="dl-btn cancel" title="취소" onclick="event.stopPropagation(); cancelDownload('${item.id}')" ${status === 'downloading' || status === 'pending' || status === 'paused' || status === 'extracting' ? '' : 'disabled'}>
|
<button class="dl-btn cancel" title="취소" onclick="event.stopPropagation(); cancelDownload('${item.id}')" ${status === 'downloading' || status === 'pending' || status === 'paused' || status === 'extracting' || status === 'waiting' ? '' : 'disabled'}>
|
||||||
<i class="fa fa-stop"></i>
|
<i class="fa fa-stop"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="dl-btn delete" title="삭제" onclick="event.stopPropagation(); deleteDownload('${item.id}')">
|
<button class="dl-btn delete" title="삭제" onclick="event.stopPropagation(); deleteDownload('${item.id}')">
|
||||||
@@ -1245,6 +1257,29 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteCompleted() {
|
||||||
|
if (!confirm('완료된 항목만 큐에서 삭제하시겠습니까?')) return;
|
||||||
|
$.ajax({
|
||||||
|
url: '/{{ arg["package_name"] }}/ajax/{{ arg["module_name"] }}/delete_completed',
|
||||||
|
type: 'POST',
|
||||||
|
data: {},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(ret) {
|
||||||
|
if (ret.ret === 'success') {
|
||||||
|
const mem = ret.data?.memory ?? 0;
|
||||||
|
const db = ret.data?.db ?? 0;
|
||||||
|
$.notify(`<strong>완료 항목 삭제 완료 (메모리 ${mem}, DB ${db})</strong>`, {type: 'success'});
|
||||||
|
} else {
|
||||||
|
$.notify('<strong>완료 항목 삭제 실패</strong>', {type: 'warning'});
|
||||||
|
}
|
||||||
|
refreshList(false);
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$.notify('<strong>완료 항목 삭제 실패</strong>', {type: 'warning'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function deleteDownload(id) {
|
function deleteDownload(id) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/{{ arg["package_name"] }}/ajax/{{ arg["module_name"] }}/delete',
|
url: '/{{ arg["package_name"] }}/ajax/{{ arg["module_name"] }}/delete',
|
||||||
|
|||||||
Reference in New Issue
Block a user