#!/usr/bin/python
# vim:tabstop=4:softtabstop=4:shiftwidth=4:expandtab:filetype=python:
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# freevo - the main entry point to the whole suite of applications
# -----------------------------------------------------------------------
# $Id: freevo 10094 2007-11-11 04:44:00Z duncan $
#
# Notes: This is a rewrite of the old shell script in Python
# Todo:
#
# -----------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2003 Krister Lagerstrom, et al.
# Please see the file freevo/Docs/CREDITS for a complete list of authors.
#
# 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 MER-
# CHANTABILITY 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.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------

import os
import sys
import time
import popen2

from signal import *


debug = 0
cmdfile = None
def _debug_(message, level=1):
    global debug
    if debug >= level:
        print message


def _gdb_script_(message):
    global debug
    global cmdfile
    if debug < 2:
        return
    print >>cmdfile, message


def show_help():
    """
    show how to use this script
    """
    helper_list = ''
    for helper in os.listdir(os.environ['FREEVO_HELPERS']):
        if helper.endswith('.py') and not helper == '__init__.py':
            helper_list += '  ' + helper[:-3] + '\n'

    print '''\
freevo [ script | options]
options:
  -fs            start freevo in a new x session in full-screen
  -doc           create api documentation (for developers)
  -trace         activate full trace (useful for debugging)
  setup          run freevo setup to scan your environment
  start          start freevo as daemon in background
  stop           stop the current freevo process

freevo can start the following scripts, use --help on these
scripts to get more informations about options.

%s

Example: freevo imdb --help"
         freevo webserver start"

You can also create a symbolic link to freevo with the name of the
script you want to execute. E.g. put a link imdb pointing to freevo
in your path to access the imdb helper script

Example: ln -s path-to-freevo imdb
         imdb --help

Before running freevo the first time, you need to run \'freevo setup\'
After that, you can run freevo without parameter.
    ''' % helper_list
    sys.exit(0)


def get_python(check_freevo):
    """
    get the newest version of python [ with freevo installed ]
    """
    _debug_('version=%r' % (sys.version)) 
    if float(sys.version[0:3]) >= 2.3:
        # python seems to be ok
        search = ('python', 'python2')
    elif float(sys.version[0:3]) >= 2.2:
        # try python2.3, else take python
        search = ('python2.3', 'python')
    else:
        # python is too old, try to find python2.3 or python2
        search = ('python2.3', 'python2')

    for python in search:
        for path in os.environ['PATH'].split(':'):
            if os.path.isfile(os.path.join(path, python)):
                # we found the binary for python
                if not check_freevo:
                    # return if we don't check for an installed version
                    # of freevo
                    _debug_('python=%r' % (python)) 
                    return python

                # try to import freevo with this python and get
                # the path
                cmd = '%s -c "import freevo; print freevo.__path__[0]"' % python
                child = popen2.Popen4(cmd)
                while 1:
                    data = child.fromchild.readline()
                    if not data:
                        break
                    if os.path.isdir(data[:-1]):
                        # ok, found it, close child and return
                        child.wait()
                        child.fromchild.close()
                        if child.childerr:
                            child.childerr.close()
                        child.tochild.close()
                        _debug_('python=%r data=%r' % (python, data[:-1])) 
                        return python, data[:-1]

                child.wait()
                child.fromchild.close()
                if child.childerr:
                    child.childerr.close()
                child.tochild.close()

    # nothing found? That's bad!
    if check_freevo:
        _debug_('python=%r data=%r' % (None, None))
        return None, None
    return None


