#!/usr/bin/python -OO
# Copyright 2008-2010 The SABnzbd-Team <team@sabnzbd.org>
#
# 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
sys.path.insert(0,'/usr/share/sabnzbdplus')
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

try:
    import Cheetah
    if Cheetah.Version[0] != '2':
        raise ValueError
except ValueError:
    print "Sorry, requires Python module Cheetah 2.0rc7 or higher."
    sys.exit(1)
except:
    print "The following modules need to be installed: Cheetah, cherrypy(included, unpack the zip), sqlite(if python 2.4)"
    sys.exit(1)

import cherrypy
if not cherrypy.__version__.startswith("3.2"):
    print "Sorry, requires Python module Cherrypy 3.2 (use the included version)"
    sys.exit(1)

from cherrypy import _cpserver
from cherrypy import _cpwsgi_server

SQLITE_DLL = True
try:
    from sqlite3 import version as sqlite3_version
except:
    try:
        from pysqlite2.dbapi2 import version as sqlite3_version
    except:
        if os.name != 'nt':
            print "Sorry, requires Python module sqlite3 (pysqlite2 in python2.4)"
            print "Try: apt-get install python-pysqlite2"
            sys.exit(1)
        else:
            SQLITE_DLL = False

import sabnzbd
import sabnzbd.interface
from sabnzbd.constants import *
import sabnzbd.newsunpack
from sabnzbd.misc import get_user_shellfolders, launch_a_browser, real_path, \
     check_latest_version, panic_tmpl, panic_port, panic_fwall, panic_sqlite, panic, exit_sab, \
     panic_xport, notify, split_host, convert_version, get_ext, create_https_certificates, \
     windows_variant, ip_extract
import sabnzbd.scheduler as scheduler
import sabnzbd.config as config
import sabnzbd.cfg
import sabnzbd.downloader as downloader
from sabnzbd.lang import T, Ta
from sabnzbd.utils import osx

from threading import Thread

LOG_FLAG = False  # Global for this module, signalling loglevel change


#------------------------------------------------------------------------------
signal.signal(signal.SIGINT, sabnzbd.sig_handler)
signal.signal(signal.SIGTERM, sabnzbd.sig_handler)

try:
    import win32api
    win32api.SetConsoleCtrlHandler(sabnzbd.sig_handler, True)
except ImportError:
    if sabnzbd.WIN32:
        print "Sorry, requires Python module PyWin32."
        sys.exit(1)


def guard_loglevel():
    """ Callback function for guarding loglevel """
    global LOG_FLAG
    LOG_FLAG = True


#------------------------------------------------------------------------------
class FilterCP3:
    ### Filter out all CherryPy3-Access logging that we receive,
    ### because we have the root logger
    def __init__(self):
        pass
    def filter(self, record):
        _cplogging = record.module == '_cplogging'
        # Python2.4 fix
        # record has no attribute called funcName under python 2.4
        if hasattr(record, 'funcName'):
            access = record.funcName == 'access'
        else:
            access = True
        return not (_cplogging and access)


class guiHandler(logging.Handler):
    """
    Logging handler collects the last warnings/errors/exceptions
    to be displayed in the web-gui
    """
    def __init__(self, size):
        """
        Initializes the handler
        """
        logging.Handler.__init__(self)
        self.size = size
        self.store = []

    def emit(self, record):
        """
        Emit a record by adding it to our private queue
        """
        if len(self.store) >= self.size:
            # Loose the oldest record
            self.store.pop(0)
        try:
            self.store.append(self.format(record))
        except UnicodeDecodeError:
            # Catch elusive Unicode conversion problems
            pass

    def clear(self):
        self.store = []

    def count(self):
        return len(self.store)

    def last(self):
        if self.store:
            return self.store[len(self.store)-1]
        else:
            return ""

    def content(self):
        """
        Return an array with last records
        """
        return self.store


#------------------------------------------------------------------------------

def print_help():
    print
    print "Usage: %s [-f <configfile>] <other options>" % sabnzbd.MY_NAME
    print
    print "Options marked [*] are stored in the config file"
    print
    print "Options:"
    print "  -f  --config-file <ini>  Location of config file"
    print "  -s  --server <srv:port>  Listen on server:port [*]"
    print "  -t  --templates <templ>  Template directory [*]"
    print "  -2  --template2 <templ>  Secondary template dir [*]"
    print
    print "  -l  --logging <0..2>     Set logging level (0= least, 2= most) [*]"
    print "  -w  --weblogging <0..2>  Set cherrypy logging (0= off, 1= on, 2= file-only) [*]"
    print
    print "  -b  --browser <0..1>     Auto browser launch (0= off, 1= on) [*]"
    if sabnzbd.WIN32:
        print "  -d  --daemon             Use when run as a service"
    else:
        print "  -d  --daemon             Fork daemon process"
    print
    print "      --force              Discard web-port timeout (see Wiki!)"
    print "  -h  --help               Print this message"
    print "  -v  --version            Print version information"
    print "  -c  --clean              Remove queue, cache and logs"
    print "  -p  --pause              Start in paused mode"
    print "      --https <port>       Port to use for HTTPS server"
    print "      --log-all            Log all article handling (for developers)"

def print_version():
    print """
%s-%s

Copyright (C) 2008-2010, The SABnzbd-Team <team@sabnzbd.org>
SABnzbd comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. It is licensed under the
GNU GENERAL PUBLIC LICENSE Version 2 or (at your option) any later version.

""" % (sabnzbd.MY_NAME, sabnzbd.__version__)


