Browse Source

Sync with 0.6.x

tags/0.6.0
ShyPike 15 years ago
parent
commit
b10e3a19be
  1. 20
      CHANGELOG.txt
  2. 11
      ISSUES.txt
  3. 28
      SABHelper.py
  4. 90
      SABnzbd.py
  5. 1
      language/nl-du.txt
  6. 1
      language/us-en.txt
  7. 2
      sabnzbd/cfg.py
  8. 35
      sabnzbd/dirscanner.py
  9. 4
      sabnzbd/encoding.py
  10. 5
      sabnzbd/interface.py
  11. 2
      sabnzbd/misc.py
  12. 6
      sabnzbd/newsunpack.py
  13. 26
      sabnzbd/newzbin.py
  14. 20
      sabnzbd/nzbqueue.py
  15. 12
      sabnzbd/nzbstuff.py
  16. 3
      sabnzbd/postproc.py
  17. 2
      sabnzbd/rss.py
  18. 3
      sabnzbd/scheduler.py

20
CHANGELOG.txt

@ -1,5 +1,23 @@
-------------------------------------------------------------------------------
0.5.2 RC2 by The SABnzbd-Team
0.5.2 RC1 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fixed: Large queue caused very slow UI and high memory usage
- Fixed: Some very large rar sets failed to unpack
- Fixed: garbled emails were sent
- Python user scripts now run properly on OSX
- Fixed: SeasonSort could produce trailing spaces, which fail on Windows
- Fixed: unpacking errors not properly reported to user script
- Keep trying when receiving garbage from nzbmatrix.com
- Add total-size fields to API-call History.
- Auto-search for free port when specified port is occupied
- Improve reporting of newzbin errors
-------------------------------------------------------------------------------
0.5.1 Final by The SABnzbd-Team
-------------------------------------------------------------------------------
- Fix accented character problems in emails
- Warn when user doesn't have permission to use a web port (Linux/OSX)
-------------------------------------------------------------------------------
0.5.1 RC2 by The SABnzbd-Team
-------------------------------------------------------------------------------
- Accept comma-separated email address in "email_to" option.
- Allow manual retry of URL-based NZB fetches when a bad NZB is received

11
ISSUES.txt

@ -14,6 +14,12 @@
in Config->Switches.
This will force the use of the old and tried, but slower par2-classic program.
- A bug in Windows 7 may cause severe memory leaks when you use SABnzbd in
combination with some virus scanners and firewals.
Install this hotfix:
Description: http://support.microsoft.com/kb/979223/en-us
Download location: http://support.microsoft.com/hotfix/KBHotfix.aspx?kbnum=979223&kbln=en-us
- Windows cannot handle pathnames longer than 254 characters.
Currently, SABnzbd doesn't handle this problem gracefully.
We have added the INI-only option "folder_length_max" in which you can set
@ -62,8 +68,3 @@
make sure that the drives are mounted.
The operating system wil simply redirect your files to alternative locations.
You may have trouble finding the files when mounting the drive later.
- Sometimes logging stops. This is a notorious bug in Python logging. SABnzbd will
function as normal. If you run from sources or use the --console option with the
Win32-binary, you will see that logging continues in the console window, but
nothing will be written to the log-files.

28
SABHelper.py

@ -43,25 +43,43 @@ def HandleCommandLine(allow_service=True):
""" Handle command line for a Windows Service
Prescribed name that will be called by Py2Exe.
You MUST set 'cmdline_style':'custom' in the package.py!
Returns True when any service commands were detected.
"""
win32serviceutil.HandleCommandLine(SABHelper)
def start_sab():
return subprocess.Popen('net start SABnzbd', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).stdout.read()
#------------------------------------------------------------------------------
def main():
mail = MailSlot()
if not mail.create(200):
if not mail.create(10):
return '- Cannot create Mailslot'
active = False # SABnzbd should be running
counter = 0 # Time allowed for SABnzbd to be silent
while True:
msg = mail.receive()
if msg == 'restart':
time.sleep(1.0)
res = subprocess.Popen('net start SABnzbd', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).stdout.read()
counter = 0
start_sab()
elif msg == 'stop':
active = False
elif msg == 'active':
active = True
counter = 0
if active:
counter += 1
if counter > 120: # 120 seconds
counter = 0
start_sab()
rc = win32event.WaitForMultipleObjects((WIN_SERVICE.hWaitStop,
WIN_SERVICE.overlapped.hEvent), 0, 500)
WIN_SERVICE.overlapped.hEvent), 0, 1000)
if rc == win32event.WAIT_OBJECT_0:
mail.disconnect()
return ''
@ -79,7 +97,7 @@ class SABHelper(win32serviceutil.ServiceFramework):
_svc_display_name_ = 'SABnzbd Helper'
_svc_deps_ = ["EventLog", "Tcpip"]
_svc_description_ = 'Automated downloading from Usenet. ' \
'This service helps SABnzbdcd.. to restart itself.'
'This service helps SABnzbd to restart itself.'
def __init__(self, args):
global WIN_SERVICE

90
SABnzbd.py

@ -113,6 +113,44 @@ def guard_loglevel():
#------------------------------------------------------------------------------
# Improved RotatingFileHandler
# See: http://www.mail-archive.com/python-bugs-list@python.org/msg53913.html
# http://bugs.python.org/file14420/NTSafeLogging.py
# Thanks Erik Antelman
#
if sabnzbd.WIN32:
import msvcrt
import _subprocess
import codecs
def duplicate(handle, inheritable=False):
target_process = _subprocess.GetCurrentProcess()
return _subprocess.DuplicateHandle(
_subprocess.GetCurrentProcess(), handle, target_process,
0, inheritable, _subprocess.DUPLICATE_SAME_ACCESS).Detach()
class MyRotatingFileHandler(logging.handlers.RotatingFileHandler):
def _open(self):
"""
Open the current base file with the (original) mode and encoding.
Return the resulting stream.
"""
if self.encoding is None:
stream = open(self.baseFilename, self.mode)
newosf = duplicate(msvcrt.get_osfhandle(stream.fileno()), inheritable=False)
newFD = msvcrt.open_osfhandle(newosf,os.O_APPEND)
newstream = os.fdopen(newFD,self.mode)
stream.close()
return newstream
else:
stream = codecs.open(self.baseFilename, self.mode, self.encoding)
return stream
else:
MyRotatingFileHandler = logging.handlers.RotatingFileHandler
#------------------------------------------------------------------------------
class FilterCP3:
### Filter out all CherryPy3-Access logging that we receive,
### because we have the root logger
@ -370,17 +408,21 @@ def GetProfileInfo(vista_plus):
pass
elif sabnzbd.DARWIN:
sabnzbd.DIR_APPDATA = '%s/Library/Application Support/SABnzbd' % (os.environ['HOME'])
sabnzbd.DIR_LCLDATA = sabnzbd.DIR_APPDATA
sabnzbd.DIR_HOME = os.environ['HOME']
ok = True
home = os.environ.get('HOME')
if home:
sabnzbd.DIR_APPDATA = '%s/Library/Application Support/SABnzbd' % home
sabnzbd.DIR_LCLDATA = sabnzbd.DIR_APPDATA
sabnzbd.DIR_HOME = home
ok = True
else:
# Unix/Linux
sabnzbd.DIR_APPDATA = '%s/.%s' % (os.environ['HOME'], DEF_WORKDIR)
sabnzbd.DIR_LCLDATA = sabnzbd.DIR_APPDATA
sabnzbd.DIR_HOME = os.environ['HOME']
ok = True
home = os.environ.get('HOME')
if home:
sabnzbd.DIR_APPDATA = '%s/.%s' % (home, DEF_WORKDIR)
sabnzbd.DIR_LCLDATA = sabnzbd.DIR_APPDATA
sabnzbd.DIR_HOME = home
ok = True
if not ok:
panic("Cannot access the user profile.",
@ -651,7 +693,7 @@ def cherrypy_logging(log_path):
# Make a new RotatingFileHandler for the error log.
fname = getattr(log, "rot_error_file", log_path)
h = logging.handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount)
h = MyRotatingFileHandler(fname, 'a', maxBytes, backupCount)
h.setLevel(logging.DEBUG)
h.setFormatter(cherrypy._cplogging.logfmt)
log.error_log.addHandler(h)
@ -970,7 +1012,7 @@ def main():
try:
sabnzbd.LOGFILE = os.path.join(logdir, DEF_LOG_FILE)
logsize = sabnzbd.cfg.log_size.get_int()
rollover_log = logging.handlers.RotatingFileHandler(\
rollover_log = MyRotatingFileHandler(\
sabnzbd.LOGFILE, 'a+',
logsize,
sabnzbd.cfg.log_backups())
@ -1224,6 +1266,17 @@ def main():
check_latest_version()
autorestarted = False
mail = None
if sabnzbd.WIN_SERVICE:
mail = MailSlot()
if mail.connect():
logging.info('Connected to the SABHelper service')
mail.send('active')
else:
logging.error('Cannot reach the SABHelper service')
mail = None
# Have to keep this running, otherwise logging will terminate
timer = 0
while not sabnzbd.SABSTOP:
@ -1259,6 +1312,9 @@ def main():
if not sabnzbd.check_all_tasks():
autorestarted = True
cherrypy.engine.execv = True
# Notify guardian
if sabnzbd.WIN_SERVICE and mail:
mail.send('active')
else:
timer += 1
@ -1289,20 +1345,18 @@ def main():
pid = os.fork()
if pid == 0:
os.execv(sys.executable, args)
elif sabnzbd.WIN_SERVICE:
logging.info('Asking the SABnzbdHelper service for a restart')
mail = MailSlot()
if mail.connect():
mail.send('restart')
mail.disconnect()
else:
logging.error('Cannot reach the SABnzbdHelper service')
elif sabnzbd.WIN_SERVICE and mail:
logging.info('Asking the SABHelper service for a restart')
mail.send('restart')
mail.disconnect()
return
else:
cherrypy.engine._do_execv()
config.save_config()
if sabnzbd.WIN_SERVICE and mail:
mail.send('stop')
notify("SAB_Shutdown", None)
osx.sendGrowlMsg('SABnzbd',T('grwl-shutdown-end-msg'),osx.NOTIFICATION['startup'])
logging.info('Leaving SABnzbd')

1
language/nl-du.txt

@ -368,6 +368,7 @@ sch-speedlimit Maximum snelheid
sch-pause_all Alles pauzeren
sch-pause_post Pauzeer nabewerken
sch-resume_post Hervat nabewerken
sch-scan_folder Bewaakte map uitlezen
# Config->RSS

1
language/us-en.txt

@ -392,6 +392,7 @@ sch-speedlimit Speedlimit
sch-pause_all Pause All
sch-pause_post Pause post-processing
sch-resume_post Resume post-processing
sch-scan_folder Scan watched folder
# Config->RSS
configRSS RSS Configuration

2
sabnzbd/cfg.py

@ -149,7 +149,7 @@ cache_dir = OptionDir('misc', 'cache_dir', 'cache', validation=validate_safedir)
admin_dir = OptionDir('misc', 'admin_dir', 'admin', validation=validate_safedir)
#log_dir = OptionDir('misc', 'log_dir', 'logs')
dirscan_dir = OptionDir('misc', 'dirscan_dir', create=False)
dirscan_speed = OptionNumber('misc', 'dirscan_speed', DEF_SCANRATE, 1, 3600)
dirscan_speed = OptionNumber('misc', 'dirscan_speed', DEF_SCANRATE, 0, 3600)
SIZE_LIMIT = OptionStr('misc', 'size_limit')
cherryhost = OptionStr('misc','host', DEF_HOST)

35
sabnzbd/dirscanner.py

@ -228,6 +228,7 @@ class DirScanner(threading.Thread):
self.error_reported = False # Prevents mulitple reporting of missing watched folder
self.dirscan_dir = cfg.dirscan_dir.get_path()
self.dirscan_speed = cfg.dirscan_speed()
self.busy = False
cfg.dirscan_dir.callback(self.newdir)
DirScanner.do = self
@ -247,6 +248,20 @@ class DirScanner(threading.Thread):
sabnzbd.save_admin((self.dirscan_dir, self.ignored, self.suspected), sabnzbd.SCAN_FILE_NAME)
def run(self):
logging.info('Dirscanner starting up')
self.shutdown = False
while not self.shutdown:
# Use variable scan delay
x = max(self.dirscan_speed, 1)
while (x > 0) and not self.shutdown:
time.sleep(1.0)
x = x - 1
if self.dirscan_speed and not self.shutdown:
self.scan()
def scan(self):
def run_dir(folder, catdir):
try:
files = os.listdir(folder)
@ -326,18 +341,10 @@ class DirScanner(threading.Thread):
CleanList(self.ignored, folder, files)
CleanList(self.suspected, folder, files)
logging.info('Dirscanner starting up')
self.shutdown = False
while not self.shutdown:
# Use variable scan delay
if not self.busy:
self.busy = True
dirscan_dir = self.dirscan_dir
x = self.dirscan_speed
while (x > 0) and not self.shutdown:
time.sleep(1.0)
x = x - 1
if dirscan_dir and not self.shutdown and not sabnzbd.PAUSED_ALL:
if dirscan_dir and not sabnzbd.PAUSED_ALL:
run_dir(dirscan_dir, None)
try:
@ -353,4 +360,10 @@ class DirScanner(threading.Thread):
dpath = os.path.join(dirscan_dir, dd)
if os.path.isdir(dpath) and dd.lower() in cats:
run_dir(dpath, dd.lower())
self.busy = False
def dirscan():
""" Wrapper required for scheduler """
logging.info('Scheduled folder scan')
DirScanner.do.scan()

4
sabnzbd/encoding.py

@ -49,7 +49,7 @@ def platform_encode(p):
if gUTF:
return p.encode('utf-8')
else:
return p.encode('latin-1', 'replace').replace('?', '_')
return p.encode('latin-1', 'replace')
elif isinstance(p, basestring):
if gUTF:
try:
@ -59,7 +59,7 @@ def platform_encode(p):
return p.decode('latin-1').encode('utf-8')
else:
try:
return p.decode('utf-8').encode('latin-1', 'replace').replace('?', '_')
return p.decode('utf-8').encode('latin-1', 'replace')
except:
return p
else:

5
sabnzbd/interface.py

@ -2132,7 +2132,8 @@ class ConfigRss:
#------------------------------------------------------------------------------
_SCHED_ACTIONS = ('resume', 'pause', 'pause_all', 'shutdown', 'restart', 'speedlimit', 'pause_post', 'resume_post')
_SCHED_ACTIONS = ('resume', 'pause', 'pause_all', 'shutdown', 'restart', 'speedlimit',
'pause_post', 'resume_post', 'scan_folder')
class ConfigScheduling:
def __init__(self, web_dir, root, prim):
@ -2182,7 +2183,7 @@ class ConfigScheduling:
act = ''
if act in ('enable_server', 'disable_server'):
action = T("sch-" + act) + ' ' + server
item = (snum, h, '%02d' % int(m), days[day], action)
item = (snum, h, '%02d' % int(m), days.get(day, '**'), action)
conf['taskinfo'].append(item)
snum += 1

2
sabnzbd/misc.py

@ -978,7 +978,7 @@ def bad_fetch(nzo, url, msg='', retry=False, content=False):
msg = T('his-badArchive')
else:
# Failed fetch
msg = T('his-failedURL')
msg = ' (' + msg + ')'
if retry:
nzbname = nzo.get_dirname_rename()

6
sabnzbd/newsunpack.py

@ -354,7 +354,6 @@ def file_join(nzo, workdir, workdir_complete, delete, joinables):
#------------------------------------------------------------------------------
def rar_unpack(nzo, workdir, workdir_complete, delete, rars):
errors = False
extracted_files = []
rar_sets = {}
@ -385,10 +384,9 @@ def rar_unpack(nzo, workdir, workdir_complete, delete, rars):
try:
newfiles, rars = RAR_Extract(rarpath, len(rar_sets[rar_set]),
nzo, rar_set, extraction_path)
success = True
success = newfiles and rars
except:
success = False
errors = True
msg = sys.exc_info()[1]
nzo.set_fail_msg(T('error-unpackFail@1') % msg)
setname = nzo.get_dirname()
@ -419,7 +417,7 @@ def rar_unpack(nzo, workdir, workdir_complete, delete, rars):
except OSError:
logging.warning(Ta('warn-delFailed@1'), brokenrar)
return errors, extracted_files
return not success, extracted_files
def RAR_Extract(rarfile, numrars, nzo, setname, extraction_path):

26
sabnzbd/newzbin.py

@ -132,7 +132,7 @@ class MSGIDGrabber(Thread):
sleeper(int(filename))
else:
# Fatal error, give up on this one
bad_fetch(nzo, msgid, retry=False)
bad_fetch(nzo, msgid, msg=nzo_info, retry=True)
msgid = None
osx.sendGrowlMsg(T('grwl-nzbadd-title'),filename,osx.NOTIFICATION['download'])
@ -146,7 +146,7 @@ class MSGIDGrabber(Thread):
def _grabnzb(msgid):
""" Grab one msgid from newzbin """
nothing = (None, None, None, None)
msg = ''
retry = (60, None, None, None)
nzo_info = {'msgid': msgid}
@ -188,7 +188,7 @@ def _grabnzb(msgid):
pass
if not (rcode or rtext):
logging.error(T('error-nbProtocol'))
return nothing
return None, None, None, None
# Official return codes:
# 200 = OK, NZB content follows
@ -221,27 +221,27 @@ def _grabnzb(msgid):
return int(wait+1), None, None, None
if rcode in ('402'):
logging.warning(Ta('warn-nbCredit'))
return nothing
msg = Ta('warn-nbCredit')
return None, None, None, msg
if rcode in ('401'):
logging.warning(Ta('warn-nbNoAuth'))
return nothing
msg = Ta('warn-nbNoAuth')
return None, None, None, msg
if rcode in ('400', '404'):
logging.error(Ta('error-nbReport@1'), msgid)
return nothing
msg = Ta('error-nbReport@1') % msgid
return None, None, None, msg
if rcode != '200':
logging.error(Ta('error-nbUnkownError@2'), rcode, rtext)
return nothing
msg = Ta('error-nbUnkownError@2') % (rcode, rtext)
return None, None, None, msg
# Process data
report_name = response.getheader('X-DNZB-Name')
report_cat = response.getheader('X-DNZB-Category')
if not (report_name and report_cat):
logging.error(Ta('error-nbInfo@1'), msgid)
return nothing
msg = Ta('error-nbInfo@1') % msgid
return None, None, None, msg
# sanitize report_name
newname = sanitize_foldername(report_name)

20
sabnzbd/nzbqueue.py

@ -99,8 +99,8 @@ class NzbQueue(TryList):
self.add(nzo, save = False)
@synchronized(NZBQUEUE_LOCK)
def save(self):
""" Save queue """
def save(self, save_nzo=None):
""" Save queue, all nzo's or just the specified one """
logging.info("Saving queue")
nzo_ids = []
@ -110,7 +110,8 @@ class NzbQueue(TryList):
nzo_ids.append(os.path.join(nzo.get_workdir(), nzo.nzo_id))
else:
nzo_ids.append(nzo.nzo_id)
sabnzbd.save_data(nzo, nzo.nzo_id, nzo.get_workpath())
if save_nzo is None or nzo is save_nzo:
sabnzbd.save_data(nzo, nzo.nzo_id, nzo.get_workpath())
sabnzbd.save_admin((QUEUE_VERSION, nzo_ids, []), QUEUE_FILE_NAME)
@ -151,7 +152,7 @@ class NzbQueue(TryList):
try:
future.__init__(filename, msgid, pp, scr, nzb=data, futuretype=False, cat=categ, priority=priority, nzbname=nzbname, nzo_info=nzo_info)
future.nzo_id = nzo_id
self.save()
self.save(future)
except ValueError:
self.remove(nzo_id, False)
except TypeError:
@ -245,7 +246,7 @@ class NzbQueue(TryList):
#if the queue is empty then simple append the item to the bottom
self.__nzo_list.append(nzo)
if save:
self.save()
self.save(nzo)
if nzo.get_status() not in ('Fetching',):
osx.sendGrowlMsg(T('grwl-nzbadd-title'),nzo.get_filename(),osx.NOTIFICATION['download'])
@ -274,14 +275,15 @@ class NzbQueue(TryList):
self.cleanup_nzo(nzo)
if save:
self.save()
self.save(nzo)
@synchronized(NZBQUEUE_LOCK)
def remove_multiple(self, nzo_ids):
for nzo_id in nzo_ids:
self.remove(nzo_id, add_to_history = False, save = False)
self.save()
# Save with invalid nzo_id, to that only queue file is saved
self.save('x')
@synchronized(NZBQUEUE_LOCK)
def remove_all(self):
@ -891,9 +893,9 @@ def reset_all_try_lists():
global __NZBQ
if __NZBQ: __NZBQ.reset_all_try_lists()
def save():
def save(nzo=None):
global __NZBQ
if __NZBQ: __NZBQ.save()
if __NZBQ: __NZBQ.save(nzo)
def generate_future(msg, pp, script, cat, url, priority, nzbname):
global __NZBQ

12
sabnzbd/nzbstuff.py

@ -822,13 +822,17 @@ class NzbObject(TryList):
for nzf in self.__files:
# Don't try to get an article if server is in try_list of nzf
if not nzf.server_in_try_list(server):
if not nzf.import_finished:
# Only load NZF when it's a primary server
# or when it's a backup server without active primaries
if server.fillserver ^ sabnzbd.active_primaries():
nzf.finish_import()
# Still not finished? Something went wrong...
if not nzf.import_finished:
logging.error(Ta('error-qImport@1'), nzf)
nzf_remove_list.append(nzf)
continue
else:
continue
article = nzf.get_article(server)
if article:
@ -837,12 +841,10 @@ class NzbObject(TryList):
for nzf in nzf_remove_list:
self.__files.remove(nzf)
if article:
return article
else:
if not article:
# No articles for this server, block for next time
self.add_to_try_list(server)
return
return article
def move_top_bulk(self, nzf_ids):
self.__cleanup_nzf_ids(nzf_ids)

3
sabnzbd/postproc.py

@ -526,9 +526,8 @@ def addPrefixes(path,nzo):
def HandleEmptyQueue():
""" Check if empty queue calls for action """
sabnzbd.save_state()
if sabnzbd.QUEUECOMPLETEACTION_GO:
sabnzbd.save_state()
logging.info("Queue has finished, launching: %s (%s)", \
sabnzbd.QUEUECOMPLETEACTION, sabnzbd.QUEUECOMPLETEARG)
if sabnzbd.QUEUECOMPLETEARG:

2
sabnzbd/rss.py

@ -250,7 +250,7 @@ class RSSQueue:
return T('warn-failRSS@2') % (uri, str(d['bozo_exception']))
if not entries:
logging.info('RSS Feed was empty: %s', uri)
return 'RSS Feed was empty'
return ''
order = 0
# Filter out valid new links

3
sabnzbd/scheduler.py

@ -29,6 +29,7 @@ import sabnzbd.utils.kronos as kronos
import sabnzbd.rss as rss
from sabnzbd.newzbin import Bookmarks
import sabnzbd.downloader
import sabnzbd.dirscanner
import sabnzbd.misc
import sabnzbd.config as config
import sabnzbd.cfg as cfg
@ -110,6 +111,8 @@ def init():
action = sabnzbd.enable_server
elif action_name == 'disable_server' and arguments != []:
action = sabnzbd.disable_server
elif action_name == 'scan_folder':
action = sabnzbd.dirscanner.dirscan
else:
logging.warning(Ta('warn-badSchedAction@1'), action_name)
continue

Loading…
Cancel
Save