Compare commits

..

3 Commits

Author SHA1 Message Date
Safihre
efe17ca3bb Changes to fix the tests 2025-10-10 15:10:38 +02:00
Safihre
d4995e3120 Only clean up files created by the specific download 2025-10-10 14:15:47 +02:00
Safihre
90989b374a Let Sorter track file changes 2025-10-10 13:44:47 +02:00
43 changed files with 201 additions and 482 deletions

View File

@@ -13,10 +13,10 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v5
- name: Set up Python
- name: Set up Python 3.13
uses: actions/setup-python@v6
with:
python-version: "3.14"
python-version: "3.13"
architecture: "x64"
cache: pip
cache-dependency-path: "**/requirements.txt"
@@ -89,18 +89,18 @@ jobs:
# We need the official Python, because the GA ones only support newer macOS versions
# The deployment target is picked up by the Python build tools automatically
# If updated, make sure to also set LSMinimumSystemVersion in SABnzbd.spec
PYTHON_VERSION: "3.14.0"
MACOSX_DEPLOYMENT_TARGET: "10.15"
PYTHON_VERSION: "3.13.7"
MACOSX_DEPLOYMENT_TARGET: "10.13"
# We need to force compile for universal2 support
CFLAGS: -arch x86_64 -arch arm64
ARCHFLAGS: -arch x86_64 -arch arm64
steps:
- uses: actions/checkout@v5
- name: Set up Python
- name: Set up Python 3.13
# Only use this for the caching of pip packages!
uses: actions/setup-python@v6
with:
python-version: "3.14"
python-version: "3.13"
cache: pip
cache-dependency-path: "**/requirements.txt"
- name: Cache Python download
@@ -219,7 +219,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.14"
python-version: "3.x"
cache: pip
cache-dependency-path: "builder/release-requirements.txt"
- name: Download Source distribution artifact

View File

@@ -37,10 +37,10 @@ jobs:
include:
- name: macOS
os: macos-13
python-version: "3.14"
python-version: "3.13"
- name: Windows
os: windows-2022
python-version: "3.14"
python-version: "3.13"
steps:
- uses: actions/checkout@v5

View File

@@ -30,7 +30,7 @@ jobs:
run: |
python3 tools/make_mo.py
- name: Push translatable and translated texts back to repo
uses: stefanzweifel/git-auto-commit-action@v7.0.0
uses: stefanzweifel/git-auto-commit-action@v6.0.1
if: env.TX_TOKEN
with:
commit_message: |

View File