#------------------------------------------------------------------------------
def daemonize():
    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError:
        print "fork() failed"
        sys.exit(1)

    os.chdir(sabnzbd.DIR_PROG)
    os.setsid()
    # Make sure I can read my own files and shut out others
    prev= os.umask(0)
    os.umask(prev and int('077',8))

    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError:
        print "fork() failed"
        sys.exit(1)

    dev_null = file('/dev/null', 'r')
    os.dup2(dev_null.fileno(), sys.stdin.fileno())

#------------------------------------------------------------------------------
def Bail_Out(browserhost, cherryport, access=False):
    """Abort program because of CherryPy troubles
    """
    logging.error(Ta('error-noWebUi'))
    if access:
        panic_xport(browserhost, cherryport)
    else:
        panic_port(browserhost, cherryport)
    sabnzbd.halt()
    exit_sab(2)

#------------------------------------------------------------------------------
def Web_Template(key, defweb, wdir):
    """ Determine a correct web template set,
        return full template path
    """
    if wdir is None:
        try:
            wdir = fix_webname(key.get())
        except:
            wdir = ''
    if not wdir:
        wdir = defweb
    key.set(wdir)
    if not wdir:
        # No default value defined, accept empty path
        return ''

    full_dir = real_path(sabnzbd.DIR_INTERFACES, wdir)
    full_main = real_path(full_dir, DEF_MAIN_TMPL)
    logging.info("Web dir is %s", full_dir)

    if not os.path.exists(full_main):
        logging.warning(Ta('warn-noSkin@1'), full_main)
        full_dir = real_path(sabnzbd.DIR_INTERFACES, DEF_STDINTF)
        full_main = real_path(full_dir, DEF_MAIN_TMPL)
        if not os.path.exists(full_main):
            logging.exception('Cannot find standard template: %s', full_dir)
            panic_tmpl(full_dir)
            exit_sab(1)

    sabnzbd.lang.install_language(real_path(full_dir, DEF_INT_LANGUAGE), sabnzbd.cfg.LANGUAGE.get(), wdir)

    return real_path(full_dir, "templates")


#------------------------------------------------------------------------------
def CheckColor(color, web_dir):
    """ Check existence of color-scheme """
    if color and os.path.exists(os.path.join(web_dir,'static/stylesheets/colorschemes/'+color+'.css')):
        return color
    else:
        return ''

#------------------------------------------------------------------------------
def fix_webname(name):
    if name:
        xname = name.title()
    else:
        xname = ''
    if xname in ('Default',):
        return 'Classic'
    elif xname in ('Classic', 'Plush', 'Mobile'):
        return xname
    elif xname in ('Smpl', 'Wizard'):
        return name.lower()
    else:
        return name

#------------------------------------------------------------------------------
def GetProfileInfo(vista_plus):
    """ Get the default data locations
    """
    ok = False
    if sabnzbd.WIN32:
        specials = get_user_shellfolders()
        try:
            sabnzbd.DIR_APPDATA = '%s\\%s' % (specials['AppData'], DEF_WORKDIR)
            sabnzbd.DIR_LCLDATA = '%s\\%s' % (specials['Local AppData'], DEF_WORKDIR)
            sabnzbd.DIR_HOME = specials['Personal']
            ok = True
        except:
            try:
                if vista_plus:
                    root = os.environ['AppData']
                    user = os.environ['USERPROFILE']
                    sabnzbd.DIR_APPDATA = '%s\\%s' % (root.replace('\\Roaming', '\\Local'), DEF_WORKDIR)
                    sabnzbd.DIR_HOME    = '%s\\Documents' % user
                else:
                    root = os.environ['USERPROFILE']
                    sabnzbd.DIR_APPDATA = '%s\\%s' % (root, DEF_WORKDIR)
                    sabnzbd.DIR_HOME = root

                try:
                    # Conversion to 8bit ASCII required for CherryPy
                    sabnzbd.DIR_APPDATA = sabnzbd.DIR_APPDATA.encode('latin-1')
                    sabnzbd.DIR_HOME = sabnzbd.DIR_HOME.encode('latin-1')
                    ok = True
                except:
                    # If unconvertible characters exist, use MSDOS name
                    try:
                        sabnzbd.DIR_APPDATA = win32api.GetShortPathName(sabnzbd.DIR_APPDATA)
                        sabnzbd.DIR_HOME = win32api.GetShortPathName(sabnzbd.DIR_HOME)
                        ok = True
                    except:
                        pass
                sabnzbd.DIR_LCLDATA = sabnzbd.DIR_APPDATA
            except:
                pass

    elif sabnzbd.DARWIN:
        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
        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.",
              "Please start with sabnzbd.ini file in another location")
        exit_sab(2)


