mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2026-02-25 11:07:24 -05:00
Compare commits
9 Commits
4.5.4Beta2
...
bugfix/cle
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efe17ca3bb | ||
|
|
d4995e3120 | ||
|
|
90989b374a | ||
|
|
fb2d412c97 | ||
|
|
1c0b1205b2 | ||
|
|
f556cea488 | ||
|
|
a2447253a0 | ||
|
|
3393d7c976 | ||
|
|
06572bdf7d |
2
.github/workflows/integration_testing.yml
vendored
2
.github/workflows/integration_testing.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14.0-rc.3"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
name: ["Linux"]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
|
||||
@@ -465,13 +465,10 @@
|
||||
**/
|
||||
jQuery(document).ready(function(){
|
||||
// Reload form in case we change items that make the servers appear different
|
||||
jQuery('input[name="priority"], input[name="displayname"], textarea[name="notes"]').on('change', function() {
|
||||
jQuery('.fullform').submit(function() {
|
||||
// No ajax this time
|
||||
jQuery('input[name="ajax"]').val('')
|
||||
// Skip the fancy stuff, just submit
|
||||
this.submit()
|
||||
})
|
||||
jQuery('input[name="priority"], input[name="displayname"], textarea[name="notes"]').on('change', function(event) {
|
||||
var parentForm = jQuery(event.target).parents("form")
|
||||
parentForm.unbind("submit")
|
||||
parentForm.find('input[name="ajax"]').val('')
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -298,6 +298,19 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr ""
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py, sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr ""
|
||||
@@ -3302,10 +3315,6 @@ msgstr ""
|
||||
msgid "Naming"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr ""
|
||||
@@ -3403,7 +3412,7 @@ msgid "Warn 5 days in advance of account expiration date."
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota for this account, counted from the time it is set. In bytes, optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few minutes."
|
||||
msgid "Quota for this server, counted from the time it is set. In bytes, optionally follow with K,M,G.<br />Checked every few minutes. Notification is sent when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -335,6 +335,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Kvóta přesažena, pozastavuji stahování"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Nesprávný parametr"
|
||||
@@ -3474,10 +3488,6 @@ msgstr ""
|
||||
msgid "Naming"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr ""
|
||||
@@ -3580,9 +3590,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -352,6 +352,20 @@ msgstr "Job \"%s\" er sandsynligvis krypteret: \"password\" i filnavnet \"%s\""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Kvote brugt, pause downloading"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Kvota"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Fejl parameter"
|
||||
@@ -3601,10 +3615,6 @@ msgstr "Efterbehandling"
|
||||
msgid "Naming"
|
||||
msgstr "Navngivning"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Kvota"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Hvor meget der kan downloades i denne måned (K/M/G)"
|
||||
@@ -3712,9 +3722,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -376,6 +376,20 @@ msgstr "Aufgabe \"%s\" ist wahrscheinlich verschlüsselt: \"Passwort\" im Datein
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Kontingent aufgebraucht, Downloads werden angehalten"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Kontingent"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Fehlerhafter Parameter"
|
||||
@@ -3730,10 +3744,6 @@ msgstr "Nachbearbeitung"
|
||||
msgid "Naming"
|
||||
msgstr "Benennung"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Kontingent"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Wie viel kann in diesem Monat heruntergeladen werden (K/M/G)?"
|
||||
@@ -3847,13 +3857,10 @@ msgstr "5 Tage vor dem Ablauf des Accounts warnen."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
"Kontingent für dieses Konto, gezählt ab dem Zeitpunkt, an dem es festgelegt "
|
||||
"wird. In Bytes, optional gefolgt von K, M, G.<br />Warne, wenn es 0 "
|
||||
"erreicht, wird alle paar Minuten überprüft."
|
||||
|
||||
#. Server's retention time in days
|
||||
#: sabnzbd/skintext.py
|
||||
|
||||
@@ -365,6 +365,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Quota gastado, pausando cola"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Cuota"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Parámetro incorrecto"
|
||||
@@ -3704,10 +3718,6 @@ msgstr "Post procesado"
|
||||
msgid "Naming"
|
||||
msgstr "Nombrado"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Cuota"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Cantidad de descarga permitida este mes (K/M/G)"
|
||||
@@ -3817,9 +3827,9 @@ msgstr "Advertir 5 días antes de la fecha de expiración de la cuenta."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -334,6 +334,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Latausrajoitus saavutettu, keskeytetään lataukset"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Latausrajoitus"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Virheellinen parametri"
|
||||
@@ -3562,10 +3576,6 @@ msgstr "Jälkikäsittely"
|
||||
msgid "Naming"
|
||||
msgstr "Nimeäminen"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Latausrajoitus"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Kuinka paljon voidaan ladata tässä kuussa (K/M/G)"
|
||||
@@ -3671,9 +3681,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -370,6 +370,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Quota atteint, téléchargement mis en pause"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Quota"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Paramètre incorrect"
|
||||
@@ -3714,10 +3728,6 @@ msgstr "Post-traitement"
|
||||
msgid "Naming"
|
||||
msgstr "Appellation"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Quota"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Combien peut-être télécharger ce mois (K/M/G)"
|
||||
@@ -3828,13 +3838,10 @@ msgstr "Avertir 5 jours avant la date d'expiration du compte."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
"Quota pour ce compte calculé à partir du moment où il est défini. En octets,"
|
||||
" éventuellement suivi de K,M,G.<br />Avertir quand il atteint 0, vérifié "
|
||||
"toutes les quelques minutes."
|
||||
|
||||
#. Server's retention time in days
|
||||
#: sabnzbd/skintext.py
|
||||
|
||||
@@ -335,6 +335,20 @@ msgstr "העבודה \"%s\" כנראה מוצפנת: \"סיסמה\" בשם הק
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "מכסה נוצלה, משהה הורדה"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "מכסה"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "פרמטר שגוי"
|
||||
@@ -3566,10 +3580,6 @@ msgstr "בתר־עיבוד"
|
||||
msgid "Naming"
|
||||
msgstr "מתן שמות"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "מכסה"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "כמה ניתן להוריד החודש (ק״ב/מ״ב/ג״ב)"
|
||||
@@ -3674,12 +3684,10 @@ msgstr "הזהר 5 ימים טרם תאריך תפוגת החשבון."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
"מכסה עבור חשבון זה, נספרת מהזמן שהיא הוגדרה. בבתים, יכולה לבוא עם K,M,G.<br "
|
||||
"/>הזהר כאשר המכסה מגיעה אל 0, היא נבדקת כל כמה דקות."
|
||||
|
||||
#. Server's retention time in days
|
||||
#: sabnzbd/skintext.py
|
||||
|
||||
@@ -361,6 +361,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Quota esaurita, download in pausa"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Quota"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Parametro non corretto"
|
||||
@@ -3673,10 +3687,6 @@ msgstr "Post-elaborazione"
|
||||
msgid "Naming"
|
||||
msgstr "Denominazione"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Quota"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Quanto può essere scaricato questo mese (K/M/G)"
|
||||
@@ -3786,13 +3796,10 @@ msgstr "Avvisa 5 giorni prima della data di scadenza dell'account."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
"Quota per questo account, contata dal momento in cui è impostata. In byte, "
|
||||
"opzionalmente seguito da K,M,G.<br />Avvisa quando raggiunge 0, controllato "
|
||||
"ogni pochi minuti."
|
||||
|
||||
#. Server's retention time in days
|
||||
#: sabnzbd/skintext.py
|
||||
|
||||
@@ -332,6 +332,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Kvote oppbrukt, setter nedlasting på pause"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Kvote"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Feil parameter"
|
||||
@@ -3539,10 +3553,6 @@ msgstr "Postprosessering"
|
||||
msgid "Naming"
|
||||
msgstr "Filnavn"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Kvote"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Hvor mye can lastes ned denne måneden (K/M/G)"
|
||||
@@ -3650,9 +3660,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -358,6 +358,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Quotum verbruikt, download is gestopt"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Quotum"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Incorrecte parameter"
|
||||
@@ -3673,10 +3687,6 @@ msgstr "Nabewerking"
|
||||
msgid "Naming"
|
||||
msgstr "Naamgeving"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Quotum"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Hoeval mag deze maand worden gedownload (K/M/G)"
|
||||
@@ -3788,14 +3798,10 @@ msgstr "Ontvang 5 dagen voor de verloopdatum een waarschuwing."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
"Quotum voor dit account, wordt geteld vanaf het moment dat het voor het "
|
||||
"eerst ingesteld wordt. In bytes, in K,M,G notatie.<br />Er wordt een "
|
||||
"waarschuwing gegeven als het quotum bereikt is, dit wordt elke paar minuten "
|
||||
"gecontroleerd."
|
||||
|
||||
#. Server's retention time in days
|
||||
#: sabnzbd/skintext.py
|
||||
|
||||
@@ -331,6 +331,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Przekroczono limit, wstrzymywanie pobierania"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Limit pobierania"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Błędny parametr"
|
||||
@@ -3550,10 +3564,6 @@ msgstr "Przetwarzanie końcowe"
|
||||
msgid "Naming"
|
||||
msgstr "Nazwy"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Limit pobierania"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Ile danych można pobrać w miesiącu (K/M/G)"
|
||||
@@ -3662,9 +3672,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -343,6 +343,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Quota esgotada, pausando o download"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Quota"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Parâmetro incorreto"
|
||||
@@ -3562,10 +3576,6 @@ msgstr "Pós-processamento"
|
||||
msgid "Naming"
|
||||
msgstr "Nomeando"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Quota"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Quanto pode ser baixado neste mês (K/M/G)"
|
||||
@@ -3673,9 +3683,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -347,6 +347,20 @@ msgstr "Sarcina „%s” este probabil criptată: „parolă” în fișierul
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Cotă epuizată, întrerupem descărcarea"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Cotă"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Parametru Incorect"
|
||||
@@ -3581,10 +3595,6 @@ msgstr "Post procesare"
|
||||
msgid "Naming"
|
||||
msgstr "Redenumire"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Cotă"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Cât de mult poate fi descărcat în acestă lună (K/M/G)"
|
||||
@@ -3693,9 +3703,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -331,6 +331,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Квота исчерпана. Загрузка приостановлена"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Квота"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Неправильный параметр"
|
||||
@@ -3541,10 +3555,6 @@ msgstr "Пост-обработка"
|
||||
msgid "Naming"
|
||||
msgstr "Именование"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Квота"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Объем, который можно загрузить в месяц (K/M/G)"
|
||||
@@ -3651,9 +3661,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -328,6 +328,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Kvota utrošena, pauziram preuzimanja"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Квота"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Погрешан параметар"
|
||||
@@ -3526,10 +3540,6 @@ msgstr "Накнадна обрада"
|
||||
msgid "Naming"
|
||||
msgstr "Именовање"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Квота"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Колико може да се преузме овог месеца (К/М/Г)"
|
||||
@@ -3637,9 +3647,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -328,6 +328,20 @@ msgstr ""
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Din kvot är uppnådd, pausar nerladdning"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Kvot"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Fel parameter"
|
||||
@@ -3538,10 +3552,6 @@ msgstr "Efterbehandling"
|
||||
msgid "Naming"
|
||||
msgstr "Döpning"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Kvot"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Hur mycket kan laddas ner denna månad (K/M/G)"
|
||||
@@ -3649,9 +3659,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -362,6 +362,20 @@ msgstr "\"%s\" işi muhtemelen şifrelenmiştir: \"parola\", \"%s\" dosya ismind
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "Kota kullanıldı, indirme duraklatılıyor"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Kota"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "Yanlış parametre"
|
||||
@@ -3661,10 +3675,6 @@ msgstr "Post processing"
|
||||
msgid "Naming"
|
||||
msgstr "İsimlendirme"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "Kota"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "Bu ay ne kadar indirme yapılabileceği (K/M/G)"
|
||||
@@ -3772,13 +3782,10 @@ msgstr "Sonlanma tarihinden 5 gün evvel ikaz et."
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
"Bu hesap için kota, bu seçeneğin ayarlanmasından itibaren hesaplanır. Bayt "
|
||||
"olarak, seçime dayalı bir şekilde K,M,G takip edebilir.<br />0 değerine "
|
||||
"ulaştığında ikazda bulun, her birkaç dakikada bir kontrol edilir."
|
||||
|
||||
#. Server's retention time in days
|
||||
#: sabnzbd/skintext.py
|
||||
|
||||
@@ -327,6 +327,20 @@ msgstr "任务 \"%s\" 可能受加密保护:文件名 \"%s\" 中有 \"password
|
||||
msgid "Quota spent, pausing downloading"
|
||||
msgstr "配额已耗尽,暂停下载"
|
||||
|
||||
#. Warning message - Notification
|
||||
#: sabnzbd/bpsmeter.py, sabnzbd/downloader.py, sabnzbd/notifier.py,
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "配额"
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Quota limit warning (%d%%)"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/bpsmeter.py
|
||||
msgid "Downloading resumed after quota reset"
|
||||
msgstr ""
|
||||
|
||||
#: sabnzbd/cfg.py, sabnzbd/interface.py
|
||||
msgid "Incorrect parameter"
|
||||
msgstr "参数不正确"
|
||||
@@ -3486,10 +3500,6 @@ msgstr "后期处理"
|
||||
msgid "Naming"
|
||||
msgstr "命名"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "Quota"
|
||||
msgstr "配额"
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid "How much can be downloaded this month (K/M/G)"
|
||||
msgstr "本月能下载多少数据量 (K/M/G)"
|
||||
@@ -3592,9 +3602,9 @@ msgstr ""
|
||||
|
||||
#: sabnzbd/skintext.py
|
||||
msgid ""
|
||||
"Quota for this account, counted from the time it is set. In bytes, "
|
||||
"optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few"
|
||||
" minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally"
|
||||
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
|
||||
"when quota is spent."
|
||||
msgstr ""
|
||||
|
||||
#. Server's retention time in days
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Main requirements
|
||||
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
|
||||
apprise==1.9.4
|
||||
apprise==1.9.5
|
||||
sabctools==8.2.6
|
||||
CT3==3.4.0
|
||||
cffi==2.0.0
|
||||
@@ -32,7 +32,7 @@ rebulk==3.2.0
|
||||
|
||||
# Recent cryptography versions require Rust. If you run into issues compiling this
|
||||
# SABnzbd will also work with older pre-Rust versions such as cryptography==3.3.2
|
||||
cryptography==46.0.1
|
||||
cryptography==46.0.2
|
||||
|
||||
# We recommend using "orjson" as it is 2x as fast as "ujson". However, it requires
|
||||
# Rust so SABnzbd works just as well with "ujson" or the Python built in "json" module
|
||||
@@ -67,7 +67,7 @@ paho-mqtt==1.6.1 # Pinned, newer versions don't work with AppRise yet
|
||||
charset_normalizer==3.4.3
|
||||
idna==3.10
|
||||
urllib3==2.5.0
|
||||
certifi==2025.8.3
|
||||
certifi==2025.10.5
|
||||
oauthlib==3.3.1
|
||||
PyJWT==2.10.1
|
||||
blinker==1.9.0
|
||||
|
||||
@@ -122,6 +122,7 @@ class BPSMeter:
|
||||
"q_hour",
|
||||
"q_minute",
|
||||
"quota_enabled",
|
||||
"quota_notifications_sent",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
@@ -161,6 +162,7 @@ class BPSMeter:
|
||||
self.q_hour = 0 # Quota reset hour
|
||||
self.q_minute = 0 # Quota reset minute
|
||||
self.quota_enabled: bool = True # Scheduled quota enable/disable
|
||||
self.quota_notifications_sent: int = 0 # Track highest quota threshold that has been notified
|
||||
|
||||
def save(self):
|
||||
"""Save admin to disk"""
|
||||
@@ -323,10 +325,7 @@ class BPSMeter:
|
||||
# Quota check
|
||||
if self.have_quota and self.quota_enabled:
|
||||
self.left -= self.sum_cached_amount
|
||||
if self.left <= 0.0:
|
||||
if not sabnzbd.Downloader.paused:
|
||||
sabnzbd.Downloader.pause()
|
||||
logging.warning(T("Quota spent, pausing downloading"))
|
||||
self.check_quota()
|
||||
|
||||
# Speedometer
|
||||
try:
|
||||
@@ -431,15 +430,47 @@ class BPSMeter:
|
||||
# We record every second, but display at the user's refresh-rate
|
||||
return self.bps_list[::refresh_rate]
|
||||
|
||||
def check_quota(self):
|
||||
"""Pause the queue when all quota is spent
|
||||
Notify at specific quota usages (75%, 90%, 100%)
|
||||
"""
|
||||
if self.left <= 0.0:
|
||||
if not sabnzbd.Downloader.paused:
|
||||
sabnzbd.Downloader.pause()
|
||||
logging.warning(T("Quota spent, pausing downloading"))
|
||||
|
||||
# Guard against zero division
|
||||
if self.quota:
|
||||
# Check for quota notifications (75%, 90%, 100%)
|
||||
# Only send notification for the highest applicable threshold that hasn't been notified yet
|
||||
used_percentage = ((self.quota - self.left) / self.quota) * 100
|
||||
if used_percentage >= 100 and self.quota_notifications_sent < 100:
|
||||
sabnzbd.notifier.send_notification(T("Quota"), T("Quota spent, pausing downloading"), "quota")
|
||||
elif used_percentage >= 90 and self.quota_notifications_sent < 90:
|
||||
sabnzbd.notifier.send_notification(
|
||||
T("Quota"),
|
||||
T("Quota limit warning (%d%%)") % used_percentage,
|
||||
"quota",
|
||||
)
|
||||
elif used_percentage >= 75 and self.quota_notifications_sent < 75:
|
||||
sabnzbd.notifier.send_notification(
|
||||
T("Quota"),
|
||||
T("Quota limit warning (%d%%)") % used_percentage,
|
||||
"quota",
|
||||
)
|
||||
self.quota_notifications_sent = used_percentage
|
||||
|
||||
def reset_quota(self, force: bool = False):
|
||||
"""Check if it's time to reset the quota, optionally resuming
|
||||
Return True, when still paused or should be paused
|
||||
"""
|
||||
if force or (self.have_quota and time.time() > (self.q_time - 50)):
|
||||
self.quota = self.left = cfg.quota_size.get_float()
|
||||
self.quota_notifications_sent = 0
|
||||
logging.info("Quota was reset to %s", self.quota)
|
||||
if cfg.quota_resume():
|
||||
logging.info("Auto-resume due to quota reset")
|
||||
sabnzbd.notifier.send_notification(T("Quota"), T("Downloading resumed after quota reset"), "quota")
|
||||
sabnzbd.Downloader.resume()
|
||||
self.next_reset()
|
||||
return False
|
||||
|
||||
@@ -559,6 +559,7 @@ ncenter_prio_pp = OptionBool("ncenter", "ncenter_prio_pp", False)
|
||||
ncenter_prio_complete = OptionBool("ncenter", "ncenter_prio_complete", True)
|
||||
ncenter_prio_failed = OptionBool("ncenter", "ncenter_prio_failed", True)
|
||||
ncenter_prio_disk_full = OptionBool("ncenter", "ncenter_prio_disk_full", True)
|
||||
ncenter_prio_quota = OptionBool("ncenter", "ncenter_prio_quota", True)
|
||||
ncenter_prio_new_login = OptionBool("ncenter", "ncenter_prio_new_login", False)
|
||||
ncenter_prio_warning = OptionBool("ncenter", "ncenter_prio_warning", False)
|
||||
ncenter_prio_error = OptionBool("ncenter", "ncenter_prio_error", False)
|
||||
@@ -575,6 +576,7 @@ acenter_prio_pp = OptionBool("acenter", "acenter_prio_pp", False)
|
||||
acenter_prio_complete = OptionBool("acenter", "acenter_prio_complete", True)
|
||||
acenter_prio_failed = OptionBool("acenter", "acenter_prio_failed", True)
|
||||
acenter_prio_disk_full = OptionBool("acenter", "acenter_prio_disk_full", True)
|
||||
acenter_prio_quota = OptionBool("acenter", "acenter_prio_quota", True)
|
||||
acenter_prio_new_login = OptionBool("acenter", "acenter_prio_new_login", False)
|
||||
acenter_prio_warning = OptionBool("acenter", "acenter_prio_warning", False)
|
||||
acenter_prio_error = OptionBool("acenter", "acenter_prio_error", False)
|
||||
@@ -591,6 +593,7 @@ ntfosd_prio_pp = OptionBool("ntfosd", "ntfosd_prio_pp", False)
|
||||
ntfosd_prio_complete = OptionBool("ntfosd", "ntfosd_prio_complete", True)
|
||||
ntfosd_prio_failed = OptionBool("ntfosd", "ntfosd_prio_failed", True)
|
||||
ntfosd_prio_disk_full = OptionBool("ntfosd", "ntfosd_prio_disk_full", True)
|
||||
ntfosd_prio_quota = OptionBool("ntfosd", "ntfosd_prio_quota", True)
|
||||
ntfosd_prio_new_login = OptionBool("ntfosd", "ntfosd_prio_new_login", False)
|
||||
ntfosd_prio_warning = OptionBool("ntfosd", "ntfosd_prio_warning", False)
|
||||
ntfosd_prio_error = OptionBool("ntfosd", "ntfosd_prio_error", False)
|
||||
@@ -608,6 +611,7 @@ prowl_prio_pp = OptionNumber("prowl", "prowl_prio_pp", -3)
|
||||
prowl_prio_complete = OptionNumber("prowl", "prowl_prio_complete", 0)
|
||||
prowl_prio_failed = OptionNumber("prowl", "prowl_prio_failed", 1)
|
||||
prowl_prio_disk_full = OptionNumber("prowl", "prowl_prio_disk_full", 1)
|
||||
prowl_prio_quota = OptionNumber("prowl", "prowl_prio_quota", 0)
|
||||
prowl_prio_new_login = OptionNumber("prowl", "prowl_prio_new_login", -3)
|
||||
prowl_prio_warning = OptionNumber("prowl", "prowl_prio_warning", -3)
|
||||
prowl_prio_error = OptionNumber("prowl", "prowl_prio_error", -3)
|
||||
@@ -629,6 +633,7 @@ pushover_prio_pp = OptionNumber("pushover", "pushover_prio_pp", -3)
|
||||
pushover_prio_complete = OptionNumber("pushover", "pushover_prio_complete", -1)
|
||||
pushover_prio_failed = OptionNumber("pushover", "pushover_prio_failed", -1)
|
||||
pushover_prio_disk_full = OptionNumber("pushover", "pushover_prio_disk_full", 1)
|
||||
pushover_prio_quota = OptionNumber("pushover", "pushover_prio_quota", -1)
|
||||
pushover_prio_new_login = OptionNumber("pushover", "pushover_prio_new_login", -3)
|
||||
pushover_prio_warning = OptionNumber("pushover", "pushover_prio_warning", 1)
|
||||
pushover_prio_error = OptionNumber("pushover", "pushover_prio_error", 1)
|
||||
@@ -647,6 +652,7 @@ pushbullet_prio_pp = OptionBool("pushbullet", "pushbullet_prio_pp", False)
|
||||
pushbullet_prio_complete = OptionBool("pushbullet", "pushbullet_prio_complete", True)
|
||||
pushbullet_prio_failed = OptionBool("pushbullet", "pushbullet_prio_failed", True)
|
||||
pushbullet_prio_disk_full = OptionBool("pushbullet", "pushbullet_prio_disk_full", True)
|
||||
pushbullet_prio_quota = OptionBool("pushbullet", "pushbullet_prio_quota", True)
|
||||
pushbullet_prio_new_login = OptionBool("pushbullet", "pushbullet_prio_new_login", False)
|
||||
pushbullet_prio_warning = OptionBool("pushbullet", "pushbullet_prio_warning", False)
|
||||
pushbullet_prio_error = OptionBool("pushbullet", "pushbullet_prio_error", False)
|
||||
@@ -671,6 +677,8 @@ apprise_target_failed = OptionStr("apprise", "apprise_target_failed")
|
||||
apprise_target_failed_enable = OptionBool("apprise", "apprise_target_failed_enable", True)
|
||||
apprise_target_disk_full = OptionStr("apprise", "apprise_target_disk_full")
|
||||
apprise_target_disk_full_enable = OptionBool("apprise", "apprise_target_disk_full_enable", False)
|
||||
apprise_target_quota = OptionStr("apprise", "apprise_target_quota")
|
||||
apprise_target_quota_enable = OptionBool("apprise", "apprise_target_quota_enable", True)
|
||||
apprise_target_new_login = OptionStr("apprise", "apprise_target_new_login")
|
||||
apprise_target_new_login_enable = OptionBool("apprise", "apprise_target_new_login_enable", True)
|
||||
apprise_target_warning = OptionStr("apprise", "apprise_target_warning")
|
||||
@@ -694,6 +702,7 @@ nscript_prio_pp = OptionBool("nscript", "nscript_prio_pp", False)
|
||||
nscript_prio_complete = OptionBool("nscript", "nscript_prio_complete", True)
|
||||
nscript_prio_failed = OptionBool("nscript", "nscript_prio_failed", True)
|
||||
nscript_prio_disk_full = OptionBool("nscript", "nscript_prio_disk_full", True)
|
||||
nscript_prio_quota = OptionBool("nscript", "nscript_prio_quota", True)
|
||||
nscript_prio_new_login = OptionBool("nscript", "nscript_prio_new_login", False)
|
||||
nscript_prio_warning = OptionBool("nscript", "nscript_prio_warning", False)
|
||||
nscript_prio_error = OptionBool("nscript", "nscript_prio_error", False)
|
||||
|
||||
@@ -1143,6 +1143,11 @@ def check_server_quota():
|
||||
if server.quota():
|
||||
if server.quota.get_int() + server.usage_at_start() < sabnzbd.BPSMeter.grand_total.get(srv, 0):
|
||||
logging.warning(T("Server %s has used the specified quota"), server.displayname())
|
||||
sabnzbd.notifier.send_notification(
|
||||
T("Quota"),
|
||||
T("Server %s has used the specified quota") % server.displayname(),
|
||||
"quota",
|
||||
)
|
||||
server.quota.set("")
|
||||
config.save_config()
|
||||
|
||||
|
||||
@@ -73,9 +73,11 @@ def addresslookup6(myhost):
|
||||
|
||||
|
||||
def active_socks5_proxy() -> Optional[str]:
|
||||
"""Return the active proxy"""
|
||||
if socket.socket == socks.socksocket:
|
||||
return "%s:%s" % socks.socksocket.default_proxy[1:3]
|
||||
"""Return the active proxy. And None if no proxy is set"""
|
||||
if socks.socksocket.default_proxy:
|
||||
socks5host = socks.socksocket.default_proxy[1]
|
||||
socks5port = sabnzbd.misc.int_conv(socks.socksocket.default_proxy[2], default=1080)
|
||||
return f"{socks5host}:{socks5port}"
|
||||
return None
|
||||
|
||||
|
||||
@@ -92,11 +94,21 @@ def dnslookup() -> bool:
|
||||
|
||||
|
||||
def local_ipv4() -> Optional[str]:
|
||||
"""return IPv4 address of default local LAN interface"""
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s_ipv4:
|
||||
# Option: use 100.64.1.1 (IANA-Reserved IPv4 Prefix for Shared Address Space)
|
||||
s_ipv4.connect(("10.255.255.255", 80))
|
||||
ipv4 = s_ipv4.getsockname()[0]
|
||||
if not socks.socksocket.default_proxy:
|
||||
# No socks5 proxy, so we can use UDP (SOCK_DGRAM) and a non-reachable host
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s_ipv4:
|
||||
s_ipv4.connect(("10.255.255.255", 80))
|
||||
ipv4 = s_ipv4.getsockname()[0]
|
||||
else:
|
||||
# socks5 proxy set, so we must use TCP (SOCK_STREAM) and a reachable host: the proxy server
|
||||
socks5host = socks.socksocket.default_proxy[1]
|
||||
socks5port = sabnzbd.misc.int_conv(socks.socksocket.default_proxy[2], default=1080)
|
||||
logging.debug(f"Using proxy {socks5host} on port {socks5port} to determine local IPv4 address")
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s_ipv4:
|
||||
s_ipv4.connect((socks5host, socks5port))
|
||||
ipv4 = s_ipv4.getsockname()[0]
|
||||
except socket.error:
|
||||
ipv4 = None
|
||||
|
||||
|
||||
@@ -2029,6 +2029,7 @@ NOTIFY_OPTIONS = {
|
||||
"ncenter_prio_complete",
|
||||
"ncenter_prio_failed",
|
||||
"ncenter_prio_disk_full",
|
||||
"ncenter_prio_quota",
|
||||
"ncenter_prio_warning",
|
||||
"ncenter_prio_error",
|
||||
"ncenter_prio_queue_done",
|
||||
@@ -2045,6 +2046,7 @@ NOTIFY_OPTIONS = {
|
||||
"acenter_prio_complete",
|
||||
"acenter_prio_failed",
|
||||
"acenter_prio_disk_full",
|
||||
"acenter_prio_quota",
|
||||
"acenter_prio_warning",
|
||||
"acenter_prio_error",
|
||||
"acenter_prio_queue_done",
|
||||
@@ -2061,6 +2063,7 @@ NOTIFY_OPTIONS = {
|
||||
"ntfosd_prio_complete",
|
||||
"ntfosd_prio_failed",
|
||||
"ntfosd_prio_disk_full",
|
||||
"ntfosd_prio_quota",
|
||||
"ntfosd_prio_warning",
|
||||
"ntfosd_prio_error",
|
||||
"ntfosd_prio_queue_done",
|
||||
@@ -2078,6 +2081,7 @@ NOTIFY_OPTIONS = {
|
||||
"prowl_prio_complete",
|
||||
"prowl_prio_failed",
|
||||
"prowl_prio_disk_full",
|
||||
"prowl_prio_quota",
|
||||
"prowl_prio_warning",
|
||||
"prowl_prio_error",
|
||||
"prowl_prio_queue_done",
|
||||
@@ -2097,6 +2101,7 @@ NOTIFY_OPTIONS = {
|
||||
"pushover_prio_complete",
|
||||
"pushover_prio_failed",
|
||||
"pushover_prio_disk_full",
|
||||
"pushover_prio_quota",
|
||||
"pushover_prio_warning",
|
||||
"pushover_prio_error",
|
||||
"pushover_prio_queue_done",
|
||||
@@ -2117,6 +2122,7 @@ NOTIFY_OPTIONS = {
|
||||
"pushbullet_prio_complete",
|
||||
"pushbullet_prio_failed",
|
||||
"pushbullet_prio_disk_full",
|
||||
"pushbullet_prio_quota",
|
||||
"pushbullet_prio_warning",
|
||||
"pushbullet_prio_error",
|
||||
"pushbullet_prio_queue_done",
|
||||
@@ -2141,6 +2147,8 @@ NOTIFY_OPTIONS = {
|
||||
"apprise_target_failed_enable",
|
||||
"apprise_target_disk_full",
|
||||
"apprise_target_disk_full_enable",
|
||||
"apprise_target_quota",
|
||||
"apprise_target_quota_enable",
|
||||
"apprise_target_warning",
|
||||
"apprise_target_warning_enable",
|
||||
"apprise_target_error",
|
||||
@@ -2164,6 +2172,7 @@ NOTIFY_OPTIONS = {
|
||||
"nscript_prio_complete",
|
||||
"nscript_prio_failed",
|
||||
"nscript_prio_disk_full",
|
||||
"nscript_prio_quota",
|
||||
"nscript_prio_warning",
|
||||
"nscript_prio_error",
|
||||
"nscript_prio_queue_done",
|
||||
|
||||
@@ -89,6 +89,7 @@ NOTIFICATION_TYPES = {
|
||||
"warning": TT("Warning"), #: Notification
|
||||
"error": TT("Error"), #: Notification
|
||||
"disk_full": TT("Disk full"), #: Notification
|
||||
"quota": TT("Quota"), #: Notification
|
||||
"queue_done": TT("Queue finished"), #: Notification
|
||||
"new_login": TT("User logged in"), #: Notification
|
||||
"other": TT("Other Messages"), #: Notification
|
||||
@@ -323,6 +324,8 @@ def send_apprise(title, msg, notification_type, force=False, test=None):
|
||||
"error": apprise.common.NotifyType.FAILURE,
|
||||
# Disk full
|
||||
"disk_full": apprise.common.NotifyType.WARNING,
|
||||
# Quota
|
||||
"quota": apprise.common.NotifyType.WARNING,
|
||||
# Queue finished
|
||||
"queue_done": apprise.common.NotifyType.INFO,
|
||||
# User logged in
|
||||
|
||||
@@ -488,7 +488,7 @@ def process_job(nzo: NzbObject) -> bool:
|
||||
|
||||
if all_ok:
|
||||
# Remove files matching the cleanup list
|
||||
cleanup_list(tmp_workdir_complete, skip_nzb=True)
|
||||
newfiles = cleanup_list(newfiles, skip_nzb=True)
|
||||
|
||||
# Check if this is an NZB-only download, if so redirect to queue
|
||||
# except when PP was Download-only
|
||||
@@ -501,7 +501,7 @@ def process_job(nzo: NzbObject) -> bool:
|
||||
cleanup_empty_directories(tmp_workdir_complete)
|
||||
else:
|
||||
# Full cleanup including nzb's
|
||||
cleanup_list(tmp_workdir_complete, skip_nzb=False)
|
||||
newfiles = cleanup_list(newfiles, skip_nzb=False)
|
||||
|
||||
script_ret = 0
|
||||
script_error = False
|
||||
@@ -536,7 +536,7 @@ def process_job(nzo: NzbObject) -> bool:
|
||||
# TV/Movie/Date Renaming code part 2 - rename and move files to parent folder
|
||||
if all_ok and file_sorter.sorter_active:
|
||||
if newfiles:
|
||||
workdir_complete, ok = file_sorter.rename(newfiles, workdir_complete)
|
||||
workdir_complete, ok, newfiles = file_sorter.rename(newfiles, workdir_complete)
|
||||
if not ok:
|
||||
nzo.set_unpack_info("Unpack", T("Failed to move files"))
|
||||
nzo.fail_msg = T("Failed to move files")
|
||||
@@ -607,9 +607,9 @@ def process_job(nzo: NzbObject) -> bool:
|
||||
unique=True,
|
||||
)
|
||||
|
||||
# Cleanup again, including NZB files
|
||||
# Cleanup again, any changes made by the script will not be handled
|
||||
if all_ok and os.path.isdir(workdir_complete):
|
||||
cleanup_list(workdir_complete, False)
|
||||
newfiles = cleanup_list(newfiles, False)
|
||||
|
||||
# Force error for empty result
|
||||
all_ok = all_ok and not empty
|
||||
@@ -1101,27 +1101,34 @@ def handle_empty_queue():
|
||||
sabnzbd.LIBC.malloc_trim(0)
|
||||
|
||||
|
||||
def cleanup_list(wdir: str, skip_nzb: bool):
|
||||
def cleanup_list(file_paths: List[str], skip_nzb: bool) -> List[str]:
|
||||
"""Remove all files whose extension matches the cleanup list,
|
||||
optionally ignoring the nzb extension
|
||||
optionally ignoring the nzb extension.
|
||||
Returns the updated list of files (excluding removed files).
|
||||
"""
|
||||
if cfg.cleanup_list():
|
||||
try:
|
||||
with os.scandir(wdir) as files:
|
||||
for entry in files:
|
||||
if entry.is_dir():
|
||||
cleanup_list(entry.path, skip_nzb)
|
||||
cleanup_empty_directories(entry.path)
|
||||
else:
|
||||
if on_cleanup_list(entry.name, skip_nzb):
|
||||
try:
|
||||
logging.info("Removing unwanted file %s", entry.path)
|
||||
remove_file(entry.path)
|
||||
except Exception:
|
||||
logging.error(T("Removing %s failed"), clip_path(entry.path))
|
||||
logging.info("Traceback: ", exc_info=True)
|
||||
except Exception:
|
||||
logging.info("Traceback: ", exc_info=True)
|
||||
if not cfg.cleanup_list():
|
||||
return file_paths
|
||||
|
||||
logging.info("Checking for extensions to clean up: %s", cfg.cleanup_list.get_string())
|
||||
|
||||
remaining_files = []
|
||||
for file_path in file_paths:
|
||||
filename = os.path.basename(file_path)
|
||||
if on_cleanup_list(filename, skip_nzb):
|
||||
try:
|
||||
logging.info("Removing unwanted file %s", file_path)
|
||||
remove_file(file_path)
|
||||
# File was removed, don't add to remaining_files
|
||||
except Exception:
|
||||
logging.error(T("Removing %s failed"), clip_path(file_path))
|
||||
logging.info("Traceback: ", exc_info=True)
|
||||
# File removal failed, keep it in the list
|
||||
remaining_files.append(file_path)
|
||||
else:
|
||||
# File not on cleanup list, keep it
|
||||
remaining_files.append(file_path)
|
||||
|
||||
return remaining_files
|
||||
|
||||
|
||||
def prefix(path: str, pre: str) -> str:
|
||||
@@ -1274,6 +1281,7 @@ def del_marker(path: str):
|
||||
|
||||
|
||||
def remove_from_list(name: Optional[str], lst: List[str]):
|
||||
"""Removes item from list, modifies list in place"""
|
||||
if name:
|
||||
for n in range(len(lst)):
|
||||
if lst[n].endswith(name):
|
||||
|
||||
@@ -545,7 +545,8 @@ SKIN_TEXT = {
|
||||
"srv-expire_date": TT("Account expiration date"),
|
||||
"srv-explain-expire_date": TT("Warn 5 days in advance of account expiration date."),
|
||||
"srv-explain-quota": TT(
|
||||
"Quota for this account, counted from the time it is set. In bytes, optionally follow with K,M,G.<br />Warn when it reaches 0, checked every few minutes."
|
||||
"Quota for this server, counted from the time it is set. In bytes, optionally follow with K,M,G.<br />"
|
||||
"Checked every few minutes. Notification is sent when quota is spent."
|
||||
),
|
||||
"srv-retention": TT("Retention time"), #: Server's retention time in days
|
||||
"srv-ssl": TT("SSL"), #: Server SSL tickbox
|
||||
|
||||
@@ -501,6 +501,39 @@ class Sorter:
|
||||
logging.info("Traceback: ", exc_info=True)
|
||||
return success
|
||||
|
||||
def _update_files_after_renames(self, base_path: str, original_files: List[str]) -> List[str]:
|
||||
"""Update files list to reflect any renames that may have occurred in the base_path"""
|
||||
updated_files = []
|
||||
renamed_files = set() # Track files that no longer exist at their original paths
|
||||
|
||||
for file_path in original_files:
|
||||
# Convert to absolute path for checking
|
||||
if os.path.isabs(file_path):
|
||||
abs_file_path = file_path
|
||||
else:
|
||||
abs_file_path = os.path.join(base_path, file_path)
|
||||
abs_file_path = os.path.normpath(abs_file_path)
|
||||
|
||||
# If the original file still exists, keep it
|
||||
if os.path.exists(abs_file_path):
|
||||
updated_files.append(file_path)
|
||||
else:
|
||||
renamed_files.add(os.path.basename(file_path))
|
||||
|
||||
# If any files were renamed, add all current files in the base_path (excluding originals)
|
||||
if renamed_files:
|
||||
try:
|
||||
for item in os.listdir(base_path):
|
||||
item_path = os.path.join(base_path, item)
|
||||
if os.path.isfile(item_path) and item not in renamed_files:
|
||||
# Only add if not already in the list (to avoid duplicates)
|
||||
if item_path not in updated_files:
|
||||
updated_files.append(item_path)
|
||||
except (OSError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
return updated_files
|
||||
|
||||
def _to_filepath(self, f: str, base_path: str) -> str:
|
||||
if not is_full_path(f):
|
||||
f = os.path.join(base_path, f)
|
||||
@@ -515,23 +548,24 @@ class Sorter:
|
||||
and os.stat(filepath).st_size >= self.rename_limit
|
||||
)
|
||||
|
||||
def rename(self, files: List[str], base_path: str) -> Tuple[str, bool]:
|
||||
def rename(self, files: List[str], base_path: str) -> Tuple[str, bool, List[str]]:
|
||||
if not self.rename_files:
|
||||
return move_to_parent_directory(base_path)
|
||||
return move_to_parent_directory(base_path, files)
|
||||
|
||||
# Log the minimum filesize for renaming
|
||||
if self.rename_limit > 0:
|
||||
logging.debug("Minimum filesize for renaming set to %s bytes", self.rename_limit)
|
||||
|
||||
# Store the list of all files for later use
|
||||
all_files = files
|
||||
all_files = files[:]
|
||||
updated_files = files[:]
|
||||
|
||||
# Filter files to remove nonexistent, undersized, samples, and excluded extensions
|
||||
files = [f for f in files if self._filter_files(f, base_path)]
|
||||
|
||||
if len(files) == 0:
|
||||
logging.debug("No files left to rename after applying filter")
|
||||
return move_to_parent_directory(base_path)
|
||||
return move_to_parent_directory(base_path, updated_files)
|
||||
|
||||
# Check for season packs or sequential filenames and handle their renaming separately;
|
||||
# if neither applies or succeeds, fall back to going with the single largest file instead.
|
||||
@@ -541,7 +575,9 @@ class Sorter:
|
||||
logging.debug("Trying to rename season pack files %s", files)
|
||||
if self._rename_season_pack(files, base_path, all_files):
|
||||
cleanup_empty_directories(base_path)
|
||||
return move_to_parent_directory(base_path)
|
||||
# Update the files list to reflect any renames that happened
|
||||
updated_files = self._update_files_after_renames(base_path, updated_files)
|
||||
return move_to_parent_directory(base_path, updated_files)
|
||||
else:
|
||||
logging.debug("Season pack sorting didn´t rename any files")
|
||||
|
||||
@@ -550,7 +586,9 @@ class Sorter:
|
||||
logging.debug("Trying to rename sequential files %s", sequential_files)
|
||||
if self._rename_sequential(sequential_files, base_path):
|
||||
cleanup_empty_directories(base_path)
|
||||
return move_to_parent_directory(base_path)
|
||||
# Update the files list to reflect any renames that happened
|
||||
updated_files = self._update_files_after_renames(base_path, updated_files)
|
||||
return move_to_parent_directory(base_path, updated_files)
|
||||
else:
|
||||
logging.debug("Sequential file handling didn't rename any files")
|
||||
|
||||
@@ -575,14 +613,16 @@ class Sorter:
|
||||
renamer(filepath, new_filepath)
|
||||
renamed_files.append(new_filepath)
|
||||
except Exception:
|
||||
logging.error(T("Failed to rename %s to %s"), clip_path(base_path), clip_path(new_filepath))
|
||||
logging.error(T("Failed to rename %s to %s"), clip_path(filepath), clip_path(new_filepath))
|
||||
logging.info("Traceback: ", exc_info=True)
|
||||
|
||||
rename_similar(base_path, f_ext, self.filename_set, renamed_files)
|
||||
else:
|
||||
logging.debug("Cannot rename %s, new path %s already exists.", largest_file.get("name"), new_filepath)
|
||||
|
||||
return move_to_parent_directory(base_path)
|
||||
# Update the files list to reflect any renames that happened
|
||||
updated_files = self._update_files_after_renames(base_path, updated_files)
|
||||
return move_to_parent_directory(base_path, updated_files)
|
||||
|
||||
|
||||
class BasicAnalyzer(Sorter):
|
||||
@@ -607,29 +647,55 @@ def ends_in_file(path: str) -> bool:
|
||||
return bool(RE_ENDEXT.search(path) or RE_ENDFN.search(path))
|
||||
|
||||
|
||||
def move_to_parent_directory(workdir: str) -> Tuple[str, bool]:
|
||||
"""Move all files under 'workdir' into 'workdir/..'"""
|
||||
# Determine 'folder'/..
|
||||
def move_to_parent_directory(workdir: str, files: List[str]) -> Tuple[str, bool, List[str]]:
|
||||
"""Move specified files from workdir to workdir's parent directory and track file movements"""
|
||||
if not files:
|
||||
return workdir, True, []
|
||||
|
||||
# Determine 'workdir/..' as destination
|
||||
workdir = os.path.abspath(os.path.normpath(workdir))
|
||||
dest = os.path.abspath(os.path.normpath(os.path.join(workdir, "..")))
|
||||
|
||||
logging.debug("Moving files from %s to parent directory: %s", workdir, dest)
|
||||
|
||||
logging.debug("Moving all files from %s to %s", workdir, dest)
|
||||
updated_files = []
|
||||
|
||||
# Check for DVD folders and bail out if found
|
||||
for item in os.listdir(workdir):
|
||||
if item.lower() in IGNORED_MOVIE_FOLDERS:
|
||||
return workdir, True
|
||||
try:
|
||||
for item in os.listdir(workdir):
|
||||
if os.path.isdir(os.path.join(workdir, item)) and item.lower() in IGNORED_MOVIE_FOLDERS:
|
||||
return workdir, True, files
|
||||
except (OSError, FileNotFoundError):
|
||||
# Skip directory listing if directory doesn't exist
|
||||
pass
|
||||
|
||||
for root, dirs, files in os.walk(workdir):
|
||||
for _file in files:
|
||||
path = os.path.join(root, _file)
|
||||
new_path = path.replace(workdir, dest)
|
||||
ok, new_path = move_to_path(path, new_path)
|
||||
if not ok:
|
||||
return dest, False
|
||||
# Move each file to the parent directory
|
||||
for file_path in files:
|
||||
# Convert relative paths to absolute paths within workdir
|
||||
if os.path.isabs(file_path):
|
||||
abs_file_path = file_path
|
||||
else:
|
||||
abs_file_path = os.path.join(workdir, file_path)
|
||||
abs_file_path = os.path.normpath(abs_file_path)
|
||||
|
||||
if not os.path.exists(abs_file_path):
|
||||
# Skip files that don't exist
|
||||
continue
|
||||
|
||||
filename = os.path.basename(abs_file_path)
|
||||
new_path = os.path.join(dest, filename)
|
||||
|
||||
ok, new_path = move_to_path(abs_file_path, new_path)
|
||||
if not ok:
|
||||
return dest, False, files
|
||||
# Track this file as it was moved
|
||||
updated_files.append(new_path)
|
||||
|
||||
# Clean up empty directories in the workdir
|
||||
cleanup_empty_directories(workdir)
|
||||
return dest, True
|
||||
|
||||
# Return the parent directory and list of files that were actually moved
|
||||
return dest, True, updated_files
|
||||
|
||||
|
||||
def guess_what(name: str) -> MatchesDict:
|
||||
|
||||
@@ -351,13 +351,15 @@ class TestSortingFunctions:
|
||||
ffs.fs.create_file(base_dir + "/" + test_file, int("0644", 8))
|
||||
assert os.path.exists(base_dir + "/" + test_file) is True
|
||||
|
||||
return_path, return_status = sorting.move_to_parent_directory(base_dir + "/TEST")
|
||||
# Create the file list to move
|
||||
files_to_move = [base_dir + "/TEST/DIR/FILE"]
|
||||
return_path, return_status, return_files = sorting.move_to_parent_directory(base_dir + "/TEST", files_to_move)
|
||||
|
||||
# Affected by move
|
||||
assert not os.path.exists(base_dir + "/TEST/DIR/FILE") # Moved to subdir
|
||||
assert not os.path.exists(base_dir + "/TEST/DIR2") # Deleted empty directory
|
||||
assert not os.path.exists(base_dir + "/DIR2") # Dirs don't get moved, only their file content
|
||||
assert os.path.exists(base_dir + "/DIR/FILE") # Moved file
|
||||
assert os.path.exists(base_dir + "/FILE") # Moved file
|
||||
# Not moved
|
||||
assert not os.path.exists(base_dir + "/some.file")
|
||||
assert not os.path.exists(base_dir + "/2")
|
||||
@@ -366,6 +368,8 @@ class TestSortingFunctions:
|
||||
# Function return values
|
||||
assert (return_path) == base_dir
|
||||
assert (return_status) is True
|
||||
assert len(return_files) == 1
|
||||
assert return_files[0] == base_dir + "/FILE"
|
||||
|
||||
# Exception for DVD directories
|
||||
with pyfakefs.fake_filesystem_unittest.Patcher() as ffs:
|
||||
@@ -380,13 +384,15 @@ class TestSortingFunctions:
|
||||
ffs.fs.create_file(base_dir + "/" + test_file, int("0644", 8))
|
||||
assert os.path.exists(base_dir + "/" + test_file) is True
|
||||
|
||||
return_path, return_status = sorting.move_to_parent_directory(base_dir + "/TEST")
|
||||
# Create the file list to move (includes file in DVD directory)
|
||||
files_to_move = [base_dir + "/TEST/" + dvd + "/FILE"]
|
||||
return_path, return_status, return_files = sorting.move_to_parent_directory(base_dir + "/TEST", files_to_move)
|
||||
|
||||
# Nothing should move in the presence of a DVD directory structure
|
||||
assert os.path.exists(base_dir + "/TEST/" + dvd + "/FILE")
|
||||
assert os.path.exists(base_dir + "/TEST/DIR2")
|
||||
assert not os.path.exists(base_dir + "/DIR2")
|
||||
assert not os.path.exists(base_dir + "/DIR/FILE")
|
||||
assert not os.path.exists(base_dir + "/FILE")
|
||||
assert not os.path.exists(base_dir + "/some.file")
|
||||
assert not os.path.exists(base_dir + "/2")
|
||||
assert os.path.exists(base_dir + "/dir/some.file")
|
||||
@@ -394,6 +400,8 @@ class TestSortingFunctions:
|
||||
# Function return values
|
||||
assert (return_path) == base_dir + "/TEST"
|
||||
assert (return_status) is True
|
||||
# Files should be returned as-is when DVD structure prevents moving
|
||||
assert return_files == files_to_move
|
||||
|
||||
@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows tests")
|
||||
def test_move_to_parent_directory_win(self):
|
||||
@@ -409,13 +417,15 @@ class TestSortingFunctions:
|
||||
ffs.fs.create_file(base_dir + "\\" + test_file, int("0644", 8))
|
||||
assert os.path.exists(base_dir + "\\" + test_file) is True
|
||||
|
||||
return_path, return_status = sorting.move_to_parent_directory(base_dir + "\\TEST")
|
||||
# Create the file list to move
|
||||
files_to_move = [base_dir + "\\TEST\\DIR\\FILE"]
|
||||
return_path, return_status, return_files = sorting.move_to_parent_directory(base_dir + "\\TEST", files_to_move)
|
||||
|
||||
# Affected by move
|
||||
assert not os.path.exists(base_dir + "\\TEST\\DIR\\FILE") # Moved to subdir
|
||||
assert not os.path.exists(base_dir + "\\TEST\\DIR2") # Deleted empty directory
|
||||
assert not os.path.exists(base_dir + "\\DIR2") # Dirs don't get moved, only their file content
|
||||
assert os.path.exists(base_dir + "\\DIR\\FILE") # Moved file
|
||||
assert os.path.exists(base_dir + "\\FILE") # Moved file
|
||||
# Not moved
|
||||
assert not os.path.exists(base_dir + "\\some.file")
|
||||
assert not os.path.exists(base_dir + "\\2")
|
||||
@@ -424,6 +434,8 @@ class TestSortingFunctions:
|
||||
# Function return values
|
||||
assert (return_path) == base_dir
|
||||
assert (return_status) is True
|
||||
assert len(return_files) == 1
|
||||
assert return_files[0] == base_dir + "\\FILE"
|
||||
|
||||
# Exception for DVD directories
|
||||
with pyfakefs.fake_filesystem_unittest.Patcher() as ffs:
|
||||
@@ -438,20 +450,24 @@ class TestSortingFunctions:
|
||||
ffs.fs.create_file(base_dir + "\\" + test_file, int("0644", 8))
|
||||
assert os.path.exists(base_dir + "\\" + test_file) is True
|
||||
|
||||
return_path, return_status = sorting.move_to_parent_directory(base_dir + "\\TEST")
|
||||
# Create the file list to move (includes file in DVD directory)
|
||||
files_to_move = [base_dir + "\\TEST\\" + dvd + "\\FILE"]
|
||||
return_path, return_status, return_files = sorting.move_to_parent_directory(base_dir + "\\TEST", files_to_move)
|
||||
|
||||
# Nothing should move in the presence of a DVD directory structure
|
||||
assert os.path.exists(base_dir + "\\TEST\\" + dvd + "\\FILE")
|
||||
assert os.path.exists(base_dir + "\\TEST\\DIR2")
|
||||
assert not os.path.exists(base_dir + "\\DIR2")
|
||||
assert not os.path.exists(base_dir + "\\DIR\\FILE")
|
||||
assert not os.path.exists(base_dir + "\\FILE")
|
||||
assert not os.path.exists(base_dir + "\\some.file")
|
||||
assert not os.path.exists(base_dir + "\\2")
|
||||
assert os.path.exists(base_dir + "\\dir\\some.file")
|
||||
assert os.path.exists(base_dir + "\\dir\\2")
|
||||
# Function return values
|
||||
# Function return values - should return original directory when DVD structure found
|
||||
assert (return_path) == base_dir + "\\TEST"
|
||||
assert (return_status) is True
|
||||
# Files should be returned as-is when DVD structure prevents moving
|
||||
assert return_files == files_to_move
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("clean_cache_dir")
|
||||
@@ -766,6 +782,10 @@ class TestSortingSorter:
|
||||
):
|
||||
"""Test the file renaming of the Sorter class"""
|
||||
with pyfakefs.fake_filesystem_unittest.Patcher() as ffs:
|
||||
# Add guessit package directory to real paths so it can access its config files
|
||||
import guessit
|
||||
guessit_path = os.path.dirname(guessit.__file__)
|
||||
ffs.fs.add_real_paths([guessit_path])
|
||||
# Make up a job name
|
||||
job_name = "Simulated.Job." + job_tag + ".2160p.Web.x264-SAB"
|
||||
|
||||
@@ -816,7 +836,7 @@ class TestSortingSorter:
|
||||
)
|
||||
sorter.get_values()
|
||||
sorter.construct_path()
|
||||
sort_dest, is_ok = sorter.rename(all_files, job_dir)
|
||||
sort_dest, is_ok, updated_files = sorter.rename(all_files, job_dir)
|
||||
|
||||
# Check the result
|
||||
try:
|
||||
@@ -1314,7 +1334,7 @@ class TestSortingSorter:
|
||||
sorted_path = sorter.construct_path()
|
||||
# Check season pack status again after constructing the path
|
||||
assert sorter.is_season_pack is result_is_season_pack_later
|
||||
sorted_dest, sorted_ok = sorter.rename(globber(job_dir), job_dir)
|
||||
sorted_dest, sorted_ok, updated_files = sorter.rename(globber(job_dir), job_dir)
|
||||
|
||||
# Verify the results
|
||||
for pattern, number in result_globs.items():
|
||||
|
||||
Reference in New Issue
Block a user