#!/usr/bin/python3 -OO
# Copyright 2007-2020 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.hexversion < 0x03050000:
    print("Sorry, requires Python 3.5 or above")
    print("You can read more at: https://sabnzbd.org/python3")
    sys.exit(1)

import logging
import logging.handlers
import importlib.util
import traceback
import getopt
import signal
import socket
import platform
import subprocess
import ssl
import time
import re

try:
    import Cheetah
    import feedparser
    import configobj
    import cherrypy
    import portend
    import cryptography
    import chardet
except ImportError as e:
    print("Not all required Python modules are available, please check requirements.txt")
    print("Missing module:", e.name)
    print("You can read more at: https://sabnzbd.org/python3")
    print("If you still experience problems, remove all .pyc files in this folder and subfolders")
    sys.exit(1)

import sabnzbd
import sabnzbd.lang
import sabnzbd.interface
from sabnzbd.constants import *
import sabnzbd.newsunpack
from sabnzbd.misc import (
    check_latest_version,
    exit_sab,
    split_host,
    create_https_certificates,
    windows_variant,
    ip_extract,
    set_serv_parms,
    get_serv_parms,
    get_from_url,
    upload_file_to_sabnzbd,
)
from sabnzbd.filesystem import get_ext, real_path, long_path, globber_full, remove_file
from sabnzbd.panic import panic_tmpl, panic_port, panic_host, panic, launch_a_browser
import sabnzbd.scheduler as scheduler
import sabnzbd.config as config
import sabnzbd.cfg
import sabnzbd.downloader
import sabnzbd.notifier as notifier
import sabnzbd.zconfig

try:
    import win32api
    import win32serviceutil
    import win32evtlogutil
    import win32event
    import win32service
    import win32ts
    import pywintypes

    win32api.SetConsoleCtrlHandler(sabnzbd.sig_handler, True)
    from sabnzbd.utils.apireg import get_connection_info, set_connection_info, del_connection_info
except ImportError:
    if sabnzbd.WIN32:
        print("Sorry, requires Python module PyWin32.")
        sys.exit(1)

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


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


def warning_helpful(*args, **kwargs):
    """ Wrapper to ignore helpfull warnings if desired """
    if sabnzbd.cfg.helpfull_warnings():
        return logging.warning(*args, **kwargs)
    return logging.info(*args, **kwargs)


logging.warning_helpful = warning_helpful


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 % is part of the msg, this could fail
        try:
            record_msg = record.msg % record.args
        except TypeError:
            record_msg = record.msg + str(record.args)

        if record.levelname == "WARNING":
            sabnzbd.LAST_WARNING = record_msg
        else:
            sabnzbd.LAST_ERROR = record_msg

        if len(self.store) >= self.size:
            # Loose the oldest record
            self.store.pop(0)
        try:
            # Append traceback, if available
            warning = {"type": record.levelname, "text": record_msg, "time": int(time.time())}
            if record.exc_info:
                warning["text"] = "%s\n%s" % (warning["text"], traceback.format_exc())
            self.store.append(warning)
        except UnicodeDecodeError:
            # Catch elusive Unicode conversion problems
            pass

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

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

    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()
    print("  -l  --logging <-1..2>     Set logging level (-1=off, 0= least, 2= most) [*]")
    print("  -w  --weblogging         Enable cherrypy access logging")
    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("      --pid <path>         Create a PID file in the given folder (full path)")
        print("      --pidfile <path>     Create a PID file with the given name (full path)")
    print()
    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("      --repair             Add orphaned jobs from the incomplete folder to the queue")
    print("      --repair-all         Try to reconstruct the queue from the incomplete folder")
    print("                           with full data reconstruction")
    print("      --https <port>       Port to use for HTTPS server")
    print("      --ipv6_hosting <0|1> Listen on IPv6 address [::1] [*]")
    print("      --no-login           Start with username and password reset")
    print("      --log-all            Log all article handling (for developers)")
    print("      --disable-file-log   Logging is only written to console")
    print("      --new                Run a new instance of SABnzbd")
    print()
    print("NZB (or related) file:")
    print("  NZB or compressed NZB file, with extension .nzb, .zip, .rar, .7z, .gz, or .bz2")
    print()


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

Copyright (C) 2007-2020 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():
    """ Daemonize the process, based on various StackOverflow answers """
    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)

    # Flush I/O buffers
    sys.stdout.flush()
    sys.stderr.flush()

    # Get log file  path and remove the log file if it got too large
    log_path = os.path.join(sabnzbd.cfg.log_dir.get_path(), DEF_LOG_ERRFILE)
    if os.path.exists(log_path) and os.path.getsize(log_path) > sabnzbd.cfg.log_size.get_int():
        remove_file(log_path)

    # Replace file descriptors for stdin, stdout, and stderr
    with open("/dev/null", "rb", 0) as f:
        os.dup2(f.fileno(), sys.stdin.fileno())
    with open(log_path, "ab", 0) as f:
        os.dup2(f.fileno(), sys.stdout.fileno())
    with open(log_path, "ab", 0) as f:
        os.dup2(f.fileno(), sys.stderr.fileno())


def abort_and_show_error(browserhost, cherryport, err=""):
    """ Abort program because of CherryPy troubles """
    logging.error(T("Failed to start web-interface") + " : " + str(err))
    if not sabnzbd.DAEMON:
        if "49" in err:
            panic_host(browserhost, cherryport)
        else:
            panic_port(browserhost, cherryport)
    sabnzbd.halt()
    exit_sab(2)


def identify_web_template(key, defweb, wdir):
    """ Determine a correct web template set, return full template path """
    if wdir is None:
        try:
            wdir = fix_webname(key())
        except:
            wdir = ""
    if not wdir:
        wdir = defweb
    if key:
        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)

    if not os.path.exists(full_main):
        logging.warning_helpful(T("Cannot find web template: %s, trying standard template"), 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)

    logging.info("Template location for %s is %s", defweb, full_dir)
    return real_path(full_dir, "templates")


