-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathqueue_manager.py
More file actions
111 lines (92 loc) · 3.92 KB
/
Copy pathqueue_manager.py
File metadata and controls
111 lines (92 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"""
queue_manager.py
-----------------
Owns the list of DownloadItems and the operations on it: add, remove,
reorder, clear, retry, duplicate detection, and persistence to disk. No Tk
dependency - the UI renders `queue_manager.items` and calls these methods;
the download manager calls `claim_next()` to pull work.
"""
import threading
import downloader
import queue_store
from models import DownloadItem, Status
class QueueManager:
def __init__(self):
self.items: list[DownloadItem] = []
self._lock = threading.Lock()
# -- adding / finding ------------------------------------------------
def add(self, item: DownloadItem) -> None:
self.items.append(item)
def find_duplicate(self, url: str) -> DownloadItem | None:
"""Find an existing item pointing at the same video, ignoring
tracking query parameters. Cancelled/Failed items don't count,
since the user has already indicated those didn't succeed.
"""
normalized = downloader.normalize_url(url)
for item in self.items:
if item.status in (Status.CANCELLED, Status.FAILED):
continue
if downloader.normalize_url(item.url) == normalized:
return item
return None
# -- removing / reordering -------------------------------------------
def remove(self, item: DownloadItem) -> None:
if item in self.items:
self.items.remove(item)
def move(self, item: DownloadItem, direction: int) -> bool:
"""Swap `item` with its neighbour in `direction` (-1 up, +1 down).
Returns False if the move isn't possible (already at an edge).
"""
index = self.items.index(item)
new_index = index + direction
if not (0 <= new_index < len(self.items)):
return False
self.items[index], self.items[new_index] = self.items[new_index], self.items[index]
return True
def clear_completed(self) -> None:
self.items = [item for item in self.items if item.status != Status.COMPLETED]
def clear_failed(self) -> None:
self.items = [item for item in self.items if item.status != Status.FAILED]
# -- retrying ----------------------------------------------------------
@staticmethod
def retry(item: DownloadItem) -> None:
item.status = Status.QUEUED
item.error = None
item.error_detail = None
def retry_all_failed(self) -> int:
count = 0
for item in self.items:
if item.status == Status.FAILED:
self.retry(item)
count += 1
return count
# -- stats / claiming work for the download manager --------------------
def stats(self) -> dict:
counts = {status: 0 for status in (
Status.QUEUED, Status.DOWNLOADING, Status.PAUSED,
Status.COMPLETED, Status.FAILED, Status.CANCELLED,
)}
for item in self.items:
counts[item.status] = counts.get(item.status, 0) + 1
return counts
def has_runnable(self) -> bool:
return any(item.status in Status.RUNNABLE for item in self.items)
def claim_next(self) -> DownloadItem | None:
"""Thread-safely pick the next runnable item and mark it Downloading
immediately, so two workers can never both claim the same item.
"""
with self._lock:
for item in self.items:
if item.status in Status.RUNNABLE:
item.status = Status.DOWNLOADING
return item
return None
# -- persistence ---------------------------------------------------------
def persist(self) -> None:
queue_store.save_queue(self.items)
def load_persisted(self) -> list[DownloadItem]:
return queue_store.load_queue()
def discard_persisted(self) -> None:
queue_store.clear_queue_file()
def replace_with(self, items: list[DownloadItem]) -> None:
self.items = items