Compare commits

...

18 Commits
3.0.1 ... 3.0.x

Author SHA1 Message Date
Safihre
bc4f06dd1d Limit feedparser<6.0.0 for 3.0.x 2020-09-13 16:40:14 +02:00
Safihre
51cc765949 Update text files for 3.0.2 2020-08-30 20:50:45 +02:00
Safihre
19c6a4fffa Propagation delay label was shown even if no delay was activated 2020-08-29 16:46:16 +02:00
Safihre
105ac32d2f Reading RSS feed with no categories set could result in crash
Closes #1589
2020-08-28 10:16:49 +02:00
Safihre
57550675d2 Removed logging in macOS sabApp that resulted in double logging 2020-08-28 10:16:41 +02:00
Safihre
e674abc5c0 Update text files for 3.0.2RC2 2020-08-26 08:56:29 +02:00
Safihre
f965c96f51 Change the macOS power assertion to NoIdleSleep 2020-08-26 08:50:54 +02:00
Safihre
c76b8ed9e0 End-of-queue-script did not run on Windows due to long-path
https://forums.sabnzbd.org/viewtopic.php?f=3&t=24918

Will refactor this so they all call 1 function.
2020-08-24 11:28:14 +02:00
Safihre
4fbd0d8a7b Check if name is a string before switching to nzbfile in addfile
Closes #1584
2020-08-24 09:05:25 +02:00
Safihre
2186c0fff6 Update text files for 3.0.2 RC 1 2020-08-21 15:42:35 +02:00
Safihre
1adca9a9c1 Do not crash if certifi certificates are not available
This could happen on Windows, due to overactive virus scanners
2020-08-21 15:26:06 +02:00
Safihre
9408353f2b Priority was not parsed correctly if supplied as string 2020-08-21 15:12:09 +02:00
Safihre
84f4d453d2 Permissions would be set even if user didn't set any
Windows developers like me shouldn't do permissions stuff..
2020-08-21 15:12:01 +02:00
Safihre
d10209f2a1 Extend tests of create_all_dirs to cover apply_umask=False 2020-08-21 15:11:53 +02:00
Safihre
3ae149c72f Split the make_mo.py command for NSIS 2020-08-19 22:21:02 +02:00
Safihre
47385acc3b Make sure we force the final_name to string on legacy get_attrib_file 2020-08-19 16:21:13 +02:00
Safihre
814eeaa900 Redesigned the saving of attributes
Now uses pickle, so that the type of the property is preserved.
Made flexible, so that more properties can be easily added later.
Closes #1575
2020-08-19 16:21:07 +02:00
Safihre
5f2ea13aad NzbFile comparison could crash when comparing finished_files
https://forums.sabnzbd.org/viewtopic.php?f=3&t=24902&p=121748
2020-08-19 08:50:06 +02:00
15 changed files with 145 additions and 74 deletions

View File

@@ -1,5 +1,5 @@
*******************************************
*** This is SABnzbd 3.0.1 ***
*** This is SABnzbd ***
*******************************************
SABnzbd is an open-source cross-platform binary newsreader.

View File

@@ -1,7 +1,7 @@
Metadata-Version: 1.0
Name: SABnzbd
Version: 3.0.1
Summary: SABnzbd-3.0.1
Version: 3.0.2
Summary: SABnzbd-3.0.2
Home-page: https://sabnzbd.org
Author: The SABnzbd Team
Author-email: team@sabnzbd.org

View File

@@ -1,6 +1,18 @@
Release Notes - SABnzbd 3.0.1
Release Notes - SABnzbd 3.0.2
=========================================================
## Bugfixes since 3.0.1
- Priority was not parsed correctly if supplied as through the API.
- API-call `addfile` could fail if `name` and `nzbfile` were used.
- Permissions were still not set correctly when creating directories.
- Propagation delay label was shown even if no delay was activated.
- Reading RSS feed with no categories set could result in crash.
- Jobs with numeric names could crash post-processing.
- Jobs with missing articles could result in crash.
- macOS: changed the power assertion to `NoIdleSleep`.
- Windows: end-of-queue-script did not run on Windows.
- Windows: crash if the virus scanner removed the certificate bundle.
## Bugfixes since 3.0.0
- Basic Authentication resulted in crash.
- Permissions were not set correctly when creating directories.

View File