def getpid(name, arg):
    """
    get pid of running 'name'
    """
    _debug_('getpid(name=%r, arg=%r)' % (name, arg))
    for fname in ('/var/run/' + name  + '-%s.pid' % os.getuid(),
                  '/tmp/' + name + '-%s.pid' % os.getuid()):
        if os.path.isfile(fname):
            f = open(fname)
            try:
                pid = int(f.readline()[:-1])
            except ValueError:
                # file does not contain a number
                _debug_('fname=%r pid=%r' % (fname, 0)) 
                return fname, 0
            f.close()

            proc = '/proc/' + str(pid) + '/cmdline'
            # FIXME: BSD support missing here
            try:
                if os.path.isfile(proc):
                    f = open(proc)
                    proc_arg = f.readline().split('\0')[:-1]
                    f.close()
                else:
                    # process not running
                    _debug_('fname=%r pid=%r' % (fname, 0)) 
                    return fname, 0

            except (OSError, IOError):
                # running, but not freevo (because not mine)
                _debug_('fname=%r pid=%r' % (fname, 0)) 
                return fname, 0

            if '-OO' in proc_arg:
                proc_arg.remove('-OO')

            if proc_arg and ((arg[0].find('runapp') == -1 and \
                len(proc_arg)>2 and arg[1] != proc_arg[1]) or \
                len(proc_arg)>3 and arg[2] != proc_arg[2]):
                # different proc I guess
                try:
                    os.unlink(fname)
                except OSError:
                    pass
                _debug_('fname=%r pid=%r' % (fname, 0)) 
                return fname, 0
            _debug_('fname=%r pid=%r' % (fname, pid)) 
            return fname, pid
    _debug_('fname=%r pid=%r' % (fname, 0)) 
    return fname, 0


def stop(name, arg):
    """
    stop running process 'name'
    """
    fname, pid = getpid(name, arg)
    if not pid:
        _debug_('cannot kill %r no pid' % (name))
        return 0
    try:
        for signal in (SIGINT, SIGTERM, SIGKILL):
            try:
                _debug_('trying to kill %r pid=%r with signal=%r' % (name, pid, signal))
                os.kill(pid, signal)
                for i in range(10):
                    if getpid(name, arg)[1] == 0:
                        _debug_('killed %r pid=%r with signal=%r (%r)' % (name, pid, signal, i))
                        break
                    time.sleep(0.1)

            except OSError, e:
                _debug_('kill(pid=%r signal=%r): %s' % (pid, signal, e))
                pass
            if getpid(name, arg)[1] == 0:
                return 1
        return 0
    finally:
        try:
            os.unlink(fname)
            _debug_('%s removed' % (fname))
        except OSError, e:
            _debug_('%s NOT removed' % (fname))


def start(name, arg, bg, store=1):
    """
    start a process
    """
    global cmdfile
    _debug_('start(name=%r, arg=%r, bg=%r, store=%r)' % (name, arg, bg, store))
    _gdb_script_('cat > /tmp/freevo-gdb << _END_')
    _gdb_script_('b main')
    _gdb_script_('r %s' % ' '.join(arg[1:]))
    _gdb_script_('_END_')
    _gdb_script_('gdb -x /tmp/freevo-gdb %s' % (arg[0]))
    if cmdfile: cmdfile.close()

    pid = os.fork()
    if pid:
        if store:
            try:
                f = open('/var/run/' + name + '-%s.pid' % os.getuid(), 'w')
            except (OSError, IOError):
                f = open('/tmp/' + name + '-%s.pid' % os.getuid(), 'w')

            f.write(str(pid)+'\n')
            f.close()

        if not bg:
            try:
                os.waitpid(pid, 0)
            except KeyboardInterrupt:
                os.kill(pid, SIGTERM)
                try:
                    os.waitpid(pid, 0)
                except KeyboardInterrupt:
                    pass
                if store and os.path.isfile(f.name):
                    os.unlink(f.name)
    else:
        os.execvp(arg[0], arg)

#--------------------------------------------------------------------------------
# Main block
#--------------------------------------------------------------------------------
while len(sys.argv) > 1 and sys.argv[1] in ('--debug', '-d'):
    debug += 1
    sys.argv.pop(1)