#------------------------------------------------------------------------------
def print_modules():
    """ Log all detected optional or external modules
    """
    if sabnzbd.decoder.HAVE_YENC:
        logging.info("_yenc module... found!")
    else:
        if hasattr(sys, "frozen"):
            logging.error(Ta('error-noYEnc'))
        else:
            logging.info("_yenc module... NOT found!")

    if sabnzbd.newsunpack.PAR2_COMMAND:
        logging.info("par2 binary... found (%s)", sabnzbd.newsunpack.PAR2_COMMAND)
    else:
        logging.error(Ta('error-noPar2'))

    if sabnzbd.newsunpack.PAR2C_COMMAND:
        logging.info("par2-classic binary... found (%s)", sabnzbd.newsunpack.PAR2C_COMMAND)

    if sabnzbd.newsunpack.RAR_COMMAND:
        logging.info("unrar binary... found (%s)", sabnzbd.newsunpack.RAR_COMMAND)
    else:
        logging.warning(Ta('warn-noUnrar'))

    if sabnzbd.newsunpack.ZIP_COMMAND:
        logging.info("unzip binary... found (%s)", sabnzbd.newsunpack.ZIP_COMMAND)
    else:
        logging.warning(Ta('warn-noUnzip'))

    if not sabnzbd.WIN32:
        if sabnzbd.newsunpack.NICE_COMMAND:
            logging.info("nice binary... found (%s)", sabnzbd.newsunpack.NICE_COMMAND)
        else:
            logging.info("nice binary... NOT found!")
        if sabnzbd.newsunpack.IONICE_COMMAND:
            logging.info("ionice binary... found (%s)", sabnzbd.newsunpack.IONICE_COMMAND)
        else:
            logging.info("ionice binary... NOT found!")

    if sabnzbd.newswrapper.HAVE_SSL:
        logging.info("pyOpenSSL... found (%s)", sabnzbd.newswrapper.HAVE_SSL)
    else:
        logging.info("pyOpenSSL... NOT found - try apt-get install python-pyopenssl (SSL is optional)")


#------------------------------------------------------------------------------
def get_webhost(cherryhost, cherryport, https_port):
    """ Determine the webhost address and port,
        return (host, port, browserhost)
    """
    if cherryhost is None:
        cherryhost = sabnzbd.cfg.CHERRYHOST.get()
    else:
        sabnzbd.cfg.CHERRYHOST.set(cherryhost)

    # Get IP address, but discard APIPA/IPV6
    # If only APIPA's or IPV6 are found, fall back to localhost
    ipv4 = ipv6 = False
    localhost = hostip = 'localhost'
    try:
        info = socket.getaddrinfo(socket.gethostname(), None)
    except:
        # Hostname does not resolve, use 0.0.0.0
        cherryhost = '0.0.0.0'
        info = socket.getaddrinfo(localhost, None)
    for item in info:
        ip = str(item[4][0])
        if ip.startswith('169.254.'):
            pass # Is an APIPA
        elif ':' in ip:
            ipv6 = True
        elif '.' in ip and not ipv4:
            ipv4 = True
            hostip = ip

    # A blank host will use the local ip address
    if cherryhost == '':
        if ipv6 and ipv4:
            # To protect Firefox users, use numeric IP
            cherryhost = hostip
            browserhost = hostip
        else:
            cherryhost = socket.gethostname()
            browserhost = cherryhost

    # 0.0.0.0 will listen on all ipv4 interfaces (no ipv6 addresses)
    elif cherryhost == '0.0.0.0':
        # Just take the gamble for this
        cherryhost = '0.0.0.0'
        browserhost = localhost

    # :: will listen on all ipv6 interfaces (no ipv4 addresses)
    elif cherryhost in ('::','[::]'):
        cherryhost = cherryhost.strip('[').strip(']')
        # Assume '::1' == 'localhost'
        browserhost = localhost

    # IPV6 address
    elif '[' in cherryhost or ':' in cherryhost:
        browserhost = cherryhost

    # IPV6 numeric address
    elif cherryhost.replace('.', '').isdigit():
        # IPV4 numerical
        browserhost = cherryhost

    elif cherryhost == localhost:
        cherryhost = localhost
        browserhost = localhost

    else:
        # If on Vista and/or APIPA, use numerical IP, to help FireFoxers
        if ipv6 and ipv4:
            cherryhost = hostip
        browserhost = cherryhost

    # Some systems don't like brackets in numerical ipv6
    if '[' in cherryhost:
        if sabnzbd.DARWIN:
            cherryhost = cherryhost.strip('[]')
        else:
            try:
                info = socket.getaddrinfo(cherryhost, None)
            except:
                cherryhost = cherryhost.strip('[]')

    if ipv6 and ipv4 and \
       (browserhost not in ('localhost', '127.0.0.1', '[::1]', '::1')):
        sabnzbd.AMBI_LOCALHOST = True
        logging.info("IPV6 has priority on this system, potential Firefox issue")

    if ipv6 and ipv4 and cherryhost == '' and sabnzbd.WIN32:
        logging.warning(Ta('warn-0000'))

    if cherryhost == 'localhost' and not sabnzbd.WIN32 and not sabnzbd.DARWIN:
        # On the Ubuntu family, localhost leads to problems for CherryPy
        ips = ip_extract()
        if '127.0.0.1' in ips and '::1' in ips:
            cherryhost = '127.0.0.1'
            if ips[0] != '127.0.0.1':
                browserhost = '127.0.0.1'

    if cherryhost == 'localhost' and sabnzbd.DARWIN:
        cherryhost = '127.0.0.1'
        browserhost = 'localhost'

    if cherryport is None:
        cherryport = sabnzbd.cfg.CHERRYPORT.get_int()
    else:
        sabnzbd.cfg.CHERRYPORT.set(str(cherryport))

    if https_port is None:
        https_port = sabnzbd.cfg.HTTPS_PORT.get_int()
    else:
        sabnzbd.cfg.HTTPS_PORT.set(str(https_port))
        # if the https port was specified, assume they want HTTPS enabling also
        sabnzbd.cfg.ENABLE_HTTPS.set(True)

    if cherryport == https_port:
        sabnzbd.cfg.ENABLE_HTTPS.set(False)
        # Should have a translated message, but that's not available yet
        #logging.error(Ta('error-sameHTTP-HTTPS'))
        logging.error('HTTP and HTTPS ports cannot be the same')

    return cherryhost, cherryport, browserhost, https_port