def check_template_scheme(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
    elif color and os.path.exists(os.path.join(web_dir, "static", "stylesheets", "colorschemes", color)):
        return color
    else:
        return ""


def fix_webname(name):
    if name:
        xname = name.title()
    else:
        xname = ""
    if xname in ("Default",):
        return "Glitter"
    elif xname in ("Glitter", "Plush"):
        return xname
    elif xname in ("Wizard",):
        return name.lower()
    elif xname in ("Config",):
        return "Glitter"
    else:
        return name


def get_user_profile_paths(vista_plus):
    """ Get the default data locations on Windows"""
    if sabnzbd.DAEMON and sabnzbd.WIN32:
        # In daemon mode, do not try to access the user profile
        # just assume that everything defaults to the program dir
        sabnzbd.DIR_APPDATA = sabnzbd.DIR_PROG
        sabnzbd.DIR_LCLDATA = sabnzbd.DIR_PROG
        sabnzbd.DIR_HOME = sabnzbd.DIR_PROG
        if sabnzbd.WIN32:
            # Ignore Win32 "logoff" signal
            # This should work, but it doesn't
            # Instead the signal_handler will ignore the "logoff" signal
            # signal.signal(5, signal.SIG_IGN)
            pass
        return
    elif sabnzbd.WIN32:
        try:
            from win32com.shell import shell, shellcon

            path = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, None, 0)
            sabnzbd.DIR_APPDATA = os.path.join(path, DEF_WORKDIR)
            path = shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, None, 0)
            sabnzbd.DIR_LCLDATA = os.path.join(path, DEF_WORKDIR)
            sabnzbd.DIR_HOME = os.environ["USERPROFILE"]
        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 = user
                else:
                    root = os.environ["USERPROFILE"]
                    sabnzbd.DIR_APPDATA = "%s\\%s" % (root, DEF_WORKDIR)
                    sabnzbd.DIR_HOME = root
                sabnzbd.DIR_LCLDATA = sabnzbd.DIR_APPDATA
            except:
                pass

        # Long-path everything
        sabnzbd.DIR_APPDATA = long_path(sabnzbd.DIR_APPDATA)
        sabnzbd.DIR_LCLDATA = long_path(sabnzbd.DIR_LCLDATA)
        sabnzbd.DIR_HOME = long_path(sabnzbd.DIR_HOME)
        return

    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
            return
    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
            return

    # Nothing worked
    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.SABYENC_ENABLED:
        # Yes, we have SABYenc, and it's the correct version, so it's enabled
        logging.info("SABYenc module (v%s)... found!", sabnzbd.decoder.SABYENC_VERSION)
    else:
        # Something wrong with SABYenc, so let's determine and print what:
        if sabnzbd.decoder.SABYENC_VERSION:
            # We have a VERSION, thus a SABYenc module, but it's not the correct version
            logging.error(
                T("SABYenc disabled: no correct version found! (Found v%s, expecting v%s)"),
                sabnzbd.decoder.SABYENC_VERSION,
                sabnzbd.constants.SABYENC_VERSION_REQUIRED,
            )
        else:
            # No SABYenc module at all
            logging.error(
                T("SABYenc module... NOT found! Expecting v%s - https://sabnzbd.org/sabyenc"),
                sabnzbd.constants.SABYENC_VERSION_REQUIRED,
            )
        # Do not allow downloading
        sabnzbd.NO_DOWNLOADING = True

    logging.info("Cryptography module (v%s)... found!", cryptography.__version__)

    if sabnzbd.WIN32 and sabnzbd.newsunpack.MULTIPAR_COMMAND:
        logging.info("MultiPar binary... found (%s)", sabnzbd.newsunpack.MULTIPAR_COMMAND)
    elif sabnzbd.newsunpack.PAR2_COMMAND:
        logging.info("par2 binary... found (%s)", sabnzbd.newsunpack.PAR2_COMMAND)
    else:
        logging.error(T("par2 binary... NOT found!"))
        # Do not allow downloading
        sabnzbd.NO_DOWNLOADING = True

    if sabnzbd.newsunpack.RAR_COMMAND:
        logging.info("UNRAR binary... found (%s)", sabnzbd.newsunpack.RAR_COMMAND)

        # Report problematic unrar
        if sabnzbd.newsunpack.RAR_PROBLEM:
            have_str = "%.2f" % (float(sabnzbd.newsunpack.RAR_VERSION) / 100)
            want_str = "%.2f" % (float(sabnzbd.constants.REC_RAR_VERSION) / 100)
            logging.warning_helpful(
                T("Your UNRAR version is %s, we recommend version %s or higher.<br />"), have_str, want_str
            )
        elif not (sabnzbd.WIN32 or sabnzbd.DARWIN):
            logging.info("UNRAR binary version %.2f", (float(sabnzbd.newsunpack.RAR_VERSION) / 100))
    else:
        logging.error(T("unrar binary... NOT found"))
        # Do not allow downloading
        sabnzbd.NO_DOWNLOADING = True

    # If available, we prefer 7zip over unzip
    if sabnzbd.newsunpack.SEVEN_COMMAND:
        logging.info("7za binary... found (%s)", sabnzbd.newsunpack.SEVEN_COMMAND)
    else:
        logging.info(T("7za binary... NOT found!"))

        if sabnzbd.newsunpack.ZIP_COMMAND:
            logging.info("unzip binary... found (%s)", sabnzbd.newsunpack.ZIP_COMMAND)
        else:
            logging.info(T("unzip binary... NOT found!"))

    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!")

    # Show fatal warning
    if sabnzbd.NO_DOWNLOADING:
        logging.error(T("Essential modules are missing, downloading cannot start."))


def all_localhosts():
    """ Return all unique values of localhost in order of preference """
    ips = ["127.0.0.1"]
    try:
        # Check whether IPv6 is available and enabled
        info = socket.getaddrinfo("::1", None)
        af, socktype, proto, _canonname, _sa = info[0]
        s = socket.socket(af, socktype, proto)
        s.close()
    except socket.error:
        return ips
    try:
        info = socket.getaddrinfo("localhost", None)
    except socket.error:
        # localhost does not resolve
        return ips
    ips = []
    for item in info:
        item = item[4][0]
        # Avoid problems on strange Linux settings
        if not isinstance(item, str):
            continue
        # Only return IPv6 when enabled
        if item not in ips and ("::1" not in item or sabnzbd.cfg.ipv6_hosting()):
            ips.append(item)
    return ips


def check_resolve(host):
    """ Return True if 'host' resolves """
    try:
        socket.getaddrinfo(host, None)
    except socket.error:
        # Does not resolve
        return False
    return True


def get_webhost(cherryhost, cherryport, https_port):
    """Determine the webhost address and port,
    return (host, port, browserhost)
    """
    if cherryhost == "0.0.0.0" and not check_resolve("127.0.0.1"):
        cherryhost = ""
    elif cherryhost == "::" and not check_resolve("::1"):
        cherryhost = ""

    if cherryhost is None:
        cherryhost = sabnzbd.cfg.cherryhost()
    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 socket.error:
        # Hostname does not resolve
        try:
            # Valid user defined name?
            info = socket.getaddrinfo(cherryhost, None)
        except socket.error:
            if cherryhost not in ("localhost", "127.0.0.1", "::1"):
                cherryhost = "0.0.0.0"
            try:
                info = socket.getaddrinfo(localhost, None)
            except socket.error:
                info = socket.getaddrinfo("127.0.0.1", None)
                localhost = "127.0.0.1"
    for item in info:
        ip = str(item[4][0])
        if ip.startswith("169.254."):
            pass  # Automatic Private IP Addressing (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 sabnzbd.DARWIN:
            cherryhost = cherryhost.strip("[]")
        else:
            try:
                socket.getaddrinfo(cherryhost, None)
            except socket.error:
                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_helpful(T("Please be aware the 0.0.0.0 hostname will need an IPv6 address for external access"))

    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"

    # This is to please Chrome on OSX
    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 and sabnzbd.cfg.enable_https():
        sabnzbd.cfg.enable_https.set(False)
        # Should have a translated message, but that's not available yet
        logging.error(T("HTTP and HTTPS ports cannot be the same"))

    return cherryhost, cherryport, browserhost, https_port


def attach_server(host, port, cert=None, key=None, chain=None):
    """ Define and attach server, optionally HTTPS """
    if sabnzbd.cfg.ipv6_hosting() or "::1" not in host:
        http_server = cherrypy._cpserver.Server()
        http_server.bind_addr = (host, port)
        if cert and key:
            http_server.ssl_module = "builtin"
            http_server.ssl_certificate = cert
            http_server.ssl_private_key = key
            http_server.ssl_certificate_chain = chain
        http_server.subscribe()


def is_sabnzbd_running(url):
    """ Return True when there's already a SABnzbd instance running. """
    try:
        url = "%s&mode=version" % url
        # Do this without certificate verification, few installations will have that
        prev = sabnzbd.set_https_verification(False)
        ver = get_from_url(url)
        sabnzbd.set_https_verification(prev)
        return ver and (re.search(r"\d+\.\d+\.", ver) or ver.strip() == sabnzbd.__version__)
    except:
        return False


def find_free_port(host, currentport):
    """ Return a free port, 0 when nothing is free """
    n = 0
    while n < 10 and currentport <= 49151:
        try:
            portend.free(host, currentport, timeout=0.025)
            return currentport
        except:
            currentport += 5
            n += 1
    return 0


def check_for_sabnzbd(url, upload_nzbs, allow_browser=True):
    """Check for a running instance of sabnzbd on this port
    allow_browser==True|None will launch the browser, False will not.
    """
    if allow_browser is None:
        allow_browser = True
    if is_sabnzbd_running(url):
        # Upload any specified nzb files to the running instance
        if upload_nzbs:
            prev = sabnzbd.set_https_verification(False)
            for f in upload_nzbs:
                upload_file_to_sabnzbd(url, f)
            sabnzbd.set_https_verification(prev)
        else:
            # Launch the web browser and quit since sabnzbd is already running
            # Trim away everything after the final slash in the URL
            url = url[: url.rfind("/") + 1]
            launch_a_browser(url, force=allow_browser)
        exit_sab(0)
        return True
    return False


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.
    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) or os.path.isfile(path + ".bak"):
        return path
    else:
        _dirpart, name = os.path.split(path)
        if name.find(".") < 1:
            return inipath
        else:
            return path


def commandline_handler():
    """Split win32-service commands are true parameters
    Returns:
        service, sab_opts, serv_opts, upload_nzbs
    """
    service = ""
    sab_opts = []
    serv_opts = [os.path.normpath(os.path.abspath(sys.argv[0]))]
    upload_nzbs = []

    # OSX binary: get rid of the weird -psn_0_123456 parameter
    for arg in sys.argv:
        if arg.startswith("-psn_"):
            sys.argv.remove(arg)
            break

    # 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_start = 2
    else:
        slice_start = 1

    # Prepend options from env-variable to options
    info = os.environ.get("SABnzbd", "").split()
    info.extend(sys.argv[slice_start:])

    try:
        opts, args = getopt.getopt(
            info,
            "phdvncwl:s:f:t:b:2:",
            [
                "pause",
                "help",
                "daemon",
                "nobrowser",
                "clean",
                "logging=",
                "weblogging",
                "server=",
                "templates",
                "ipv6_hosting=",
                "template2",
                "browser=",
                "config-file=",
                "force",
                "disable-file-log",
                "version",
                "https=",
                "autorestarted",
                "repair",
                "repair-all",
                "log-all",
                "no-login",
                "pid=",
                "new",
                "console",
                "pidfile=",
                # Below Win32 Service options
                "password=",
                "username=",
                "startup=",
                "perfmonini=",
                "perfmondll=",
                "interactive",
                "wait=",
            ],
        )
    except getopt.GetoptError:
        print_help()
        exit_sab(2)

    # Check for Win32 service commands
    if args and args[0] in ("install", "update", "remove", "start", "stop", "restart", "debug"):
        service = args[0]
        serv_opts.extend(args)

    if not service:
        # Get and remove any NZB file names
        for entry in args:
            if get_ext(entry) in VALID_NZB_FILES + VALID_ARCHIVES:
                upload_nzbs.append(os.path.abspath(entry))

    for opt, arg in opts:
        if opt in ("password", "username", "startup", "perfmonini", "perfmondll", "interactive", "wait"):
            # Service option, just collect
            if service:
                serv_opts.append(opt)
                if arg:
                    serv_opts.append(arg)
        else:
            if opt == "-f":
                arg = os.path.normpath(os.path.abspath(arg))
            sab_opts.append((opt, arg))

    return service, sab_opts, serv_opts, upload_nzbs


def get_f_option(opts):
    """ Return value of the -f option """
    for opt, arg in opts:
        if opt == "-f":
            return arg
    else:
        return None


def main():
    global LOG_FLAG
    import sabnzbd  # Due to ApplePython bug

    autobrowser = None
    autorestarted = False
    sabnzbd.MY_FULLNAME = sys.argv[0]
    sabnzbd.MY_NAME = os.path.basename(sabnzbd.MY_FULLNAME)
    fork = False
    pause = False
    inifile = None
    cherryhost = None
    cherryport = None
    https_port = None
    cherrypylogging = None
    clean_up = False
    logging_level = None
    no_file_log = False
    web_dir = None
    vista_plus = False
    win64 = False
    repair = 0
    no_login = False
    sabnzbd.RESTART_ARGS = [sys.argv[0]]
    pid_path = None
    pid_file = None
    new_instance = False
    ipv6_hosting = None

    _service, sab_opts, _serv_opts, upload_nzbs = commandline_handler()

    for opt, arg in sab_opts:
        if opt == "--servicecall":
            sabnzbd.MY_FULLNAME = arg
        elif opt in ("-d", "--daemon"):
            if not sabnzbd.WIN32:
                fork = True
            autobrowser = False
            sabnzbd.DAEMON = True
            sabnzbd.RESTART_ARGS.append(opt)
        elif opt in ("-f", "--config-file"):
            inifile = arg
            sabnzbd.RESTART_ARGS.append(opt)
            sabnzbd.RESTART_ARGS.append(arg)
        elif opt in ("-h", "--help"):
            print_help()
            exit_sab(0)
        elif opt in ("-t", "--templates"):
            web_dir = 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 ValueError:
                autobrowser = True
        elif opt == "--autorestarted":
            autorestarted = True
        elif opt in ("-c", "--clean"):
            clean_up = True
        elif opt in ("-w", "--weblogging"):
            cherrypylogging = True
        elif opt in ("-l", "--logging"):
            try:
                logging_level = int(arg)
            except:
                logging_level = -2
            if logging_level < -1 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 == "--https":
            https_port = int(arg)
            sabnzbd.RESTART_ARGS.append(opt)
            sabnzbd.RESTART_ARGS.append(arg)
        elif opt == "--repair":
            repair = 1
            pause = True
        elif opt == "--repair-all":
            repair = 2
            pause = True
        elif opt == "--log-all":
            sabnzbd.LOG_ALL = True
        elif opt == "--disable-file-log":
            no_file_log = True
        elif opt == "--no-login":
            no_login = True
        elif opt == "--pid":
            pid_path = arg
            sabnzbd.RESTART_ARGS.append(opt)
            sabnzbd.RESTART_ARGS.append(arg)
        elif opt == "--pidfile":
            pid_file = arg
            sabnzbd.RESTART_ARGS.append(opt)
            sabnzbd.RESTART_ARGS.append(arg)
        elif opt == "--new":
            new_instance = True
        elif opt == "--ipv6_hosting":
            ipv6_hosting = arg

    sabnzbd.MY_FULLNAME = os.path.normpath(os.path.abspath(sabnzbd.MY_FULLNAME))
    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()

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

    LOGLEVELS = (logging.FATAL, 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)

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

    if inifile:
        # INI file given, simplest case
        inifile = evaluate_inipath(inifile)
    else:
        # No ini file given, need profile data
        get_user_profile_paths(vista_plus)
        # Find out where INI file is
        inifile = os.path.abspath(os.path.join(sabnzbd.DIR_LCLDATA, DEF_INI_FILE))

    # Long-path notation on Windows to be sure
    inifile = long_path(inifile)

    # If INI file at non-std location, then use INI location 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(inifile + ".bak") 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)

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

    if ipv6_hosting is not None:
        sabnzbd.cfg.ipv6_hosting.set(ipv6_hosting)

    # Determine web host address
    cherryhost, cherryport, browserhost, https_port = get_webhost(cherryhost, cherryport, https_port)
    enable_https = sabnzbd.cfg.enable_https()

    # When this is a daemon, just check and bail out if port in use
    if sabnzbd.DAEMON:
        if enable_https and https_port:
            try:
                portend.free(cherryhost, https_port, timeout=0.05)
            except IOError:
                abort_and_show_error(browserhost, cherryport)
            except:
                abort_and_show_error(browserhost, cherryport, "49")
        try:
            portend.free(cherryhost, cherryport, timeout=0.05)
        except IOError:
            abort_and_show_error(browserhost, cherryport)
        except:
            abort_and_show_error(browserhost, cherryport, "49")

    # Windows instance is reachable through registry
    url = None
    if sabnzbd.WIN32 and not new_instance:
        url = get_connection_info()
        if url and check_for_sabnzbd(url, upload_nzbs, autobrowser):
            exit_sab(0)

    # SSL
    if enable_https:
        port = https_port or cherryport
        try:
            portend.free(browserhost, port, timeout=0.05)
        except IOError as error:
            if str(error) == "Port not bound.":
                pass
            else:
                if not url:
                    url = "https://%s:%s%s/api?" % (browserhost, port, sabnzbd.cfg.url_base())
                if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
                    # Bail out if we have fixed our ports after first start-up
                    if sabnzbd.cfg.fixed_ports():
                        abort_and_show_error(browserhost, cherryport)
                    # Find free port to bind
                    newport = find_free_port(browserhost, port)
                    if newport > 0:
                        # Save the new port
                        if https_port:
                            https_port = newport
                            sabnzbd.cfg.https_port.set(newport)
                        else:
                            # In case HTTPS == HTTP port
                            cherryport = newport
                            sabnzbd.cfg.cherryport.set(newport)
        except:
            # Something else wrong, probably badly specified host
            abort_and_show_error(browserhost, cherryport, "49")

    # NonSSL check if there's no HTTPS or we only use 1 port
    if not (enable_https and not https_port):
        try:
            portend.free(browserhost, cherryport, timeout=0.05)
        except IOError as error:
            if str(error) == "Port not bound.":
                pass
            else:
                if not url:
                    url = "http://%s:%s%s/api?" % (browserhost, cherryport, sabnzbd.cfg.url_base())
                if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
                    # Bail out if we have fixed our ports after first start-up
                    if sabnzbd.cfg.fixed_ports():
                        abort_and_show_error(browserhost, cherryport)
                    # Find free port to bind
                    port = find_free_port(browserhost, cherryport)
                    if port > 0:
                        sabnzbd.cfg.cherryport.set(port)
                        cherryport = port
        except:
            # Something else wrong, probably badly specified host
            abort_and_show_error(browserhost, cherryport, "49")

    # We found a port, now we never check again
    sabnzbd.cfg.fixed_ports.set(True)

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

    if clean_up:
        xlist = globber_full(logdir)
        for x in xlist:
            if RSS_FILE_NAME not in x:
                try:
                    os.remove(x)
                except:
                    pass

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

    # Log-related constants we always need
    if logging_level is None:
        logging_level = sabnzbd.cfg.log_level()
    else:
        sabnzbd.cfg.log_level.set(logging_level)
    sabnzbd.LOGFILE = os.path.join(logdir, DEF_LOG_FILE)
    logformat = "%(asctime)s::%(levelname)s::[%(module)s:%(lineno)d] %(message)s"
    logger.setLevel(LOGLEVELS[logging_level + 1])

    try:
        if not no_file_log:
            rollover_log = logging.handlers.RotatingFileHandler(
                sabnzbd.LOGFILE, "a+", sabnzbd.cfg.log_size.get_int(), sabnzbd.cfg.log_backups()
            )
            rollover_log.setFormatter(logging.Formatter(logformat))
            logger.addHandler(rollover_log)

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

    # Fork on non-Windows processes
    if fork and not sabnzbd.WIN32:
        daemonize()
    else:
        if console_logging:
            console = logging.StreamHandler()
            console.setLevel(LOGLEVELS[logging_level + 1])
            console.setFormatter(logging.Formatter(logformat))
            logger.addHandler(console)
        if no_file_log:
            logging.info("Console logging only")

    logging.info("--------------------------------")
    logging.info("%s-%s (rev=%s)", sabnzbd.MY_NAME, sabnzbd.__version__, sabnzbd.__baseline__)
    logging.info("Full executable path = %s", sabnzbd.MY_FULLNAME)
    if sabnzbd.WIN32:
        suffix = ""
        if win64:
            suffix = "(win64)"
        try:
            logging.info("Platform = %s %s", platform.platform(), suffix)
        except:
            logging.info("Platform = %s <unknown>", suffix)
    else:
        logging.info("Platform = %s", os.name)
    logging.info("Python-version = %s", sys.version)
    logging.info("Arguments = %s", sabnzbd.CMDLINE)
    if sabnzbd.DOCKER:
        logging.info("Running inside a docker container")
    else:
        logging.info("Not inside a docker container")

    # Find encoding; relevant for external processing activities
    logging.info("Preferred encoding = %s", sabnzbd.encoding.CODEPAGE)

    # On Linux/FreeBSD/Unix "UTF-8" is strongly, strongly adviced:
    if not sabnzbd.WIN32 and not sabnzbd.DARWIN and not ("utf-8" in sabnzbd.encoding.CODEPAGE.lower()):
        logging.warning_helpful(
            T(
                "SABnzbd was started with encoding %s, this should be UTF-8. Expect problems with Unicoded file and directory names in downloads."
            ),
            sabnzbd.encoding.CODEPAGE,
        )

    # TODO: Remove after 3.1.0
    if sys.hexversion < 0x03060000:
        logging.warning_helpful("Python 3.5 is end-of-life. SABnzbd 3.2.0 will only run on Python 3.6 and above.")

    # SSL Information
    logging.info("SSL version = %s", ssl.OPENSSL_VERSION)

    # Load (extra) certificates if supplied by certifi
    # This is optional and provided in the binaries
    if importlib.util.find_spec("certifi") is not None:
        import certifi

        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:
        # List the number of certificates available (can take up to 1.5 seconds)
        ctx = ssl.create_default_context()
        logging.debug("Available certificates: %s", repr(ctx.cert_store_stats()))

        # Show IPv4/IPv6 address
        from sabnzbd.getipaddress import localipv4, publicipv4, ipv6

        mylocalipv4 = localipv4()
        if mylocalipv4:
            logging.debug("My local IPv4 address = %s", mylocalipv4)
        else:
            logging.debug("Could not determine my local IPv4 address")

        mypublicipv4 = publicipv4()
        if mypublicipv4:
            logging.debug("My public IPv4 address = %s", mypublicipv4)
        else:
            logging.debug("Could not determine my public IPv4 address")

        myipv6 = ipv6()
        if myipv6:
            logging.debug("My IPv6 address = %s", myipv6)
        else:
            logging.debug("Could not determine my IPv6 address")

        # Measure and log system performance measured by pystone and - if possible - CPU model
        from sabnzbd.utils.getperformance import getpystone, getcpu

        pystoneperf = getpystone()
        if pystoneperf:
            logging.debug("CPU Pystone available performance = %s", pystoneperf)
        else:
            logging.debug("CPU Pystone available performance could not be calculated")
        cpumodel = getcpu()  # Linux only
        if cpumodel:
            logging.debug("CPU model = %s", cpumodel)

    logging.info("Using INI file %s", inifile)

    if autobrowser is not None:
        sabnzbd.cfg.autobrowser.set(autobrowser)

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

    os.chdir(sabnzbd.DIR_PROG)

    sabnzbd.WEB_DIR = identify_web_template(sabnzbd.cfg.web_dir, DEF_STDINTF, fix_webname(web_dir))
    sabnzbd.WEB_DIR_CONFIG = identify_web_template(None, DEF_STDCONFIG, "")
    sabnzbd.WIZARD_DIR = os.path.join(sabnzbd.DIR_INTERFACES, "wizard")

    sabnzbd.WEB_COLOR = check_template_scheme(sabnzbd.cfg.web_color(), sabnzbd.WEB_DIR)
    sabnzbd.cfg.web_color.set(sabnzbd.WEB_COLOR)

    # Handle the several tray icons
    if sabnzbd.cfg.win_menu() and not sabnzbd.DAEMON:
        if sabnzbd.WIN32:
            import sabnzbd.sabtray

            sabnzbd.WINTRAY = sabnzbd.sabtray.SABTrayThread()
        elif sabnzbd.LINUX_POWER and os.environ.get("DISPLAY"):
            try:
                import gi

                gi.require_version("Gtk", "3.0")
                from gi.repository import Gtk
                import sabnzbd.sabtraylinux

                sabnzbd.LINUXTRAY = sabnzbd.sabtraylinux.StatusIcon()
            except:
                logging.info("python3-gi not found, no SysTray.")

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

    # HTTPS certificate generation
    https_cert = sabnzbd.cfg.https_cert.get_path()
    https_key = sabnzbd.cfg.https_key.get_path()
    https_chain = sabnzbd.cfg.https_chain.get_path()
    if not (sabnzbd.cfg.https_chain() and os.path.exists(https_chain)):
        https_chain = None

    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 not (os.path.exists(https_cert) and os.path.exists(https_key)):
            logging.warning(T("Disabled HTTPS because of missing CERT and KEY files"))
            enable_https = False
            sabnzbd.cfg.enable_https.set(False)

        # So the cert and key files do exist, now let's check if they are valid:
        trialcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
        try:
            trialcontext.load_cert_chain(https_cert, https_key)
            logging.info("HTTPS keys are OK")
        except:
            logging.warning(T("Disabled HTTPS because of invalid CERT and KEY files"))
            logging.info("Traceback: ", exc_info=True)
            enable_https = False
            sabnzbd.cfg.enable_https.set(False)

    # Starting of the webserver
    # Determine if this system has multiple definitions for 'localhost'
    hosts = all_localhosts()
    multilocal = len(hosts) > 1 and cherryhost in ("localhost", "0.0.0.0")

    # For 0.0.0.0 CherryPy will always pick IPv4, so make sure the secondary localhost is IPv6
    if multilocal and cherryhost == "0.0.0.0" and hosts[1] == "127.0.0.1":
        hosts[1] = "::1"

    # The Windows binary requires numeric localhost as primary address
    if cherryhost == "localhost":
        cherryhost = hosts[0]

    if enable_https:
        if https_port:
            # Extra HTTP port for primary localhost
            attach_server(cherryhost, cherryport)
            if multilocal:
                # Extra HTTP port for secondary localhost
                attach_server(hosts[1], cherryport)
                # Extra HTTPS port for secondary localhost
                attach_server(hosts[1], https_port, https_cert, https_key, https_chain)
            cherryport = https_port
        elif multilocal:
            # Extra HTTPS port for secondary localhost
            attach_server(hosts[1], cherryport, https_cert, https_key, https_chain)

        cherrypy.config.update(
            {
                "server.ssl_module": "builtin",
                "server.ssl_certificate": https_cert,
                "server.ssl_private_key": https_key,
                "server.ssl_certificate_chain": https_chain,
            }
        )
    elif multilocal:
        # Extra HTTP port for secondary localhost
        attach_server(hosts[1], cherryport)

    if no_login:
        sabnzbd.cfg.username.set("")
        sabnzbd.cfg.password.set("")

    mime_gzip = (
        "text/*",
        "application/javascript",
        "application/x-javascript",
        "application/json",
        "application/xml",
        "application/vnd.ms-fontobject",
        "application/font*",
        "image/svg+xml",
    )
    cherrypy.config.update(
        {
            "server.environment": "production",
            "server.socket_host": cherryhost,
            "server.socket_port": cherryport,
            "server.shutdown_timeout": 0,
            "log.screen": False,
            "engine.autoreload.on": False,
            "tools.encode.on": True,
            "tools.gzip.on": True,
            "tools.gzip.mime_types": mime_gzip,
            "request.show_tracebacks": True,
            "error_page.401": sabnzbd.panic.error_page_401,
            "error_page.404": sabnzbd.panic.error_page_404,
        }
    )

    # Do we want CherryPy Logging? Cannot be done via the config
    if cherrypylogging:
        sabnzbd.WEBLOGFILE = os.path.join(logdir, DEF_LOG_CHERRY)
        cherrypy.log.screen = True
        cherrypy.log.access_log.propagate = True
        cherrypy.log.access_file = str(sabnzbd.WEBLOGFILE)
    else:
        cherrypy.log.access_log.propagate = False

    # Force mimetypes (OS might overwrite them)
    forced_mime_types = {"css": "text/css", "js": "application/javascript"}

    static = {
        "tools.staticdir.on": True,
        "tools.staticdir.dir": os.path.join(sabnzbd.WEB_DIR, "static"),
        "tools.staticdir.content_types": forced_mime_types,
    }
    staticcfg = {
        "tools.staticdir.on": True,
        "tools.staticdir.dir": os.path.join(sabnzbd.WEB_DIR_CONFIG, "staticcfg"),
        "tools.staticdir.content_types": forced_mime_types,
    }
    wizard_static = {
        "tools.staticdir.on": True,
        "tools.staticdir.dir": os.path.join(sabnzbd.WIZARD_DIR, "static"),
        "tools.staticdir.content_types": forced_mime_types,
    }

    appconfig = {
        "/api": {
            "tools.auth_basic.on": False,
            "tools.response_headers.on": True,
            "tools.response_headers.headers": [("Access-Control-Allow-Origin", "*")],
        },
        "/static": static,
        "/wizard/static": wizard_static,
        "/favicon.ico": {
            "tools.staticfile.on": True,
            "tools.staticfile.filename": os.path.join(sabnzbd.WEB_DIR_CONFIG, "staticcfg", "ico", "favicon.ico"),
        },
        "/staticcfg": staticcfg,
    }

    # Make available from both URLs
    main_page = sabnzbd.interface.MainPage()
    cherrypy.Application.relative_urls = "server"
    cherrypy.tree.mount(main_page, "/", config=appconfig)
    cherrypy.tree.mount(main_page, sabnzbd.cfg.url_base(), 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:
        cherrypy.engine.start()
    except:
        logging.error(T("Failed to start web-interface: "), exc_info=True)
        abort_and_show_error(browserhost, cherryport)

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

    if sabnzbd.WIN32:
        if enable_https:
            mode = "s"
        else:
            mode = ""
        api_url = "http%s://%s:%s%s/api?apikey=%s" % (
            mode,
            browserhost,
            cherryport,
            sabnzbd.cfg.url_base(),
            sabnzbd.cfg.api_key(),
        )

        # Write URL directly to registry
        set_connection_info(api_url)

    if pid_path or pid_file:
        sabnzbd.pid_file(pid_path, pid_file, cherryport)

    # Stop here in case of fatal errors
    if sabnzbd.NO_DOWNLOADING:
        return

    # Start all SABnzbd tasks
    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()

    # Upload any nzb/zip/rar/nzb.gz/nzb.bz2 files from file association
    if upload_nzbs:
        for upload_nzb in upload_nzbs:
            sabnzbd.add_nzbfile(upload_nzb)

    # Set URL for browser
    if enable_https:
        browser_url = "https://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
    else:
        browser_url = "http://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
    sabnzbd.BROWSER_URL = browser_url

    if not autorestarted:
        launch_a_browser(browser_url)
        notifier.send_notification("SABnzbd", T("SABnzbd %s started") % sabnzbd.__version__, "startup")
        # Now's the time to check for a new version
        check_latest_version()
    autorestarted = False

    # ZeroConfig/Bonjour needs a ip. Lets try to find it.
    try:
        z_host = socket.gethostbyname(socket.gethostname())
    except socket.gaierror:
        z_host = cherryhost
    sabnzbd.zconfig.set_bonjour(z_host, cherryport)

    # Have to keep this running, otherwise logging will terminate
    timer = 0
    while not sabnzbd.SABSTOP:
        if sabnzbd.LAST_WARNING:
            msg = sabnzbd.LAST_WARNING
            sabnzbd.LAST_WARNING = None
            sabnzbd.notifier.send_notification(T("Warning"), msg, "warning")
        if sabnzbd.LAST_ERROR:
            msg = sabnzbd.LAST_ERROR
            sabnzbd.LAST_ERROR = None
            sabnzbd.notifier.send_notification(T("Error"), msg, "error")

        time.sleep(3)

        # Check for loglevel changes
        if LOG_FLAG:
            LOG_FLAG = False
            level = LOGLEVELS[sabnzbd.cfg.log_level() + 1]
            logger.setLevel(level)
            if console_logging:
                console.setLevel(level)

        # 30 sec polling tasks
        if timer > 9:
            timer = 0
            # Keep OS 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
                sabnzbd.TRIGGER_RESTART = True
        else:
            timer += 1

        # 3 sec polling tasks
        # Check for auto-restart request
        # Or special restart cases like Mac and WindowsService
        if sabnzbd.TRIGGER_RESTART:
            logging.info("Performing triggered restart")
            # Shutdown
            sabnzbd.shutdown_program()

            if sabnzbd.downloader.Downloader.do.paused:
                sabnzbd.RESTART_ARGS.append("-p")
            if autorestarted:
                sabnzbd.RESTART_ARGS.append("--autorestarted")
            sys.argv = sabnzbd.RESTART_ARGS

            os.chdir(org_dir)
            # If OSX frozen restart of app instead of embedded python
            if hasattr(sys, "frozen") and sabnzbd.DARWIN:
                # [[NSProcessInfo processInfo] processIdentifier]]
                # logging.info("%s" % (NSProcessInfo.processInfo().processIdentifier()))
                my_pid = os.getpid()
                my_name = sabnzbd.MY_FULLNAME.replace("/Contents/MacOS/SABnzbd", "")
                my_args = " ".join(sys.argv[1:])
                cmd = 'kill -9 %s && open "%s" --args %s' % (my_pid, my_name, my_args)
                logging.info("Launching: %s", cmd)
                os.system(cmd)
            elif sabnzbd.WIN_SERVICE:
                # Use external service handler to do the restart
                # Wait 5 seconds to clean up
                subprocess.Popen("timeout 5 & sc start SABnzbd", shell=True)
            else:
                cherrypy.engine._do_execv()

    config.save_config()

    if sabnzbd.WINTRAY:
        sabnzbd.WINTRAY.terminate = True
    if sabnzbd.WIN32:
        del_connection_info()

    # Send our final goodbyes!
    notifier.send_notification("SABnzbd", T("SABnzbd shutdown finished"), "startup")
    logging.info("Leaving SABnzbd")
    sys.stderr.flush()
    sys.stdout.flush()
    sabnzbd.pid_file()

    if hasattr(sys, "frozen") and sabnzbd.DARWIN:
        try:
            AppHelper.stopEventLoop()
        except:
            # Failing AppHelper libary!
            os._exit(0)
    elif sabnzbd.WIN_SERVICE:
        # Do nothing, let service handle it
        pass
    else:
        os._exit(0)


##############################################################################
# Windows Service Support
##############################################################################


if sabnzbd.WIN32:
    import servicemanager

    class SABnzbd(win32serviceutil.ServiceFramework):
        """ Win32 Service Handler """

        _svc_name_ = "SABnzbd"
        _svc_display_name_ = "SABnzbd Binary Newsreader"
        _svc_deps_ = ["EventLog", "Tcpip"]
        _svc_description_ = (
            "Automated downloading from Usenet. "
            'Set to "automatic" to start the service at system startup. '
            "You may need to login with a real user account when you need "
            "access to network shares."
        )

        # Only SABnzbd-console.exe can print to the console, so the service is installed
        # from there. But we run SABnzbd.exe so nothing is logged. Logging can cause the
        # Windows Service to stop because the output buffers are full.
        if hasattr(sys, "frozen"):
            _exe_name_ = "SABnzbd.exe"

        def __init__(self, args):
            win32serviceutil.ServiceFramework.__init__(self, args)
            self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
            sabnzbd.WIN_SERVICE = self

        def SvcDoRun(self):
            msg = "SABnzbd-service %s" % sabnzbd.__version__
            self.Logger(servicemanager.PYS_SERVICE_STARTED, msg + " has started")
            sys.argv = get_serv_parms(self._svc_name_)
            main()
            self.Logger(servicemanager.PYS_SERVICE_STOPPED, msg + " has stopped")

        def SvcStop(self):
            sabnzbd.shutdown_program()
            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_, msg)
            )

        def ErrLogger(self, msg, text):
            win32evtlogutil.ReportEvent(
                self._svc_display_name_,
                servicemanager.PYS_SERVICE_STOPPED,
                0,
                servicemanager.EVENTLOG_ERROR_TYPE,
                (self._svc_name_, msg),
                text,
            )


SERVICE_MSG = """
You may need to set additional Service parameters!
Verify the settings in Windows Services (services.msc).

https://sabnzbd.org/wiki/advanced/sabnzbd-as-a-windows-service
"""


def handle_windows_service():
    """Handle everything for Windows Service
    Returns True when any service commands were detected or
    when we have started as a service.
    """
    # Detect if running as Windows Service (only Vista and above!)
    # Adapted from https://stackoverflow.com/a/55248281/5235502
    # Only works when run from the exe-files
    if hasattr(sys, "frozen") and win32ts.ProcessIdToSessionId(win32api.GetCurrentProcessId()) == 0:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(SABnzbd)
        servicemanager.StartServiceCtrlDispatcher()
        return True

    # Handle installation and other options
    service, sab_opts, serv_opts, _upload_nzbs = commandline_handler()

    if service:
        if service in ("install", "update"):
            # In this case check for required parameters
            path = get_f_option(sab_opts)
            if not path:
                print(("The -f <path> parameter is required.\n" "Use: -f <path> %s" % service))
                return True

            # First run the service installed, because this will
            # set the service key in the Registry
            win32serviceutil.HandleCommandLine(SABnzbd, argv=serv_opts)

            # Add our own parameter to the Registry
            if set_serv_parms(SABnzbd._svc_name_, sab_opts):
                print(SERVICE_MSG)
            else:
                print("ERROR: Cannot set required registry info.")
        else:
            # Pass the other commands directly
            win32serviceutil.HandleCommandLine(SABnzbd)

    return bool(service)


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


if __name__ == "__main__":
    # We can only register these in the main thread
    signal.signal(signal.SIGINT, sabnzbd.sig_handler)
    signal.signal(signal.SIGTERM, sabnzbd.sig_handler)

    if sabnzbd.WIN32:
        if not handle_windows_service():
            main()

    elif sabnzbd.DARWIN and sabnzbd.FOUNDATION:

        # OSX binary runner
        from threading import Thread
        from PyObjCTools import AppHelper
        from AppKit import NSApplication
        from sabnzbd.osxmenu import SABnzbdDelegate

        # Need to run the main application in separate thread because the eventLoop
        # has to be in the main thread. The eventLoop is required for the menu.
        # This code is made with trial-and-error, please improve!
        class startApp(Thread):
            def run(self):
                main()
                AppHelper.stopEventLoop()

        sabApp = startApp()
        sabApp.start()

        # Initialize the menu
        shared_app = NSApplication.sharedApplication()
        sabnzbd_menu = SABnzbdDelegate.alloc().init()
        shared_app.setDelegate_(sabnzbd_menu)
        # Build the menu
        sabnzbd_menu.awakeFromNib()
        # Run the main eventloop
        AppHelper.runEventLoop()
    else:
        main()
