mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2026-09-09 20:22:40 -04:00
Compare commits
1
Commits
develop
...
feature/alt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2854c605a6 |
No files matched your search
@@ -91,7 +91,12 @@
|
||||
<td class="name">
|
||||
<div class="row-wrap-text" data-bind="visible: !editingName(), css: { 'direct-unpack-text': direct_unpack }">
|
||||
<!-- ko foreach: labels -->
|
||||
<span class="label label-warning" data-bind="text: \$data" ></span>
|
||||
<!-- ko if: \$parent.duplicate_info().nzo_ids -->
|
||||
<span class="label label-warning" style="cursor: pointer" data-bind="text: \$data, click: \$parent.showDuplicates"></span>
|
||||
<!-- /ko -->
|
||||
<!-- ko ifnot: \$parent.duplicate_info().nzo_ids -->
|
||||
<span class="label label-warning" data-bind="text: \$data"></span>
|
||||
<!-- /ko -->
|
||||
<!-- /ko -->
|
||||
<span data-bind="text: name, attr: { 'title': name_title }"></span>
|
||||
<!-- ko if: password() -->
|
||||
|
||||
@@ -337,7 +337,7 @@ function ViewModel() {
|
||||
limit: parseInt(self.queue.paginationLimit())
|
||||
}
|
||||
if (self.queue.searchTerm()) {
|
||||
parseSearchQuery(api_call, self.queue.searchTerm(), ["cat", "category", "priority", "status"])
|
||||
parseSearchQuery(api_call, self.queue.searchTerm(), ["cat", "category", "priority", "status", "nzo_ids"])
|
||||
}
|
||||
var queueApi = callAPI(api_call)
|
||||
.done(self.updateQueue)
|
||||
@@ -367,7 +367,7 @@ function ViewModel() {
|
||||
last_history_update: self.history.lastUpdate
|
||||
}
|
||||
if (self.history.searchTerm()) {
|
||||
parseSearchQuery(history_call, self.history.searchTerm(), ["cat", "category", "status"])
|
||||
parseSearchQuery(history_call, self.history.searchTerm(), ["cat", "category", "status", "nzo_ids"])
|
||||
}
|
||||
|
||||
// History
|
||||
|
||||
@@ -549,6 +549,7 @@ function QueueModel(parent, data) {
|
||||
self.index = ko.observable(data.index);
|
||||
self.status = ko.observable(data.status);
|
||||
self.labels = ko.observableArray(data.labels);
|
||||
self.duplicate_info = ko.observable(data.duplicate_info || {});
|
||||
self.isGrabbing = ko.observable(data.status === 'Grabbing' || data.avg_age === '-')
|
||||
self.isFetchingBlocks = data.status === 'Fetching' || data.priority === 'Repair' // No need to update
|
||||
self.totalMB = ko.observable(parseFloat(data.mb));
|
||||
@@ -690,6 +691,7 @@ function QueueModel(parent, data) {
|
||||
self.labels(data.labels);
|
||||
self.rawLabels = data.labels.toString();
|
||||
}
|
||||
self.duplicate_info(data.duplicate_info || {});
|
||||
};
|
||||
|
||||
// Pause individual download
|
||||
@@ -772,6 +774,25 @@ function QueueModel(parent, data) {
|
||||
parent.parent.filelist.loadFiles(self)
|
||||
}
|
||||
|
||||
// Filter queue or history by the duplicate's nzo_ids
|
||||
self.showDuplicates = function() {
|
||||
var info = self.duplicate_info();
|
||||
if (!info || !info.nzo_ids || !info.nzo_ids.length) return;
|
||||
var filter = 'nzo_ids:' + info.nzo_ids.join(',');
|
||||
if (info.source === 'history') {
|
||||
// Enable archive view if any matched item is archived
|
||||
if (info.archive && !parent.parent.history.showArchive()) {
|
||||
parent.parent.history.showArchive(true);
|
||||
}
|
||||
// Switch to history tab and set the search filter
|
||||
parent.parent.history.searchTerm(filter);
|
||||
$('.history-queue-swicher .nav-tabs a[href="#history-tab"]').tab('show');
|
||||
} else {
|
||||
// Filter within the queue
|
||||
parent.parent.queue.searchTerm(filter);
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle calculation of dropdown
|
||||
// Turns out that the <select> in the dropdown are a hugggeeee slowdown on initial load!
|
||||
// Only loading on click cuts half the speed (especially on large queues)
|
||||
|
||||
@@ -1642,6 +1642,7 @@ def build_queue(
|
||||
slot["script"] = nzo.script if nzo.script else "None"
|
||||
slot["filename"] = nzo.final_name
|
||||
slot["labels"] = nzo.labels
|
||||
slot["duplicate_info"] = nzo.duplicate_info
|
||||
slot["password"] = nzo.password if nzo.password else ""
|
||||
slot["cat"] = nzo.cat if nzo.cat else "None"
|
||||
slot["mbleft"] = "%.2f" % mbleft
|
||||
|
||||
+17
-19
@@ -373,36 +373,34 @@ class HistoryDB:
|
||||
|
||||
return items, total_items
|
||||
|
||||
def have_duplicate_key(self, duplicate_key: str) -> bool:
|
||||
"""Check whether History contains this duplicate key"""
|
||||
def have_duplicate_key(self, duplicate_key: str) -> list[dict]:
|
||||
"""Check whether History contains this duplicate key.
|
||||
Returns list of dicts with nzo_id and archive flag, empty list if none found."""
|
||||
if self.execute(
|
||||
"""
|
||||
SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM history
|
||||
WHERE duplicate_key = ? AND status != ?
|
||||
) as found
|
||||
SELECT nzo_id, archive
|
||||
FROM history
|
||||
WHERE duplicate_key = ? AND status != ?
|
||||
""",
|
||||
(duplicate_key, Status.FAILED),
|
||||
):
|
||||
return bool(self.cursor.fetchone()["found"])
|
||||
return False
|
||||
return [{"nzo_id": row["nzo_id"], "archive": bool(row["archive"])} for row in self.cursor.fetchall() if row["nzo_id"]]
|
||||
return []
|
||||
|
||||
def have_name_or_md5sum(self, name: str, md5sum: str) -> bool:
|
||||
"""Check whether this name or md5sum is already in History"""
|
||||
def have_name_or_md5sum(self, name: str, md5sum: str) -> list[dict]:
|
||||
"""Check whether this name or md5sum is already in History.
|
||||
Returns list of dicts with nzo_id and archive flag, empty list if none found."""
|
||||
if self.execute(
|
||||
"""
|
||||
SELECT EXISTS(
|
||||
SELECT 1
|
||||
FROM history
|
||||
WHERE (name = ? COLLATE NOCASE OR md5sum = ?)
|
||||
AND status != ?
|
||||
) as found
|
||||
SELECT nzo_id, archive
|
||||
FROM history
|
||||
WHERE (name = ? COLLATE NOCASE OR md5sum = ?)
|
||||
AND status != ?
|
||||
""",
|
||||
(name, md5sum, Status.FAILED),
|
||||
):
|
||||
return bool(self.cursor.fetchone()["found"])
|
||||
return False
|
||||
return [{"nzo_id": row["nzo_id"], "archive": bool(row["archive"])} for row in self.cursor.fetchall() if row["nzo_id"]]
|
||||
return []
|
||||
|
||||
def get_history_size(self) -> tuple[int, int, int]:
|
||||
"""Returns the total size of the history and
|
||||
|
||||
+27
-3
@@ -161,6 +161,7 @@ NzbObjectSaver = (
|
||||
"bad_articles",
|
||||
"duplicate",
|
||||
"duplicate_key",
|
||||
"duplicate_of",
|
||||
"oversized",
|
||||
"precheck",
|
||||
"incomplete",
|
||||
@@ -290,6 +291,7 @@ class NzbObject(TryList):
|
||||
|
||||
self.duplicate: Optional[str] = None
|
||||
self.duplicate_key: Optional[str] = None
|
||||
self.duplicate_of: Optional[dict] = None
|
||||
|
||||
self.futuretype = futuretype
|
||||
self.removed_from_queue = False
|
||||
@@ -992,6 +994,13 @@ class NzbObject(TryList):
|
||||
|
||||
return labels
|
||||
|
||||
@property
|
||||
def duplicate_info(self) -> dict:
|
||||
"""Return info about the duplicate source for the API"""
|
||||
if not self.duplicate or not self.duplicate_of:
|
||||
return {}
|
||||
return self.duplicate_of
|
||||
|
||||
@property
|
||||
def final_name_with_password(self):
|
||||
if self.password:
|
||||
@@ -1553,6 +1562,12 @@ class NzbObject(TryList):
|
||||
|
||||
self.duplicate_key = "/".join(duplicate_key_items).lower()
|
||||
|
||||
def _set_duplicate_of_from_history(self, history_results: list[dict]):
|
||||
"""Build duplicate_of dict from history query results (list of {nzo_id, archive})"""
|
||||
nzo_ids = [item["nzo_id"] for item in history_results]
|
||||
has_archive = any(item["archive"] for item in history_results)
|
||||
self.duplicate_of = {"nzo_ids": nzo_ids, "source": "history", "archive": has_archive}
|
||||
|
||||
def duplicate_check(self, repeat: bool = False):
|
||||
"""Set the correct duplicate status"""
|
||||
if not cfg.no_dupes() and not cfg.no_smart_dupes():
|
||||
@@ -1562,9 +1577,10 @@ class NzbObject(TryList):
|
||||
if repeat:
|
||||
self.duplicate = None
|
||||
self.duplicate_key = None
|
||||
self.duplicate_of = None
|
||||
|
||||
duplicate_in_history = smart_duplicate_in_history = False
|
||||
duplicate_in_queue = smart_duplicate_in_queue = False
|
||||
duplicate_in_history = smart_duplicate_in_history = None
|
||||
duplicate_in_queue = smart_duplicate_in_queue = None
|
||||
|
||||
with HistoryDB() as history_db:
|
||||
# Dupe check off just name or nzb contents
|
||||
@@ -1595,15 +1611,23 @@ class NzbObject(TryList):
|
||||
else:
|
||||
logging.debug("Unknown type, skipping smart duplicate check")
|
||||
|
||||
# Set the correct status
|
||||
# Set the correct status and store the nzo_ids of the matched items
|
||||
if smart_duplicate_in_queue:
|
||||
self.duplicate = DuplicateStatus.SMART_DUPLICATE_ALTERNATIVE
|
||||
self.duplicate_of = {"nzo_ids": smart_duplicate_in_queue, "source": "queue"}
|
||||
elif duplicate_in_queue:
|
||||
self.duplicate = DuplicateStatus.DUPLICATE_ALTERNATIVE
|
||||
self.duplicate_of = {"nzo_ids": duplicate_in_queue, "source": "queue"}
|
||||
elif smart_duplicate_in_history:
|
||||
self.duplicate = DuplicateStatus.SMART_DUPLICATE
|
||||
self._set_duplicate_of_from_history(smart_duplicate_in_history)
|
||||
elif duplicate_in_history:
|
||||
self.duplicate = DuplicateStatus.DUPLICATE
|
||||
# backup_exists returns bool, not a list of dicts
|
||||
if isinstance(duplicate_in_history, list):
|
||||
self._set_duplicate_of_from_history(duplicate_in_history)
|
||||
else:
|
||||
self.duplicate_of = None
|
||||
|
||||
def handle_duplicate_action(self):
|
||||
"""Handle duplicate detection action"""
|
||||
|
||||
+13
-8
@@ -959,28 +959,32 @@ class NzbQueue:
|
||||
return lst
|
||||
|
||||
@NzbQueueLocker
|
||||
def have_name_or_md5sum(self, name: str, md5sum: str) -> bool:
|
||||
def have_name_or_md5sum(self, name: str, md5sum: str) -> list[str]:
|
||||
"""Check whether this name or md5sum is already
|
||||
in the queue or the post-processing queue"""
|
||||
in the queue or the post-processing queue.
|
||||
Returns list of matching nzo_ids, empty list if none found."""
|
||||
lname = name.lower()
|
||||
nzo_ids = []
|
||||
for nzo in self.__nzo_list + sabnzbd.PostProcessor.get_queue():
|
||||
# Skip any jobs already marked as duplicate, to prevent double-triggers
|
||||
# URL's do not have an MD5!
|
||||
if not nzo.duplicate and (
|
||||
nzo.final_name.lower() == lname or (nzo.md5sum and md5sum and nzo.md5sum == md5sum)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
nzo_ids.append(nzo.nzo_id)
|
||||
return nzo_ids
|
||||
|
||||
@NzbQueueLocker
|
||||
def have_duplicate_key(self, duplicate_key: str) -> bool:
|
||||
def have_duplicate_key(self, duplicate_key: str) -> list[str]:
|
||||
"""Check whether this duplicate key is already
|
||||
in the queue or the post-processing queue"""
|
||||
in the queue or the post-processing queue.
|
||||
Returns list of matching nzo_ids, empty list if none found."""
|
||||
nzo_ids = []
|
||||
for nzo in self.__nzo_list + sabnzbd.PostProcessor.get_queue():
|
||||
# Skip any jobs already marked as duplicate, to prevent double-triggers
|
||||
if not nzo.duplicate and nzo.duplicate_key == duplicate_key:
|
||||
return True
|
||||
return False
|
||||
nzo_ids.append(nzo.nzo_id)
|
||||
return nzo_ids
|
||||
|
||||
@NzbQueueLocker
|
||||
def handle_duplicate_alternatives(self, finished_nzo: NzbObject, success: bool):
|
||||
@@ -1008,6 +1012,7 @@ class NzbQueue:
|
||||
logging.info("Resuming duplicate alternative %s for ", nzo.final_name, finished_nzo.final_name)
|
||||
nzo.resume()
|
||||
nzo.duplicate = None
|
||||
nzo.duplicate_of = None
|
||||
return
|
||||
|
||||
# Take action on the alternatives to the duplicate
|
||||
|
||||
Reference in new issue
Block a user