def is_sabnzbd_running(url):
    import urllib2
    try:
        url = '%sapi?mode=version' % (url)
        s = urllib2.urlopen(url)
        ver = s.read()
        if ver and ver.strip() == sabnzbd.__version__:
            return True
        else:
            return False
    except:
        return False

def find_free_port(host, currentport):
    n = 0
    while n < 10 and currentport <= 49151:
        try:
            cherrypy.process.servers.check_port(host, currentport)
            return currentport
        except:
            currentport += 5
            n += 1
    return 0

def check_for_sabnzbd(url, upload_nzbs):
    # Check for a running instance of sabnzbd(same version) on this port
    if is_sabnzbd_running(url):
        # Upload any specified nzb files to the running instance
        if upload_nzbs:
            from sabnzbd.utils.upload import upload_file
            for f in upload_nzbs:
                upload_file(url, f)
        else:
            # Launch the web browser and quit since sabnzbd is already running
            launch_a_browser(url, force=True)
        exit_sab(0)
        return True
    return False

def copy_old_files(newpath):
    # OSX only:
    # If no INI file found but old one exists, copy it
    # When copying the INI, also copy rss, bookmarks and watched-data
    if not os.path.exists(os.path.join(newpath, DEF_INI_FILE)):
        if not os.path.exists(newpath):
            os.mkdir(newpath)
        oldpath = os.environ['HOME'] + "/.sabnzbd"
        oldini = os.path.join(oldpath, DEF_INI_FILE)
        if os.path.exists(oldini):
            import shutil
            try:
                shutil.copy(oldini, newpath)
            except:
                pass
            oldpath = os.path.join(oldpath, DEF_CACHE_DIR)
            newpath = os.path.join(newpath, DEF_CACHE_DIR)
            if not os.path.exists(newpath):
                os.mkdir(newpath)
            try:
                shutil.copy(os.path.join(oldpath, RSS_FILE_NAME), newpath)
            except:
                pass
            try:
                shutil.copy(os.path.join(oldpath, BOOKMARK_FILE_NAME), newpath)
            except:
                pass
            try:
                shutil.copy(os.path.join(oldpath, SCAN_FILE_NAME), newpath)
            except:
                pass


def evaluate_inipath(path):
    # Derive INI file path from a partial path.
    # Full file path: if file does not exist the name must contain a dot
    # but not a leading dot.
    # A foldername is enough, the standard name will be appended.

    path = os.path.normpath(os.path.abspath(path))
    inipath = os.path.join(path, DEF_INI_FILE)
    if os.path.isdir(path):
        return inipath
    elif os.path.isfile(path):
        return path
    else:
        dir, name = os.path.split(path)
        if name.find('.') < 1:
            return inipath
        else:
            return path


def cherrypy_logging(log_path):
    log = cherrypy.log
    log.access_file = ''
    log.error_file = ''
    # Max size of 512KB
    maxBytes = getattr(log, "rot_maxBytes", 524288)
    # cherrypy.log cherrypy.log.1 cherrypy.log.2
    backupCount = getattr(log, "rot_backupCount", 3)

    # 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.setLevel(logging.DEBUG)
    h.setFormatter(cherrypy._cplogging.logfmt)
    log.error_log.addHandler(h)