if debug >= 2:
    cmdfile = open('/tmp/freevo-gdb.sh', 'w')
    print >>cmdfile,'#!/bin/bash'

freevo_script = os.path.abspath(sys.argv[0])
if os.path.islink(freevo_script):
    freevo_script = os.readlink(freevo_script)

if os.path.isdir(os.path.join(os.path.dirname(freevo_script), 'src/plugins')):
    #
    # we start freevo from a directory
    #
    dname = os.path.dirname(freevo_script)
    freevo_python  = os.path.join(dname, 'src')
    freevo_helpers = os.path.join(dname, 'src/helpers')
    freevo_locale  = os.path.join(dname, 'i18n')
    freevo_share   = os.path.join(dname, 'share')
    freevo_contrib = os.path.join(dname, 'contrib')
    freevo_config  = os.path.join(dname, 'freevo_config.py')

    if os.path.isfile(os.path.join(dname, 'runtime/runapp')):
        #
        # there is a runtime, use it
        #
        runapp = os.path.join(dname, './runtime/runapp')
        python = [ runapp, os.path.join(dname, './runtime/apps/freevo_python') ]
        preload = ''
        f = open(os.path.join(dname, './runtime/preloads'))
        for lib in f.readline()[:-1].split(' '):
            preload += os.path.join(dname, lib) + ':'
        if preload:
            preload = preload[:-1]
        os.environ['FREEVO_PRELOADS'] = preload
        # FIXME: use FREEVO_PRELOADS in runapp to avoid chdirs

    else:
        #
        # no runtime, get best python version
        #
        python = get_python(0)
        if not python:
            print 'Can\'t find python >= 2.3'
            sys.exit(0)
        python = [ python ]
        runapp = ''
else:
    #
    # installed version of freevo, get best python + freevo path
    #
    if not os.path.isfile(freevo_script):
        for path in os.environ['PATH'].split(':'):
            if os.path.isfile(os.path.join(path, freevo_script)):
                freevo_script = os.path.join(path, freevo_script)
    python, freevo_python = get_python(1)
    if not python:
        print 'can\'t find python version with installed freevo'
        sys.exit(0)
    freevo_helpers = os.path.join(freevo_python, 'helpers')
    dname = os.path.abspath(os.path.join(os.path.dirname(freevo_script), '../'))
    freevo_locale  = os.path.join(dname, 'share/locale')
    freevo_share   = os.path.join(dname, 'share/freevo')
    freevo_contrib = os.path.join(freevo_share, 'contrib')
    freevo_config  = os.path.join(freevo_share, 'freevo_config.py')
    runapp         = ''
    python = [ python ]

# add the variables from above into environ so Freevo can use them, too
for var in ('freevo_script', 'runapp', 'freevo_python', 'freevo_locale',
            'freevo_share', 'freevo_contrib', 'freevo_config', 'freevo_helpers'):
    os.environ[var.upper()] = eval(var)
    _debug_('%s=%r' % (var.upper(), os.environ[var.upper()]))
    _gdb_script_('export %s=%s' % (var.upper(), os.environ[var.upper()]))

# extend PYTHONPATH to freevo
if os.environ.has_key('PYTHONPATH'):
    os.environ['PYTHONPATH'] = '%s:%s' % (freevo_python, os.environ['PYTHONPATH'])
else:
    os.environ['PYTHONPATH'] = freevo_python
_debug_('%s=%r' % ('PYTHONPATH', os.environ['PYTHONPATH']))
_gdb_script_('export %s=%s' % ('PYTHONPATH', os.environ['PYTHONPATH']))


# extend PATH to make sure the basics are there
os.environ['PATH'] = '%s:/usr/bin:/bin:/usr/local/bin:' % os.environ['PATH'] + \
                     '/usr/X11R6/bin/:/sbin:/usr/sbin'