@@ -1,74 +1,7 @@
Release Notes - SABnzbd 4.5.5
Release Notes - SABnzbd 4.5.0 Release Candidate 1
=========================================================
## Bug fixes and changes in 4.5.5
* macOS: Failed to start on versions of macOS older than 11.
Python 3.14 dropped support for macOS 10.13 and 10.14.
Because of that macOS 10.15 is required to run 4.5.5.
## Bug fixes and changes in 4.5.4
### New Features
* History details now includes option to mark job as `Completed`.
* `Quota` notifications available for all notification services.
- Sends alerts at 75%, 90%, and 100% quota usage.
* Multi-Operations now supports Move to Top/Bottom.
* New `outgoing_nntp_ip` option to bind outgoing NNTP connections to specific IP address.
### Improvements
* Setup wizard now requires successful Server Test before proceeding.
* Anime episode notation `S04 - 10` now supported for Sorting and Duplicate Detection.
* Multi-Operations: Play/Resume button unselects on second click for better usability.
* Unrar now handles renaming of invalid characters on Windows filesystem.
* Switched from vendored `sabnzbd.rarfile` module to `rarfile>=4.2`.
* Warning displayed when removing all Orphaned jobs (clears Temporary Download folder).
### Bug Fixes
* Active connections counter in Status window now updates correctly.
* Job setting changes during URL-grabbing no longer ignored.
* Incomplete `.par2` file parsing no longer leaves files behind.
* `Local IPv4 address` now detectable when using Socks5 proxy.
* Server configuration changes no longer show `Failure` message during page reload.
### Platform-Specific
* Linux: `Make Windows compatible` automatically enabled when needed.
* Windows: Executables are now signed using SignPath Foundation certificate.
* Windows: Can now start SABnzbd directly from installer.
* Windows and macOS: Binaries now use Python 3.14.
## Bug fixes and changes in 4.5.3
* Remember if `Permanently delete` was previously checked.
* All available IP-addresses will be included when selecting the fastest.
* Pre-queue script rejected NZBs were sometimes reported as `URL Fetching failed`.
* RSS `Next scan` time was not adjusted after manual `Read All Feeds Now`.
* Prevent renaming of `.cbr` files during verification.
* If `--disable-file-log` was enabled, `Show Logging` would crash.
* API: Added `time_added`, timestamp of when the job was added to the queue.
* API: History output could contain duplicate items.
* Snap: Updated packages and changed build process for reliability.
* macOS: Repair would fail on macOS 10.13 High Sierra.
* Windows: Unable to start on Windows 8.
* Windows: Updated Unrar to 7.13, which resolves CVE-2025-8088.
## Bug fixes and changes in 4.5.2
* Added Tab and Shift+Tab navigation to move between rename fields in queue.
* Invalid cookies of other services could result in errors.
* Internet Bandwidth test could be stuck in infinite loop.
* RSS readout did not ignore torrent alternatives.
* Prowl and Pushover settings did not load correctly.
* Renamed `osx` to `macos` internally.
* API: Removed `B` post-fix from `quota` and `left_quota` fields in `queue`.
* Windows: Support more languages in the installer.
* Windows and macOS: Updated par2cmdline-turbo to 1.3.0 and Unrar to 7.12.
## Bug fixes and changes in 4.5.1
* Correct platform detection on Linux.
* The `From SxxEyy` RSS filters did not always work.
* Windows and macOS: Update Unrar to 7.11.
This is the first Release Candidate for the 4.5.0 release.
## New features in 4.5.0
@@ -88,8 +21,10 @@ Release Notes - SABnzbd 4.5.5
## Upgrade notices
* Direct upgrade supported from version 3.0.0 and newer.
* Older versions require performing a `Queue repair` after upgrading.
* You can directly upgrade from version 3.0.0 and newer.
* Upgrading from older versions will require performing a `Queue repair`.
* Downgrading from version 4.2.0 or newer to 3.7.2 or older will require
performing a `Queue repair` due to changes in the internal data format.
## Known problems and solutions

View File

@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import glob
import platform
import re
import sys
import os
@@ -27,7 +28,6 @@ import tarfile
import urllib.request
import urllib.error
import configobj
import packaging.version
from typing import List
from constants import (
@@ -109,52 +109,6 @@ def patch_version_file(release_name):
ver.write(version_file)
def test_macos_min_version(binary_path: str):
# Skip check if nothing was set
if macos_min_version := os.environ.get("MACOSX_DEPLOYMENT_TARGET"):
# Skip any arm64 specific files
if "arm64" in binary_path:
print(f"Skipping arm64 binary {binary_path}")
return
# Check minimum macOS version is at least mac OS10.13
# We only check the x86_64 since for arm64 it's always macOS 11+
print(f"Checking if binary supports macOS {macos_min_version} and above: {binary_path}")
otool_output = run_external_command(
[
"otool",
"-arch",
"x86_64",
"-l",
binary_path,
],
print_output=False,
)
# Parse the output for LC_BUILD_VERSION minos
# The output is very large, so that's why we enumerate over it
req_version = packaging.version.parse(macos_min_version)
bin_version = None
lines = otool_output.split("\n")
for line_nr, line in enumerate(lines):
if "LC_VERSION_MIN_MACOSX" in line:
# Display the version in the next lines
bin_version = packaging.version.parse(lines[line_nr + 2].split()[1])
elif "minos" in line:
bin_version = packaging.version.parse(line.split()[1])
if bin_version and bin_version > req_version:
raise ValueError(f"{binary_path} requires {bin_version}, we want {req_version}")
else:
# We got the information we need
break
else:
print(lines)
raise RuntimeError(f"Could not determine minimum macOS version for {binary_path}")
else:
print(f"Skipping macOS version check, MACOSX_DEPLOYMENT_TARGET not set")
def test_sab_binary(binary_path: str):
"""Wrapper to have a simple start-up test for the binary"""
with tempfile.TemporaryDirectory() as config_dir:
@@ -336,11 +290,7 @@ if __name__ == "__main__":
"macos/7zip/7zz",
]
for file_to_sign in files_to_sign:
# Make sure it supports the macOS versions we want first
test_macos_min_version(file_to_sign)
# Then sign in
print("Signing %s with hardened runtime" % file_to_sign)
print("Signing %s with hardended runtime" % file_to_sign)
run_external_command(
[
"codesign",
@@ -365,16 +315,13 @@ if __name__ == "__main__":
# Make sure we created a fully universal2 release when releasing or during CI
if RELEASE_THIS or ON_GITHUB_ACTIONS:
for bin_to_check in glob.glob("dist/SABnzbd.app/**/*.so", recursive=True):
for bin_to_check in glob.glob("dist/SABnzbd.app/Contents/MacOS/**/*.so", recursive=True):
print("Checking if binary is universal2: %s" % bin_to_check)
file_output = run_external_command(["file", bin_to_check], print_output=False)
# Make sure we have both arm64 and x86
if not ("x86_64" in file_output and "arm64" in file_output):
raise RuntimeError("Non-universal2 binary found!")
# Make sure it supports the macOS versions we want
test_macos_min_version(bin_to_check)
# Only continue if we can sign
if authority:
# We use PyInstaller to sign the main SABnzbd executable and the SABnzbd.app

View File

@@ -75,7 +75,7 @@ print("----")
# Check if tagged as release and check for token
gh_token = os.environ.get("AUTOMATION_GITHUB_TOKEN", "")
if RELEASE_THIS and gh_token:
gh_obj = github.Github(auth=github.Auth.Token(gh_token))
gh_obj = github.Github(gh_token)
gh_repo = gh_obj.get_repo("sabnzbd/sabnzbd")
# Read the release notes
@@ -86,7 +86,7 @@ if RELEASE_THIS and gh_token:
for release in gh_repo.get_releases():
if release.tag_name == RELEASE_VERSION:
gh_release = release
print("Found existing release %s" % gh_release.name)
print("Found existing release %s" % gh_release.title)
break
else:
# Did not find it, so create the release, use the GitHub tag we got as input

View File

@@ -4,7 +4,7 @@ pyinstaller==6.16.0
packaging==25.0
pyinstaller-hooks-contrib==2025.9
altgraph==0.17.4
wrapt==2.0.0
wrapt==1.17.3
setuptools==80.9.0
# For the Windows build

View File

@@ -159,16 +159,7 @@ Unicode true
;------------------------------------------------------------------
; Run as user-level at end of install
Function PageFinishRun
; Check if SABnzbd service is installed
!insertmacro SERVICE "installed" "SABnzbd" ""
Pop $0 ;response
${If} $0 == true
; Service is installed, start the service
!insertmacro SERVICE "start" "SABnzbd" ""
${Else}
; Service not installed, run executable as user
${StdUtils.ExecShellAsUser} $0 "$INSTDIR\SABnzbd.exe" "" ""
${EndIf}
${StdUtils.ExecShellAsUser} $0 "$INSTDIR\SABnzbd.exe" "" ""
FunctionEnd

View File

@@ -132,7 +132,7 @@
<textarea name="notes" id="notes" rows="3" cols="50"></textarea>
</div>
<div class="field-pair no-field-pair-bg">
<button class="btn btn-default addNewServer" disabled data-toggle="tooltip" data-placement="top" title="$T('wizard-test-server-required')"><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
<button class="btn btn-default addNewServer" disabled><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
<button class="btn btn-default testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
</div>
<div class="field-pair result-box">
@@ -464,9 +464,6 @@
When finished loading
**/
jQuery(document).ready(function(){
// Initialize tooltips
jQuery('[data-toggle="tooltip"]').tooltip()
// 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(event) {
var parentForm = jQuery(event.target).parents("form")
@@ -563,9 +560,6 @@
// Allow adding the new server if we are in the new-server section
if(theButton.parents("form[action='addServer']").length) {
jQuery(".addNewServer").removeAttr("disabled")
jQuery(".addNewServer").removeAttr("data-toggle")
jQuery(".addNewServer").removeAttr("title")
jQuery(".addNewServer").tooltip("destroy")
}
} else {
resultBox.addClass('alert-danger')
@@ -574,10 +568,6 @@
// Disable the adding of new server, just to be sure
if(theButton.parents("form[action='addServer']").length) {
jQuery(".addNewServer").attr("disabled", "disabled")
jQuery(".addNewServer").attr("data-toggle", "tooltip")
jQuery(".addNewServer").attr("data-placement", "top")
jQuery(".addNewServer").attr("title", "$T('wizard-test-server-required')")
jQuery(".addNewServer").tooltip()
}
}
});

View File

@@ -285,14 +285,6 @@ col2 h3 a,
border-top-color: #E4E4E4 !important;
}
.tooltip.left .tooltip-arrow {
border-left-color: #E4E4E4 !important;
}
.tooltip.right .tooltip-arrow {
border-right-color: #E4E4E4 !important;
}
.Special .glyphicon-asterisk {
color: #E4E4E4 !important;
}