#------------------------------------------------------------------------------
def main():
    global LOG_FLAG

    AUTOBROWSER = None
    autorestarted = False

    sabnzbd.MY_FULLNAME = os.path.normpath(os.path.abspath(sys.argv[0]))
    sabnzbd.MY_NAME = os.path.basename(sabnzbd.MY_FULLNAME)
    sabnzbd.DIR_PROG = os.path.dirname(sabnzbd.MY_FULLNAME)
    sabnzbd.DIR_INTERFACES = real_path(sabnzbd.DIR_PROG, DEF_INTERFACES)
    sabnzbd.DIR_LANGUAGE = real_path(sabnzbd.DIR_PROG, DEF_LANGUAGE)
    org_dir = os.getcwd()

    if getattr(sys, 'frozen', None) == 'macosx_app':
        # Correct path if frozen with py2app (OSX)
        sabnzbd.MY_FULLNAME = sabnzbd.MY_FULLNAME.replace("/Resources/SABnzbd.py","/MacOS/SABnzbd")

    # Need console logging for SABnzbd.py and SABnzbd-console.exe
    consoleLogging = (not hasattr(sys, "frozen")) or (sabnzbd.MY_NAME.lower().find('-console') > 0)

    # No console logging needed for OSX app
    noConsoleLoggingOSX = (sabnzbd.DIR_PROG.find('.app/Contents/Resources') > 0)
    if noConsoleLoggingOSX:
        consoleLogging = 1

    LOGLEVELS = (logging.WARNING, logging.INFO, logging.DEBUG)

    # Setup primary logging to prevent default console logging
    gui_log = guiHandler(MAX_WARNINGS)
    gui_log.setLevel(logging.WARNING)
    format_gui = '%(asctime)s\n%(levelname)s\n%(message)s'
    gui_log.setFormatter(logging.Formatter(format_gui))
    sabnzbd.GUIHANDLER = gui_log

    # Create logger
    logger = logging.getLogger('')
    logger.setLevel(logging.WARNING)
    logger.addHandler(gui_log)

    # Create a list of passed files to load on startup
    # or pass to an already running instance of sabnzbd
    upload_nzbs = []
    for entry in sys.argv:
        if get_ext(entry) in ('.nzb','.zip','.rar', '.nzb.gz'):
            upload_nzbs.append(entry)
            sys.argv.remove(entry)


    try:
        # Ugly hack to remove the extra "SABnzbd*" parameter the Windows binary
        # gets when it's restarted
        if len(sys.argv) > 1 and \
           'sabnzbd' in sys.argv[1].lower() and \
           not sys.argv[1].startswith('-'):
            slice = 2
        else:
            slice = 1
        opts, args = getopt.getopt(sys.argv[slice:], "phdvncw:l:s:f:t:b:2:",
                                   ['pause', 'help', 'daemon', 'nobrowser', 'clean', 'logging=',
                                    'weblogging=', 'server=', 'templates', 'log-all',
                                    'template2', 'browser=', 'config-file=', 'delay=', 'force',
                                    'version', 'https=', 'autorestarted'])
    except getopt.GetoptError:
        print_help()
        exit_sab(2)

    fork = False
    pause = False
    inifile = None
    cherryhost = None
    cherryport = None
    https_port = None
    cherrypylogging = None
    clean_up = False
    logging_level = None
    web_dir = None
    web_dir2 = None
    delay = 0.0
    vista_plus = False
    vista64 = False
    force_web = False
    re_argv = [sys.argv[0]]

    for opt, arg in opts:
        if (opt in ('-d', '--daemon')):
            if not sabnzbd.WIN32:
                fork = True
            AUTOBROWSER = False
            sabnzbd.DAEMON = True
            consoleLogging = False
            re_argv.append(opt)
        elif opt in ('-h', '--help'):
            print_help()
            exit_sab(0)
        elif opt in ('-f', '--config-file'):
            inifile = arg
            re_argv.append(opt)
            re_argv.append(arg)
        elif opt in ('-t', '--templates'):
            web_dir = arg
        elif opt in ('-2', '--template2'):
            web_dir2 = arg
        elif opt in ('-s', '--server'):
            (cherryhost, cherryport) = split_host(arg)
        elif opt in ('-n', '--nobrowser'):
            AUTOBROWSER = False
        elif opt in ('-b', '--browser'):
            try:
                AUTOBROWSER = bool(int(arg))
            except:
                AUTOBROWSER = True
        elif opt in ('--autorestarted'):
            autorestarted = True
        elif opt in ('-c', '--clean'):
            clean_up= True
        elif opt in ('-w', '--weblogging'):
            try:
                cherrypylogging = int(arg)
            except:
                cherrypylogging = -1
            if cherrypylogging < 0 or cherrypylogging > 2:
                print_help()
                exit_sab(1)
        elif opt in ('-l', '--logging'):
            try:
                logging_level = int(arg)
            except:
                logging_level = -1
            if logging_level < 0 or logging_level > 2:
                print_help()
                exit_sab(1)
        elif opt in ('-v', '--version'):
            print_version()
            exit_sab(0)
        elif opt in ('-p', '--pause'):
            pause = True
        elif opt in ('--delay'):
            # For debugging of memory leak only!!
            try:
                delay = float(arg)
            except:
                pass
        elif opt in ('--force'):
            force_web = True
            re_argv.append(opt)
        elif opt in ('--https'):
            https_port = int(arg)
            re_argv.append(opt)
            re_argv.append(arg)
        elif opt in ('--log-all',):
            sabnzbd.LOG_ALL = True

    # Detect Windows variant
    if sabnzbd.WIN32:
        vista_plus, vista64 = windows_variant()

    if not SQLITE_DLL:
        panic_sqlite(sabnzbd.MY_FULLNAME)
        exit_sab(2)

    if inifile:
        # INI file given, simplest case
        inifile = evaluate_inipath(inifile)
    else:
        # No ini file given, need profile data
        GetProfileInfo(vista_plus)
        # Find out where INI file is
        inifile = os.path.abspath(sabnzbd.DIR_PROG + '/' + DEF_INI_FILE)
        if not os.path.exists(inifile):
            inifile = os.path.abspath(sabnzbd.DIR_LCLDATA + '/' + DEF_INI_FILE)
            if sabnzbd.DARWIN:
                copy_old_files(sabnzbd.DIR_LCLDATA)

    # If INI file at non-std location, then use program dir as $HOME
    if sabnzbd.DIR_LCLDATA != os.path.dirname(inifile):
        sabnzbd.DIR_HOME = os.path.dirname(inifile)

    # All system data dirs are relative to the place we found the INI file
    sabnzbd.DIR_LCLDATA = os.path.dirname(inifile)

    if not os.path.exists(inifile) and not os.path.exists(sabnzbd.DIR_LCLDATA):
        try:
            os.makedirs(sabnzbd.DIR_LCLDATA)
        except IOError:
            panic('Cannot create folder "%s".' % sabnzbd.DIR_LCLDATA, 'Check specified INI file location.')
            exit_sab(1)

    sabnzbd.cfg.set_root_folders(sabnzbd.DIR_HOME, sabnzbd.DIR_LCLDATA, sabnzbd.DIR_PROG, sabnzbd.DIR_INTERFACES)

    res, msg = config.read_config(inifile)
    if not res:
        panic(msg, 'Specify a correct file or delete this file.')
        exit_sab(1)

    # Set root folders for HTTPS server file paths
    sabnzbd.cfg.set_root_folders2()

    # Determine web host address
    cherryhost, cherryport, browserhost, https_port = get_webhost(cherryhost, cherryport, https_port)

    # If an instance of sabnzbd(same version) is already running on this port, launch the browser
    # If another program or sabnzbd version is on this port, try 10 other ports going up in a step of 5
    # If 'Port is not bound' (firewall) do not do anything (let the script further down deal with that).
    ## SSL
    enable_https = sabnzbd.cfg.ENABLE_HTTPS.get()
    if enable_https and https_port:
        try:
            cherrypy.process.servers.check_port(browserhost, https_port)
        except IOError, error:
            if str(error) == 'Port not bound.':
                pass
            else:
                url = 'https://%s:%s/' % (browserhost, https_port)
                if not check_for_sabnzbd(url, upload_nzbs):
                    port = find_free_port(browserhost, https_port)
                    if port > 0:
                        sabnzbd.cfg.HTTPS_PORT.set(port)
                        https_port = port
    ## NonSSL
    try:
        cherrypy.process.servers.check_port(browserhost, cherryport)
    except IOError, error:
        if str(error) == 'Port not bound.':
            pass
        else:
            url = 'http://%s:%s/' % (browserhost, cherryport)
            if not check_for_sabnzbd(url, upload_nzbs):
                port = find_free_port(browserhost, cherryport)
                if port > 0:
                    sabnzbd.cfg.CHERRYPORT.set(port)
                    cherryport = port


    if cherrypylogging is None:
        cherrypylogging = sabnzbd.cfg.LOG_WEB.get()
    else:
        sabnzbd.cfg.LOG_WEB.set(cherrypylogging)

    if logging_level is None:
        logging_level = sabnzbd.cfg.LOG_LEVEL.get()
    else:
        sabnzbd.cfg.LOG_LEVEL.set(logging_level)

    logdir = sabnzbd.cfg.LOG_DIR.get_path()
    if fork and not logdir:
        print "Error:"
        print "I refuse to fork without a log directory!"
        sys.exit(1)

    if clean_up:
        xlist= glob.glob(logdir + '/*')
        for x in xlist:
            if RSS_FILE_NAME not in x:
                os.remove(x)

    # Prevent the logger from raising exceptions
    # primarily to reduce the fallout of Python issue 4749
    logging.raiseExceptions = 0

    try:
        sabnzbd.LOGFILE = os.path.join(logdir, DEF_LOG_FILE)
        logsize = sabnzbd.cfg.LOG_SIZE.get_int()
        rollover_log = logging.handlers.RotatingFileHandler(\
            sabnzbd.LOGFILE, 'a+',
            logsize,
            sabnzbd.cfg.LOG_BACKUPS.get())

        format = '%(asctime)s::%(levelname)s::[%(module)s:%(lineno)d] %(message)s'
        rollover_log.setFormatter(logging.Formatter(format))
        rollover_log.addFilter(FilterCP3())
        sabnzbd.LOGHANDLER = rollover_log
        logger.addHandler(rollover_log)
        logger.setLevel(LOGLEVELS[logging_level])

    except IOError:
        print "Error:"
        print "Can't write to logfile"
        exit_sab(2)

    if fork:
        try:
            x= sys.stderr.fileno
            x= sys.stdout.fileno
            ol_path = os.path.join(logdir, DEF_LOG_ERRFILE)
            out_log = file(ol_path, 'a+', 0)
            sys.stderr.flush()
            sys.stdout.flush()
            os.dup2(out_log.fileno(), sys.stderr.fileno())
            os.dup2(out_log.fileno(), sys.stdout.fileno())
        except AttributeError:
            pass

    else:
        try:
            x= sys.stderr.fileno
            x= sys.stdout.fileno

            if consoleLogging:
                console = logging.StreamHandler()
                console.addFilter(FilterCP3())
                console.setLevel(LOGLEVELS[logging_level])
                console.setFormatter(logging.Formatter(format))
                logger.addHandler(console)
            if noConsoleLoggingOSX:
                logging.info('Console logging for OSX App disabled')
                so = file('/dev/null', 'a+')
                os.dup2(so.fileno(), sys.stdout.fileno())
                os.dup2(so.fileno(), sys.stderr.fileno())
        except AttributeError:
            pass

    logging.info('--------------------------------')
    logging.info('%s-%s (rev=%s)', sabnzbd.MY_NAME, sabnzbd.__version__, sabnzbd.__baseline__)
    if sabnzbd.WIN32:
        suffix = ''
        if vista_plus:
            suffix = ' (=Vista+)'
        if vista64:
            suffix = ' (=Vista+ x64)'
            sabnzbd.WIN64 = True
        logging.info('Platform=%s%s Class=%s', platform.platform(), suffix, os.name)
    else:
        logging.info('Platform = %s', os.name)
    logging.info('Python-version = %s', sys.version)

    # OSX 10.5 I/O priority setting
    if sabnzbd.DARWIN:
        logging.info('[osx] IO priority setting')
        try:
            from ctypes import cdll
            libc=cdll.LoadLibrary('/usr/lib/libc.dylib')
            boolSetResult=libc.setiopolicy_np(0,1,3)
            logging.info('[osx] IO priority set to throttle for process scope')
        except:
            logging.info('[osx] IO priority setting not supported')

    if AUTOBROWSER != None:
        sabnzbd.cfg.AUTOBROWSER.set(AUTOBROWSER)
    else:
        AUTOBROWSER = sabnzbd.cfg.AUTOBROWSER.get()

    sabnzbd.cfg.DEBUG_DELAY.set(delay)

    # Find external programs
    sabnzbd.newsunpack.find_programs(sabnzbd.DIR_PROG)

    init_ok = sabnzbd.initialize(pause, clean_up, evalSched=True)

    if not init_ok:
        logging.error('Initializing %s-%s failed, aborting',
                      sabnzbd.MY_NAME, sabnzbd.__version__)
        exit_sab(2)

    os.chdir(sabnzbd.DIR_PROG)

    web_dir  = Web_Template(sabnzbd.cfg.WEB_DIR,  DEF_STDINTF,  fix_webname(web_dir))
    web_dir2 = Web_Template(sabnzbd.cfg.WEB_DIR2, '', fix_webname(web_dir2))

    wizard_dir = os.path.join(sabnzbd.DIR_INTERFACES, 'wizard')
    sabnzbd.lang.install_language(os.path.join(wizard_dir, DEF_INT_LANGUAGE), sabnzbd.cfg.LANGUAGE.get(), 'wizard')

    sabnzbd.WEB_DIR  = web_dir
    sabnzbd.WEB_DIR2 = web_dir2
    sabnzbd.WIZARD_DIR = wizard_dir

    sabnzbd.WEB_COLOR = CheckColor(sabnzbd.cfg.WEB_COLOR.get(),  web_dir)
    sabnzbd.cfg.WEB_COLOR.set(sabnzbd.WEB_COLOR)
    sabnzbd.WEB_COLOR2 = CheckColor(sabnzbd.cfg.WEB_COLOR2.get(),  web_dir2)
    sabnzbd.cfg.WEB_COLOR2.set(sabnzbd.WEB_COLOR2)

    if fork and not sabnzbd.WIN32:
        daemonize()

    # Save the INI file
    config.save_config(force=True)

    logging.info('Starting %s-%s', sabnzbd.MY_NAME, sabnzbd.__version__)
    try:
        sabnzbd.start()
    except:
        logging.exception("Failed to start %s-%s", sabnzbd.MY_NAME, sabnzbd.__version__)
        sabnzbd.halt()

    print_modules()

    # Upload any nzb/zip/rar/nzb.gz files from file association
    if upload_nzbs:
        from sabnzbd.utils.upload import add_local
        for f in upload_nzbs:
            add_local(f)

    cherrylogtoscreen = False
    sabnzbd.WEBLOGFILE = None

    if cherrypylogging:
        if logdir:
            sabnzbd.WEBLOGFILE = os.path.join(logdir, DEF_LOG_CHERRY)
        # Define our custom logger for cherrypy errors
        cherrypy_logging(sabnzbd.WEBLOGFILE)
        if not fork:
            try:
                x= sys.stderr.fileno
                x= sys.stdout.fileno
                if cherrypylogging == 1:
                    cherrylogtoscreen = True
            except:
                pass

    cherrypy.config.update({'server.environment': 'production',
                            'server.socket_host': cherryhost,
                            'server.socket_port': cherryport,
                            'log.screen': cherrylogtoscreen,
                            'engine.autoreload_frequency' : 100,
                            'engine.autoreload_on' : False,
                            'engine.reexec_retry' : 100,
                            'tools.encode.on' : True,
                            'tools.gzip.on' : True,
                            'tools.sessions.on' : True,
                            'request.show_tracebacks': True,
                            'checker.check_localhost' : bool(consoleLogging),
                            'error_page.401': sabnzbd.misc.error_page_401
                           })

    https_cert = sabnzbd.cfg.HTTPS_CERT.get_path()
    https_key = sabnzbd.cfg.HTTPS_KEY.get_path()
    if enable_https:
        # If either the HTTPS certificate or key do not exist, make some self-signed ones.
        if not (https_cert and os.path.exists(https_cert)) or not (https_key and os.path.exists(https_key)):
            create_https_certificates(https_cert, https_key)

        if https_port and not (os.path.exists(https_cert) or os.path.exists(https_key)):
            logging.warning(Ta('warn-noCertKey'))
            https_port = False

        if https_port:
            secure_server = _cpwsgi_server.CPWSGIServer()
            secure_server.bind_addr = (cherryhost, https_port)
            secure_server.ssl_certificate = https_cert
            secure_server.ssl_private_key = https_key
            adapter = _cpserver.ServerAdapter(cherrypy.engine, secure_server, secure_server.bind_addr)
            adapter.subscribe()

    static = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(web_dir, 'static')}
    wizard_static = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(wizard_dir, 'static')}

    appconfig = {'/sabnzbd/api' : {'tools.basic_auth.on' : False},
                 '/api' : {'tools.basic_auth.on' : False},
                 '/m/api' : {'tools.basic_auth.on' : False},
                 '/rss' : {'tools.basic_auth.on' : False},
                 '/sabnzbd/rss' : {'tools.basic_auth.on' : False},
                 '/m/rss' : {'tools.basic_auth.on' : False},
                 '/sabnzbd/shutdown': {'streamResponse': True},
                 '/sabnzbd/static': static,
                 '/static': static,
                 '/sabnzbd/wizard/static': wizard_static,
                 '/wizard/static': wizard_static
                 }

    if web_dir2:
        static2 = {'tools.staticdir.on': True, 'tools.staticdir.dir': os.path.join(web_dir2, 'static')}
        appconfig['/sabnzbd/m/api'] = {'tools.basic_auth.on' : False}
        appconfig['/sabnzbd/m/rss'] = {'tools.basic_auth.on' : False}
        appconfig['/sabnzbd/m/shutdown'] = {'streamResponse': True}
        appconfig['/sabnzbd/m/static'] = static2
        appconfig['/m/static'] = static2
        appconfig['/sabnzbd/m/wizard/static'] = wizard_static
        appconfig['/m/wizard/static'] = wizard_static

    login_page = sabnzbd.interface.MainPage(web_dir, '/', web_dir2, '/m/', first=2)
    cherrypy.tree.mount(login_page, '/', config=appconfig)

    # Set authentication for CherryPy
    sabnzbd.interface.set_auth(cherrypy.config)

    logging.info('Starting web-interface on %s:%s', cherryhost, cherryport)

    sabnzbd.cfg.LOG_LEVEL.callback(guard_loglevel)

    try:
        # Use internal cherrypy check first to prevent ugly tracebacks
        cherrypy.process.servers.check_port(browserhost, cherryport)
        cherrypy.engine.start()
    except IOError, error:
        if str(error) == 'Port not bound.':
            if not force_web:
                panic_fwall(vista_plus)
                sabnzbd.halt()
                exit_sab(2)
        else:
            logging.debug("Failed to start web-interface: ", exc_info = True)
            # When error 13 occurs, we have no access rights
            Bail_Out(browserhost, cherryport, '13' in str(error))
    except socket.error, error:
        logging.debug("Failed to start web-interface: ", exc_info = True)
        Bail_Out(browserhost, cherryport, access=True)
    except:
        logging.debug("Failed to start web-interface: ", exc_info = True)
        Bail_Out(browserhost, cherryport)

    # Wait for server to become ready
    cherrypy.engine.wait(cherrypy.process.wspbus.states.STARTED)

    if not autorestarted:
        if enable_https and https_port:
            launch_a_browser("https://%s:%s/sabnzbd" % (browserhost, https_port))
        else:
            launch_a_browser("http://%s:%s/sabnzbd" % (browserhost, cherryport))

        notify("SAB_Launched", None)
        osx.sendGrowlMsg('SABnzbd %s' % (sabnzbd.__version__),"http://%s:%s/sabnzbd" % (browserhost, cherryport),osx.NOTIFICATION['startup'])
        # Now's the time to check for a new version
        check_latest_version()
    autorestarted = False

    # Have to keep this running, otherwise logging will terminate
    timer = 0
    while not sabnzbd.SABSTOP:
        time.sleep(3)

        # Check for loglevel changes
        if LOG_FLAG:
            LOG_FLAG = False
            level = LOGLEVELS[sabnzbd.cfg.LOG_LEVEL.get()]
            logger.setLevel(level)
            if consoleLogging:
                console.setLevel(level)

        ### 30 sec polling tasks
        if timer > 9:
            timer = 0
            # Keep Windows awake (if needed)
            sabnzbd.keep_awake()
            # Restart scheduler (if needed)
            scheduler.restart()
            # Save config (if needed)
            config.save_config()
            # Check the threads
            if not sabnzbd.check_all_tasks():
                autorestarted = True
                cherrypy.engine.execv = True
        else:
            timer += 1

        ### 3 sec polling tasks
        # Check for auto-restart request
        if cherrypy.engine.execv:
            scheduler.stop()
            sabnzbd.halt()
            cherrypy.engine.exit()
            sabnzbd.SABSTOP = True
            if downloader.paused():
                re_argv.append('-p')
            if autorestarted:
                re_argv.append('--autorestarted')
            sys.argv = re_argv
            os.chdir(org_dir)
            if sabnzbd.DARWIN:
                args = sys.argv[:]
                args.insert(0, sys.executable)
                #TO FIX : when executing from sources on osx, after a restart, process is detached from console
                #If OSX frozen restart of app instead of embedded python
                if getattr(sys, 'frozen', None) == 'macosx_app':
                    #[[NSProcessInfo processInfo] processIdentifier]]
                    #logging.info("%s" % (NSProcessInfo.processInfo().processIdentifier()))
                    logging.info(os.getpid())
                    os.system('kill -9 %s && open "%s"' % (os.getpid(),sabnzbd.MY_FULLNAME.replace("/Contents/MacOS/SABnzbd","")) )
                else:
                    pid = os.fork()
                    if pid == 0:
                        os.execv(sys.executable, args)
            else:
                cherrypy.engine._do_execv()

    config.save_config()

    notify("SAB_Shutdown", None)
    osx.sendGrowlMsg('SABnzbd',T('grwl-shutdown-end-msg'),osx.NOTIFICATION['startup'])
    logging.info('Leaving SABnzbd')
    sys.stderr.flush()
    sys.stdout.flush()
    if getattr(sys, 'frozen', None) == 'macosx_app':
        AppHelper.stopEventLoop()
    else:
        os._exit(0)


#####################################################################
#
# Platform specific startup code
#

if not getattr(sys, 'frozen', None) == 'macosx_app':
    # Windows & Unix/Linux

    if __name__ == '__main__':
        main()

else:

    # OSX

    if __name__ == '__main__':
        try:
            from PyObjCTools import AppHelper
            from SABnzbdDelegate import SABnzbdDelegate

            class startApp(Thread):
                def __init__(self):
                    logging.info('[osx] sabApp Starting - starting main thread')
                    Thread.__init__(self)
                def run(self):
                    main()
                    logging.info('[osx] sabApp Stopping - main thread quit ')
                    AppHelper.stopEventLoop()
                def stop(self):
                    logging.info('[osx] sabApp Quit - stopping main thread ')
                    sabnzbd.halt()
                    cherrypy.engine.exit()
                    sabnzbd.SABSTOP = True
                    logging.info('[osx] sabApp Quit - main thread stopped')

            sabApp = startApp()
            sabApp.start()
            AppHelper.runEventLoop()

        except:
            main()