@@ -1168,9 +1168,14 @@ def main():
if importlib.util.find_spec("certifi") is not None:
import certifi
os.environ["SSL_CERT_FILE"] = certifi.where()
logging.info("Certifi version: %s", certifi.__version__)
logging.info("Loaded additional certificates from: %s", os.environ["SSL_CERT_FILE"])
try:
os.environ["SSL_CERT_FILE"] = certifi.where()
logging.info("Certifi version: %s", certifi.__version__)
logging.info("Loaded additional certificates from: %s", os.environ["SSL_CERT_FILE"])
except:
# Sometimes the certificate file is blocked
logging.warning(T("Could not load additional certificates from certifi package"))
logging.info("Traceback: ", exc_info=True)
# Extra startup info
if sabnzbd.cfg.log_level() > 1:
@@ -1698,9 +1703,7 @@ if __name__ == "__main__":
# This code is made with trial-and-error, please improve!
class startApp(Thread):
def run(self):
logging.info("[osx] sabApp Starting - starting main thread")
main()
logging.info("[osx] sabApp Stopping - main thread quit ")
AppHelper.stopEventLoop()
sabApp = startApp()

View File

@@ -1,7 +1,7 @@
sabyenc3>=4.0.0
cheetah3>=3.0.0
cryptography
feedparser
feedparser<6.0.0
configobj
cheroot<8.4.3
cherrypy

View File