View File

@@ -52,14 +52,16 @@
var glitterTranslate = new Object();
glitterTranslate.paused = "$T('post-Paused')";
glitterTranslate.left = "$T('Glitter-left')";
glitterTranslate.clearOrphanWarning = "$T('Glitter-clearOrphanWarning')";
glitterTranslate.clearWarn = "$T('confirm')";
glitterTranslate.pausePromptFail = "$T('Glitter-pausePromptFail')"
glitterTranslate.pauseFor = "$T('pauseFor')"
glitterTranslate.minutes = "$T('mins')"
glitterTranslate.shutdown = "$T('shutdownOK?')";
glitterTranslate.restart = "$T('explain-Restart') $T('explain-needNewLogin')".replace(/\<br(\s*\/|)\>/g, '\n');
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n").replace(/&quot;/g,'"');
glitterTranslate.confirm = "$T('confirm')";
glitterTranslate.deleteMsg = "$T('nzo-delete')";
glitterTranslate.removeDown = "$T('confirm')";
glitterTranslate.removeDow1 = "$T('confirm')";
glitterTranslate.markComplete = "$T('button-mark-completed')";
glitterTranslate.renameAbort = "$T('Glitter-confirmAbortDirectUnpack')\n$T('confirm')";
glitterTranslate.retryAll = "$T('link-retryAll')?";

View File

@@ -895,7 +895,7 @@ function ViewModel() {
// Orphaned folder deletion of all
self.removeAllOrphaned = function() {
if (confirm(glitterTranslate.clearOrphanWarning)) {
if (!self.confirmDeleteHistory() || confirm(glitterTranslate.clearWarn)) {
// Show notification
showNotification('.main-notification-box-removing-multiple', 0, self.statusInfo.folders().length)
// Delete them all
@@ -912,7 +912,7 @@ function ViewModel() {
// Orphaned folder adding of all
self.addAllOrphaned = function() {
if (confirm(glitterTranslate.confirm)) {
if (!self.confirmDeleteHistory() || confirm(glitterTranslate.clearWarn)) {
// Show notification
showNotification('.main-notification-box-sendback')
// Delete them all

View File

@@ -9,7 +9,6 @@
<script type="text/javascript">
var txtTestServer = "$T('wizard-server-text')";
var txtChecking = "$T('srv-testing')";
var txtTestRequired = "$T('wizard-test-server-required')";
<!--#include raw $webdir + "/static/javascript/checkserver.js"#-->
</script>
<h3>$T('wizard-server')</h3>
@@ -83,7 +82,7 @@
</div>
<div class="row">
<div class="col-sm-4">
<button id="serverTest" class="btn btn-default" data-toggle="tooltip" data-placement="left"><span class="glyphicon glyphicon-sort"></span> $T('wizard-button-testServer')</button>
<button id="serverTest" class="btn btn-default"><span class="glyphicon glyphicon-sort"></span> $T('wizard-button-testServer')</button>
</div>
<div class="col-sm-8">
<div id="serverResponse" class="well well-sm">$T('wizard-server-text')</div>

View File

@@ -16,16 +16,9 @@ function checkRequired() {
// Check if form is valid using HTML5 validation and if server test passed
if ($("form").get(0).checkValidity() && serverTestSuccessful) {
$("#next-button").removeClass('disabled')
$("#next-button").removeAttr('data-toggle')
$("#next-button").removeAttr('title')
$("#next-button").tooltip('destroy')
return true;
} else {
$("#next-button").addClass('disabled')
$("#next-button").attr('data-toggle', 'tooltip')
$("#next-button").attr('data-placement', 'left')
$("#next-button").attr('title', txtTestRequired)
$("#next-button").tooltip()
return false;
}
}

View File

@@ -30,9 +30,8 @@
<url type="faq">https://sabnzbd.org/wiki/faq</url>
<url type="contact">https://sabnzbd.org/live-chat.html</url>
<releases>
<release version="4.5.5" date="2025-10-24" type="stable"/>
<release version="4.5.4" date="2025-10-22" type="stable"/>
<release version="4.5.3" date="2025-08-25" type="stable"/>
<release version="4.5.4" date="2025-10-31" type="stable"/>
<release version="4.5.3" date="2025-08-31" type="stable"/>
<release version="4.5.2" date="2025-07-09" type="stable"/>
<release version="4.5.1" date="2025-04-11" type="stable"/>
<release version="4.5.0" date="2025-04-01" type="stable"/>

View File

@@ -4318,10 +4318,6 @@ msgstr ""
msgid "Retry all"
msgstr ""
#: sabnzbd/skintext.py
msgid "Are you sure you want to delete all folders in your Temporary Download Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr ""
@@ -4560,11 +4556,6 @@ msgstr ""
msgid "Start Wizard"
msgstr ""
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -4535,12 +4535,6 @@ msgstr ""
msgid "Retry all"
msgstr "Opakovat vše"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Získat NZB z URL"
@@ -4787,11 +4781,6 @@ msgstr ""
msgid "Start Wizard"
msgstr ""
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -4685,12 +4685,6 @@ msgstr "Fjern alle"
msgid "Retry all"
msgstr "Prøv igen alle"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Hent NZB fra URL"
@@ -4941,11 +4935,6 @@ msgstr "Afslut SABnzbd"
msgid "Start Wizard"
msgstr "Start guide"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -4851,12 +4851,6 @@ msgstr "Alle löschen"
msgid "Retry all"
msgstr "Alle wiederholen"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "NZB aus URL laden"
@@ -5112,11 +5106,6 @@ msgstr "SABnzbd beenden"
msgid "Start Wizard"
msgstr "Assistenten starten"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Backup wiederherstellen"

View File

@@ -4823,12 +4823,6 @@ msgstr "Eliminar todo"
msgid "Retry all"
msgstr "Re-intentar todo"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Descargar NZB desde URL"
@@ -5084,11 +5078,6 @@ msgstr "Salir SABnzbd"
msgid "Start Wizard"
msgstr "Iniciar Asistente"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Restaurar copia de seguridad"

View File

@@ -4642,12 +4642,6 @@ msgstr "Poista kaikki"
msgid "Retry all"
msgstr "Yritä uudelleen kaikki"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Nouda NZB osoitteesta"
@@ -4900,11 +4894,6 @@ msgstr "Poistu SABnzbd:stä"
msgid "Start Wizard"
msgstr "Käynnistä velho"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -2,14 +2,14 @@
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2025
# Fred L <88com88@gmail.com>, 2025
# Safihre <safihre@sabnzbd.org>, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:49+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2025\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -378,11 +378,11 @@ msgstr "Quota"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr "Avertissement de limite de quota (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr "Le téléchargement a repris après la réinitialisation du quota."
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
@@ -3842,9 +3842,6 @@ msgid ""
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
"Quota pour ce serveur, calculé à partir du moment où il est défini. En "
"octets, suivi éventuellement de K,M,G.<br />Vérifié toutes les quelques "
"minutes. Une notification est envoyée lorsque le quota est atteint."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -4838,14 +4835,6 @@ msgstr "Tout supprimer"
msgid "Retry all"
msgstr "Réessayer tous"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
"Êtes-vous sûr de vouloir supprimer tous les dossiers de votre Dossier de "
"Téléchargement Temporaire ? Cette opération ne peut pas être annulée !"
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Importer le NZB depuis l'URL"
@@ -5104,11 +5093,6 @@ msgstr "Quitter SABnzbd"
msgid "Start Wizard"
msgstr "Lancer l'assistant"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr "Cliquez sur Tester le Serveur avant de continuer"
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Restaurer la sauvegarde"

View File

@@ -4650,12 +4650,6 @@ msgstr "מחק הכל"
msgid "Retry all"
msgstr "נסה שוב הכל"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "משוך NZB מכתובת"
@@ -4910,11 +4904,6 @@ msgstr "צא מן SABnzbd"
msgid "Start Wizard"
msgstr "התחל אשף"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "שחזר גיבוי"

View File

@@ -4787,12 +4787,6 @@ msgstr "Elimina tutto"
msgid "Retry all"
msgstr "Riprova tutto"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Recupera NZB da URL"
@@ -5049,11 +5043,6 @@ msgstr "Esci da SABnzbd"
msgid "Start Wizard"
msgstr "Avvia procedura guidata"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Ripristina backup"

View File

@@ -4616,12 +4616,6 @@ msgstr "Ta bort alle"
msgid "Retry all"
msgstr "Prøv alle på nytt"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Hent NZB fra URL"
@@ -4872,11 +4866,6 @@ msgstr "Avslutt SABnzbd"
msgid "Start Wizard"
msgstr "Start Veiviser"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -4784,12 +4784,6 @@ msgstr "Alles wissen"
msgid "Retry all"
msgstr "Alles opnieuw proberen"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Haal NZB op via URL"
@@ -5045,11 +5039,6 @@ msgstr "Stop SABnzbd"
msgid "Start Wizard"
msgstr "Wizard starten"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Backup herstellen"

View File

@@ -4628,12 +4628,6 @@ msgstr "Usuń wszystko"
msgid "Retry all"
msgstr "Ponów wszystkie"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Pobierz NZB z URL"
@@ -4882,11 +4876,6 @@ msgstr "Wyjście z SABnzbd"
msgid "Start Wizard"
msgstr "Uruchom kreatora konfiguracji"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -4639,12 +4639,6 @@ msgstr "Excluir Todos"
msgid "Retry all"
msgstr "Repetir todos"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Buscar NZB de uma URL"
@@ -4893,11 +4887,6 @@ msgstr "Sair do SABnzbd"
msgid "Start Wizard"
msgstr "Iniciar o Assistente"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -4658,12 +4658,6 @@ msgstr "Șterge tot"
msgid "Retry all"
msgstr "Reîncearcă toate"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Descarcă NZB din URL"
@@ -4915,11 +4909,6 @@ msgstr "Închide SABnzbd"
msgid "Start Wizard"
msgstr "Porneşte Vrăjitor"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -4622,12 +4622,6 @@ msgstr "Удалить всё"
msgid "Retry all"
msgstr ""
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr ""
@@ -4878,11 +4872,6 @@ msgstr ""
msgid "Start Wizard"
msgstr ""
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -4601,12 +4601,6 @@ msgstr "Избриши све"
msgid "Retry all"
msgstr "Ponovo pokušaj sve"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Povuci NZB sa URL"
@@ -4855,11 +4849,6 @@ msgstr "Затвори SABnzbd"
msgid "Start Wizard"
msgstr "Покрени чаробњака"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -4613,12 +4613,6 @@ msgstr "Ta bort alla"
msgid "Retry all"
msgstr "Starta om alla"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "Hämta NZB från URL"
@@ -4869,11 +4863,6 @@ msgstr "Avsluta SABnzbd"
msgid "Start Wizard"
msgstr "Starta guide"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -370,11 +370,11 @@ msgstr "Kota"
#: sabnzbd/bpsmeter.py
msgid "Quota limit warning (%d%%)"
msgstr "Kota sınır ikazı (%d%%)"
msgstr ""
#: sabnzbd/bpsmeter.py
msgid "Downloading resumed after quota reset"
msgstr "İndirme kota sıfırlamasının ardından devam etti"
msgstr ""
#: sabnzbd/cfg.py, sabnzbd/interface.py
msgid "Incorrect parameter"
@@ -3786,10 +3786,6 @@ msgid ""
" follow with K,M,G.<br />Checked every few minutes. Notification is sent "
"when quota is spent."
msgstr ""
"Bu sunucu için, ayarlandığı zamandan itibaren hesaplanan kota. Bayt olarak, "
"seçime dayalı bir şekilde K, M, G takip edebilir. <br /> Her birkaç "
"dakikada bir kontrol edilir. Kota sonuna ulaşıldığında bir bildirim "
"gönderilir."
#. Server's retention time in days
#: sabnzbd/skintext.py
@@ -4780,14 +4776,6 @@ msgstr "Tümünü Sil"
msgid "Retry all"
msgstr "Tümünü tekrar dene"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
"Geçici İndirme Dizini'ndeki tüm dizinleri silmek istediğinizden emin "
"misiniz? Bu geri alınamaz!"
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "URL konumundan NZB al"
@@ -5044,11 +5032,6 @@ msgstr "SABnzb'den çık"
msgid "Start Wizard"
msgstr "Sihirbazı Başlat"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr "Devam etmeden evvel Sunucuyu Dene unsuruna tıklayın"
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr "Yedeklemeyi geri getir"

View File

@@ -4553,12 +4553,6 @@ msgstr "全部删除"
msgid "Retry all"
msgstr "全部重试"
#: sabnzbd/skintext.py
msgid ""
"Are you sure you want to delete all folders in your Temporary Download "
"Folder? This cannot be undone!"
msgstr ""
#: sabnzbd/skintext.py
msgid "Fetch NZB from URL"
msgstr "从 URL 装取 NZB"
@@ -4805,11 +4799,6 @@ msgstr "退出 SABnzbd"
msgid "Start Wizard"
msgstr "启动向导"
#. Tooltip for disabled Next button
#: sabnzbd/skintext.py
msgid "Click on Test Server before continuing"
msgstr ""
#: sabnzbd/skintext.py
msgid "Restore backup"
msgstr ""

View File

@@ -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.3
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
@@ -50,8 +50,8 @@ winrt-Windows.UI.Notifications==3.2.1; sys_platform == 'win32'
typing_extensions==4.15.0; sys_platform == 'win32'
# macOS system calls
pyobjc-core==12.0; sys_platform == 'darwin'
pyobjc-framework-Cocoa==12.0; sys_platform == 'darwin'
pyobjc-core==11.1; sys_platform == 'darwin'
pyobjc-framework-Cocoa==11.1; sys_platform == 'darwin'
# Linux notifications
notify2==0.3.1; sys_platform != 'win32' and sys_platform != 'darwin'
@@ -64,8 +64,8 @@ markdown==3.9
paho-mqtt==1.6.1 # Pinned, newer versions don't work with AppRise yet
# Requests Requirements
charset_normalizer==3.4.4
idna==3.11
charset_normalizer==3.4.3
idna==3.10
urllib3==2.5.0
certifi==2025.10.5
oauthlib==3.3.1

0
sabnzbd/deobfuscate_filenames.py Executable file → Normal file
View File

View File

@@ -194,10 +194,9 @@ def parse_par2_file(fname: str, md5of16k: Dict[bytes, str]) -> Tuple[str, Dict[s
for i in range(48, pack_len - 32, 20):
filecrc32[fileid].append(struct.unpack("<I", data[i + 16 : i + 20])[0])
# On large files, we stop after seeing all the listings and have crc32 data for all listings
# Our unit-tests do not include large par2 files, so we cannot verify cases like #3164!
# On large files, we stop after seeing all the listings
# On smaller files, we scan them fully to get the par2-creator
if total_size > SCAN_LIMIT and len(filepar2info) == nr_files == len(filecrc32):
if total_size > SCAN_LIMIT and len(filepar2info) == nr_files:
break
# Process all the data

View File

@@ -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):

View File

@@ -840,9 +840,6 @@ SKIN_TEXT = {
"Glitter-backToQueue": TT("Send back to queue"),
"Glitter-purgeOrphaned": TT("Delete All"),
"Glitter-retryAllOrphaned": TT("Retry all"),
"Glitter-clearOrphanWarning": TT(
"Are you sure you want to delete all folders in your Temporary Download Folder? This cannot be undone!"
),
"Glitter-deleteJobAndFolders": TT("Remove NZB & Delete Files"),
"Glitter-addFromURL": TT("Fetch NZB from URL"),
"Glitter-addFromFile": TT("Upload NZB"),
@@ -919,7 +916,6 @@ SKIN_TEXT = {
"wizard-goto": TT("Go to SABnzbd"), #: Wizard step
"wizard-exit": TT("Exit SABnzbd"), #: Wizard EXIT button on first page
"wizard-start": TT("Start Wizard"), #: Wizard START button on first page
"wizard-test-server-required": TT("Click on Test Server before continuing"), #: Tooltip for disabled Next button
"restore-backup": TT("Restore backup"),
# Special
"yourRights": TT(

View File

@@ -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:

View File

@@ -6,5 +6,5 @@
# You MUST use double quotes (so " and not ')
# Do not forget to update the appdata file for every major release!
__version__ = "4.5.5"
__baseline__ = "a61a5539a7e0e0dc1f9ae140222436ba8f9fe679"
__version__ = "4.6.0-dev"
__baseline__ = "unknown"

View File

@@ -10,7 +10,7 @@ pytest-httpserver
flaky
xmltodict
tavern
tavern==3.0.0; python_version >= '3.11' # Latest version only supported on Python 3.11 and above
tavern==3.0.0a9; python_version >= '3.11' # Latest version only supported on Python 3.11 and above
flask
tavalidate
importlib_metadata

View File

@@ -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():