_debug_('%s=%r' % ('PATH', os.environ['PATH']))
_gdb_script_('export %s=%s' % ('PATH', os.environ['PATH']))

# set basic env variables
if not os.environ.has_key('HOME') or not os.environ['HOME']:
    os.environ['HOME'] = '/root'
if not os.environ.has_key('USER') or not os.environ['USER']:
    os.environ['USER'] = 'root'
_debug_('%s=%r' % ('USER', os.environ['USER']))
_debug_('%s=%r' % ('HOME', os.environ['HOME']))

# now check what and how we should start freevo
bg    = 0 # start in background
proc  = [ os.path.abspath(os.path.join(freevo_python, 'main.py')) ]
name  = os.path.splitext(os.path.basename(os.path.abspath(sys.argv[0])))[0]
check = 1 # check running instance

if len(sys.argv) > 1:
    arg = sys.argv[1]
else:
    arg = ''

# check the args
if arg in ('--help', '-h'):
    # show help
    show_help()

if arg == 'stop':
    # stop running freevo
    if not stop(name, python + proc):
        print 'freevo not running'
    sys.exit(0)

elif arg == 'start':
    # start freevo in background
    sys.argv = [ sys.argv[0] ]
    bg = 1

elif arg == '-fs':
    # start X server and run freevo, ignore everything else for now
    server_num = 0
    while 1:
        if not os.path.exists('/tmp/.X11-unix/X' + str(server_num)):
            break
        server_num += 1
    sys.stdin.close()
    os.execvp('xinit', [ 'xinit', freevo_script, '-force-fs',  '--', ':'+str(server_num) ] + sys.argv[2:])

elif arg == 'execute':
    # execute a python script
    proc  = sys.argv[2:]
    check = 0

elif arg == 'setup':
    # run setup
    proc = [ os.path.join(freevo_python, 'setup_freevo.py') ] + sys.argv[2:]

elif arg == 'prompt':
    # only run python inside the freevo env
    proc = []
    check = 0

elif arg == 'runapp':
    # Oops, runapp. We don't start python, we start sys.argv[1]
    # with the rest as args
    python[-1] = sys.argv[2]
    proc       = sys.argv[3:]
    check      = 0

elif arg and name == 'freevo' and not arg.startswith('-'):
    # start a helper. arg is the name of the script in
    # the helpers directory
    name     = arg
    proc     = [ os.path.join(freevo_python, 'helpers', name + '.py') ]
    if len(sys.argv) > 2:
        if sys.argv[2] == 'stop':
            if not stop(name, python + proc):
                print '%s not running' % name
            sys.exit(0)
        if sys.argv[2] == 'start':
            bg = 1
            sys.argv = [ sys.argv[0] ]
        else:
            sys.argv = [ sys.argv[0] ] + sys.argv[2:]

elif arg and name == 'freevo' and not (arg in ['-force-fs', '-trace', '-doc']):
    # ok, don't know about that arg, freevo should be started, but
    # it's also no freevo arg
    print 'unknown command line option: %s' % sys.argv[1:]
    print
    show_help()

else:
    # arg for freevo
    proc += sys.argv[1:]


if name != 'freevo':
    proc = [ os.path.join(freevo_python, 'helpers', name + '.py') ] +  sys.argv[1:]
    if not os.path.isfile(proc[0]):
        if os.path.isfile(name):
            name = os.path.splitext(os.path.basename(name))[0]
            proc = proc[1:]
        else:
            proc = [ os.path.join(freevo_python, 'helpers', name + '.py') ] +  sys.argv[1:]
            print 'can\'t find helper %s' % name
            sys.exit(1)

if check and getpid(name, python + proc)[1]:
    if name != 'freevo':
        print '%s still running, run \'freevo %s stop\' to stop' % (name, name)
    else:
        print 'freevo still running, run \'freevo stop\' to stop'
    sys.exit(0)

start(name, python + proc , bg, check)