@@ -848,10 +848,10 @@ def change_queue_complete_action(action, new=True):
def run_script(script):
""" Run a user script (queue complete only) """
command = [os.path.join(cfg.script_dir.get_path(), script)]
if os.path.exists(command[0]):
script_path = filesystem.make_script_path(script)
if script_path:
try:
stup, need_shell, command, creationflags = sabnzbd.newsunpack.build_command(command)
stup, need_shell, command, creationflags = sabnzbd.newsunpack.build_command([script_path])
logging.info("Spawning external command %s", command)
subprocess.Popen(
command,

View File

@@ -350,7 +350,7 @@ def _api_translate(name, output, kwargs):
def _api_addfile(name, output, kwargs):
""" API: accepts name, output, pp, script, cat, priority, nzbname """
# Normal upload will send the nzb in a kw arg called name or nzbfile
if not name:
if not name or isinstance(name, str):
name = kwargs.get("nzbfile", None)
if hasattr(name, "file") and hasattr(name, "filename") and name.filename:
cat = kwargs.get("cat")

View File

@@ -561,13 +561,10 @@ def create_all_dirs(path, apply_umask=False):
else:
# We need to build the directory recursively so we can
# apply permissions to only the newly created folders
# We cannot use os.makedirs() to do this as it ignores the mode
try:
# Try the user permissions setting
umask = int(sabnzbd.cfg.umask(), 8) | int("0700", 8)
except:
# Use default
umask = int("0700", 8)
# We cannot use os.makedirs() as it could ignore the mode
umask = sabnzbd.cfg.umask()
if umask:
umask = int(umask, 8) | int("0700", 8)
# Build path from root
path_part_combined = "/"
@@ -578,7 +575,7 @@ def create_all_dirs(path, apply_umask=False):
if not os.path.exists(path_part_combined):
os.mkdir(path_part_combined)
# Try to set permissions if desired, ignore failures
if apply_umask:
if umask and apply_umask:
set_chmod(path_part_combined, umask, report=False)
return path
except OSError:

View File

@@ -710,15 +710,15 @@ class NzbQueue:
Not locked for performance, since it only reads the queue
"""
# Pre-calculate propagation delay
propagtion_delay = float(cfg.propagation_delay() * 60)
propagation_delay = float(cfg.propagation_delay() * 60)
for nzo in self.__nzo_list:
# Not when queue paused and not a forced item
if nzo.status not in (Status.PAUSED, Status.GRABBING) or nzo.priority == TOP_PRIORITY:
# Check if past propagation delay, or forced
if (
not propagtion_delay
not propagation_delay
or nzo.priority == TOP_PRIORITY
or (nzo.avg_stamp + propagtion_delay) < time.time()
or (nzo.avg_stamp + propagation_delay) < time.time()
):
if not nzo.server_in_try_list(server):
article = nzo.get_article(server, servers)

View File

@@ -20,6 +20,7 @@ sabnzbd.nzbstuff - misc
"""
import os
import pickle
import time
import re
import logging
@@ -481,8 +482,15 @@ class NzbFile(TryList):
self.md5 = None
def __eq__(self, other):
""" Assume it's the same file if the bytes and first article are the same """
return self.bytes == other.bytes and self.decodetable[0] == other.decodetable[0]
""" Assume it's the same file if the numer bytes and first article
are the same or if there are no articles left, use the filenames
"""
if self.bytes == other.bytes:
if self.decodetable and other.decodetable:
return self.decodetable[0] == other.decodetable[0]
# Fallback to filename comparison
return self.filename == other.filename
return False
def __hash__(self):
""" Required because we implement eq. The same file can be spread
@@ -556,6 +564,8 @@ NzbObjectSaver = (
"rating_filtered",
)
NzoAttributeSaver = ("cat", "pp", "script", "priority", "final_name", "password", "url")
# Lock to prevent errors when saving the NZO data
NZO_LOCK = threading.RLock()
@@ -707,7 +717,7 @@ class NzbObject(TryList):
self.final_name = self.final_name.replace(".", " ")
# Check against identical checksum or series/season/episode
if (not reuse) and nzb and dup_check and priority != REPAIR_PRIORITY:
if (not reuse) and nzb and dup_check and self.priority != REPAIR_PRIORITY:
duplicate, series = self.has_duplicates()
else:
duplicate = series = 0
@@ -779,14 +789,10 @@ class NzbObject(TryList):
# Pickup backed-up attributes when re-using
if reuse:
cat, pp, script, priority, name, password, self.url = get_attrib_file(self.workpath, 7)
if name:
self.final_name = name
if password:
self.password = password
cat, pp, script = self.load_attribs()
# Determine category and find pp/script values
self.cat, pp_tmp, self.script, priority = cat_to_opts(cat, pp, script, priority)
self.cat, pp_tmp, self.script, priority = cat_to_opts(cat, pp, script, self.priority)
self.set_priority(priority)
self.repair, self.unpack, self.delete = pp_to_opts(pp_tmp)
@@ -1339,8 +1345,9 @@ class NzbObject(TryList):
labels.append(T("WAIT %s sec") % dif)
# Propagation delay label
if (self.avg_stamp + float(cfg.propagation_delay() * 60)) > time.time() and self.priority != TOP_PRIORITY:
wait_time = int((self.avg_stamp + float(cfg.propagation_delay() * 60) - time.time()) / 60 + 0.5)
propagation_delay = float(cfg.propagation_delay() * 60)
if propagation_delay and self.avg_stamp + propagation_delay > time.time() and self.priority != TOP_PRIORITY:
wait_time = int((self.avg_stamp + propagation_delay - time.time()) / 60 + 0.5)
labels.append(T("PROPAGATING %s min") % wait_time) # Queue indicator while waiting for propagation of post
return labels
@@ -1892,9 +1899,36 @@ class NzbObject(TryList):
sabnzbd.save_data(self, self.nzo_id, self.workpath)
def save_attribs(self):
set_attrib_file(
self.workpath, (self.cat, self.pp, self.script, self.priority, self.final_name, self.password, self.url)
)
""" Save specific attributes for Retry """
attribs = {}
for attrib in NzoAttributeSaver:
attribs[attrib] = getattr(self, attrib)
logging.debug("Saving attributes %s for %s", attribs, self.final_name)
sabnzbd.save_data(attribs, ATTRIB_FILE, self.workpath)
def load_attribs(self):
""" Load saved attributes and return them to be parsed """
attribs = sabnzbd.load_data(ATTRIB_FILE, self.workpath, remove=False)
logging.debug("Loaded attributes %s for %s", attribs, self.final_name)
# TODO: Remove fallback to old method in SABnzbd 3.2.0
if not attribs:
cat, pp, script, self.priority, name, password, self.url = get_attrib_file(self.workpath, 7)
if name:
# Could be converted to integer due to the logic in get_attrib_file
self.final_name = str(name)
if password:
self.password = password
return cat, pp, script
# Only a subset we want to apply directly to the NZO
for attrib in ("final_name", "priority", "password", "url"):
# Only set if it is present and has a value
if attribs.get(attrib):
setattr(self, attrib, attribs[attrib])
# Rest is to be used directly in the NZO-init flow
return attribs["cat"], attribs["pp"], attribs["script"]
@synchronized(NZO_LOCK)
def build_pos_nzf_table(self, nzf_ids):
@@ -2150,15 +2184,6 @@ def get_attrib_file(path, size):
return [None for _ in range(size)]
def set_attrib_file(path, attribs):
""" Write job's attributes to file """
logging.debug("Writing attributes %s to %s", attribs, path)
path = os.path.join(path, ATTRIB_FILE)
with open(path, "w", encoding="utf-8") as attr_file:
for item in attribs:
attr_file.write("%s\n" % item)
def name_extractor(subject):
""" Try to extract a file name from a subject line, return `subject` if in doubt """
result = subject

View File

@@ -791,7 +791,7 @@ def _get_link(entry):
except AttributeError:
try: # nzb.su
category = entry.tags[0]["term"]
except (AttributeError, KeyError):
except (AttributeError, IndexError, KeyError):
try:
category = entry.description
except AttributeError:

View File

@@ -45,12 +45,16 @@ def keep_awake(reason):
Multiple calls allowed.
"""
global assertion_id
kIOPMAssertionTypeNoIdleSleep = "PreventUserIdleSystemSleep"
kIOPMAssertionLevelOn = 255
errcode, assertion_id = IOPMAssertionCreateWithName(
kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, reason, None
)
return errcode == 0
# Each assertion needs to be released, so make sure to only set it once
if not assertion_id:
kIOPMAssertionTypeNoIdleSleep = "NoIdleSleepAssertion"
kIOPMAssertionLevelOn = 255
errcode, assertion_id = IOPMAssertionCreateWithName(
kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, reason, None
)
return errcode == 0
return True
def allow_sleep():

View File

@@ -768,22 +768,32 @@ class TestCreateAllDirs(ffs.TestCase, PermissionCheckerHelper):
@set_config({"umask": "0777"})
def test_permissions_777(self):
self._permissions_runner("/test_base777", "/test_base777/se 1/ep 1", "0700")
self._permissions_runner("/test_base777")
self._permissions_runner("/test_base777_nomask", apply_umask=False)
@set_config({"umask": "0770"})
def test_permissions_770(self):
self._permissions_runner("/test_base770", "/test_base770/se 1/ep 1", "0700")
self._permissions_runner("/test_base770")
self._permissions_runner("/test_base770_nomask", apply_umask=False)
@set_config({"umask": "0600"})
def test_permissions_600(self):
self._permissions_runner("/test_base600", "/test_base600/se 1/ep 1", "0700")
self._permissions_runner("/test_base600")
self._permissions_runner("/test_base600_nomask", apply_umask=False)
@set_config({"umask": "0700"})
def test_permissions_450(self):
with pytest.raises(OSError):
self._permissions_runner("/test_base_450", "/test_base_450/se 1/ep 1", "0450")
self._permissions_runner("/test_base450", perms_base="0450")
def _permissions_runner(self, test_base, new_dir, perms_base):
@set_config({"umask": ""})
def test_no_umask(self):
self._permissions_runner("/test_base_perm700", perms_base="0700")
self._permissions_runner("/test_base_perm750", perms_base="0750")
self._permissions_runner("/test_base_perm777", perms_base="0777")
self._permissions_runner("/test_base_perm600", perms_base="0600")
def _permissions_runner(self, test_base, perms_base="0700", apply_umask=True):
# Create base directory and set the base permissions
perms_base_int = int(perms_base, 8)
self.fs.create_dir(test_base, perms_base_int)
@@ -791,11 +801,18 @@ class TestCreateAllDirs(ffs.TestCase, PermissionCheckerHelper):
self.assert_dir_perms(test_base, perms_base_int)
# Create directories with permissions
filesystem.create_all_dirs(new_dir, apply_umask=True)
new_dir = os.path.join(test_base, "se 1", "ep1")
filesystem.create_all_dirs(new_dir, apply_umask=apply_umask)
# If permissions needed to be set, verify the new folder has the
# right permissions and verify the base didn't change
perms_test_int = int(cfg.umask(), 8) | int("0700", 8)
if apply_umask and cfg.umask():
perms_test_int = int(cfg.umask(), 8) | int("0700", 8)
else:
# Get the current umask, since os.mkdir masks that out
cur_umask = os.umask(0)
os.umask(cur_umask)
perms_test_int = int("0777", 8) & ~cur_umask
self.assert_dir_perms(new_dir, perms_test_int)
self.assert_dir_perms(test_base, perms_base_int)

View File

@@ -59,3 +59,17 @@ class TestSleepless:
sleepless.allow_sleep()
assert not self.check_msg_in_assertions()
assert sleepless.assertion_id is None
def test_sleepless_multi_call(self):
# If we set it twice, is it still cleared with one call
assert not self.check_msg_in_assertions()
assert sleepless.assertion_id is None
sleepless.keep_awake(self.sleep_msg)
time.sleep(2)
sleepless.keep_awake(self.sleep_msg)
assert self.check_msg_in_assertions()
sleepless.allow_sleep()
assert not self.check_msg_in_assertions()
assert sleepless.assertion_id is None

View File

@@ -281,26 +281,25 @@ if os.path.exists(tl):
TOOL = [tl]
result = True
if len(sys.argv) > 1 and sys.argv[1] == "all":
if len(sys.argv) > 1 and sys.argv[1] == "nsis":
print("NSIS MO file")
result = result and process_po_folder(DOMAIN_N, PON_DIR)
print("Patch NSIS script")
patch_nsis()
else:
print("Email MO files")
result = result and process_po_folder(DOMAIN_E, POE_DIR)
print("Email MO files")
result = result and process_po_folder(DOMAIN_E, POE_DIR)
print("Create email templates from MO files")
make_templates()
print("Create email templates from MO files")
make_templates()
print("Main program MO files")
# -n option added to remove all newlines from the translations
result = result and process_po_folder(DOMAIN, PO_DIR, "-n")
print("Main program MO files")
# -n option added to remove all newlines from the translations
result = result and process_po_folder(DOMAIN, PO_DIR, "-n")
print("Remove temporary templates")
remove_mo_files()
print("Remove temporary templates")
remove_mo_files()
print()
if result: