diff --git a/SABHelper.py b/SABHelper.py new file mode 100644 index 0000000..b52c57a --- /dev/null +++ b/SABHelper.py @@ -0,0 +1,122 @@ +#!/usr/bin/python -OO +# Copyright 2008-2010 The SABnzbd-Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import sys +if sys.version_info < (2,4): + print "Sorry, requires Python 2.4 or higher." + sys.exit(1) + +import logging +import logging.handlers +import os +import getopt +import signal +import glob +import socket +import platform +import time +import subprocess + +#------------------------------------------------------------------------------ +try: + import win32api, win32file + import win32serviceutil, win32evtlogutil, win32event, win32service, pywintypes +except ImportError: + if sabnzbd.WIN32: + print "Sorry, requires Python module PyWin32." + sys.exit(1) + +from sabnzbd.utils.mailslot import MailSlot + +#------------------------------------------------------------------------------ + +WIN_SERVICE = None + +#------------------------------------------------------------------------------ +def main(): + + mail = MailSlot() + if not mail.create(200): + return '- Cannot create Mailslot' + + 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() + rc = win32event.WaitForMultipleObjects((WIN_SERVICE.hWaitStop, + WIN_SERVICE.overlapped.hEvent), 0, 500) + if rc == win32event.WAIT_OBJECT_0: + mail.disconnect() + return '' + + +##################################################################### +# +# Windows Service Support +# +import servicemanager +class SABnzbdHelper(win32serviceutil.ServiceFramework): + """ Win32 Service Handler """ + + _svc_name_ = 'SABnzbdHelper' + _svc_display_name_ = 'SABnzbd Binary Newsreader (Helper)' + _svc_deps_ = ["EventLog", "Tcpip"] + _svc_description_ = 'Automated downloading from Usenet. ' \ + 'This service helps SABnzbd to restart itself.' + + def __init__(self, args): + global WIN_SERVICE + win32serviceutil.ServiceFramework.__init__(self, args) + + self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) + self.overlapped = pywintypes.OVERLAPPED() + self.overlapped.hEvent = win32event.CreateEvent(None, 0, 0, None) + WIN_SERVICE = self + + def SvcDoRun(self): + msg = 'SABnzbdHelper-service' + self.Logger(servicemanager.PYS_SERVICE_STARTED, msg + ' has started') + res = main() + self.Logger(servicemanager.PYS_SERVICE_STOPPED, msg + ' has stopped' + res) + + def SvcStop(self): + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + win32event.SetEvent(self.hWaitStop) + + def Logger(self, state, msg): + win32evtlogutil.ReportEvent(self._svc_display_name_, + state, 0, + servicemanager.EVENTLOG_INFORMATION_TYPE, + (self._svc_name_, unicode(msg))) + + def ErrLogger(self, msg, text): + win32evtlogutil.ReportEvent(self._svc_display_name_, + servicemanager.PYS_SERVICE_STOPPED, 0, + servicemanager.EVENTLOG_ERROR_TYPE, + (self._svc_name_, unicode(msg)), + unicode(text)) + + + +##################################################################### +# +# Platform specific startup code +# +if __name__ == '__main__': + + win32serviceutil.HandleCommandLine(SABnzbdHelper, argv=sys.argv) diff --git a/SABnzbd.py b/SABnzbd.py index ea6c76d..c5c0105 100755 --- a/SABnzbd.py +++ b/SABnzbd.py @@ -97,7 +97,10 @@ try: import win32api import win32serviceutil, win32evtlogutil, win32event, win32service, pywintypes win32api.SetConsoleCtrlHandler(sabnzbd.sig_handler, True) + from sabnzbd.utils.mailslot import MailSlot except ImportError: + class MailSlot: + pass if sabnzbd.WIN32: print "Sorry, requires Python module PyWin32." sys.exit(1) @@ -1290,8 +1293,14 @@ def main(): if pid == 0: os.execv(sys.executable, args) elif sabnzbd.WIN_SERVICE: - # Hope for the service manager to restart us - sys.exit(1) + 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') + return else: cherrypy.engine._do_execv() diff --git a/sabnzbd/utils/mailslot.py b/sabnzbd/utils/mailslot.py new file mode 100644 index 0000000..874bcc8 --- /dev/null +++ b/sabnzbd/utils/mailslot.py @@ -0,0 +1,130 @@ +#!/usr/bin/python -OO +# Copyright 2008-2010 The SABnzbd-Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +""" +sabnzbd.mailslot - Mailslot communication +""" + +import os +from win32file import GENERIC_WRITE, FILE_SHARE_READ, \ + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL +from ctypes import c_uint, c_buffer, byref, sizeof, windll + +# Win32API Shortcuts +CreateFile = windll.kernel32.CreateFileA +ReadFile = windll.kernel32.ReadFile +WriteFile = windll.kernel32.WriteFile +CloseHandle = windll.kernel32.CloseHandle +CreateMailslot = windll.kernel32.CreateMailslotA + + +class MailSlot(object): + """ Simple Windows Mailslot communication + """ + slotname = r'mailslot\SABnzbd\ServiceSlot' + + def __init__(self): + self.handle = -1 + + def create(self, timeout): + """ Create the Mailslot, after this only receiving is possible + timeout is the read timeout used for receive calls. + """ + slot = r'\\.\%s' % MailSlot.slotname + self.handle = CreateMailslot(slot, 50, timeout, None) + + return self.handle != -1 + + def connect(self): + """ Connect to existing Mailslot so that writing is possible + """ + slot = r'\\%s\%s' % (os.environ['COMPUTERNAME'], MailSlot.slotname) + self.handle = CreateFile(slot, GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) + return self.handle != -1 + + def disconnect(self): + """ Disconnect from Mailslot + """ + if self.handle != -1: + CloseHandle(self.handle) + self.handle = -1 + return True + + def send(self, command): + """ Send one message to Mailslot + """ + if self.handle == -1: + return False + w = c_uint() + return bool(WriteFile(self.handle, command, len(command), byref(w), 0)) + + def receive(self): + """ Receive one message from Mailslot + """ + r = c_uint() + buf = c_buffer(1024) + if ReadFile(self.handle, buf, sizeof(buf), byref(r), 0): + return buf.value + else: + return None + + +#------------------------------------------------------------------------------ +# Simple test +# +# First start "mailslot.py server" in one process, +# Then start "mailslot.py client" in another. +# Five "restart" and one "stop" will be send from client to server. +# The server will stop after receiving "stop" + +if __name__ == '__main__': + import sys + from time import sleep + + if not __debug__: + print 'Run this test in non-optimized mode' + exit(1) + + if len(sys.argv) > 1 and 'server' in sys.argv[1]: + + recv = MailSlot() + ret = recv.create(2) + assert ret, 'Failed to create' + while True: + data = recv.receive() + if data is not None: + print data + if data.startswith('stop'): + break + sleep(2.0) + recv.disconnect() + + elif len(sys.argv) > 1 and 'client' in sys.argv[1]: + + send = MailSlot() + ret = send.connect() + assert ret, 'Failed to connect' + for n in xrange(5): + ret = send.send('restart') + assert ret, 'Failed to send' + sleep(2.0) + send.send('stop') + assert ret, 'Failed to send' + send.disconnect() + + else: + print 'Usage: mailslot.py server|client'