#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
# vim:tabstop=4:softtabstop=4:shiftwidth=4:expandtab:filetype=python:
# -----------------------------------------------------------------------
# freevo - the main entry point to the whole suite of applications
# -----------------------------------------------------------------------
# $Id: freevo 10416 2008-02-21 21:50:31Z 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 *


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)
  --profile      activate cProfile and write stats to /tmp
  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 sys.hexversion >= 0x02040000:
        # python seems to be ok
        search = ('python', 'python2')
    elif sys.hexversion >= 0x02030000:
        # try python2.4, else take python
        search = ('python2.4', 'python')
    else:
        # python is too old, try to find python2.4 or python2
        search = ('python2.4', '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
#--------------------------------------------------------------------------------
debug = 0
while '--debug' in sys.argv or '-d' in sys.argv:
    debug += 1
    if '--debug' in sys.argv:
        sys.argv.remove('--debug')
    else:
        sys.argv.remove('-d')
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.4'
            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
profile = 0

if '--profile' in sys.argv or '-p' in sys.argv:
    profile = 1
    if '--profile' in sys.argv:
        sys.argv.remove('--profile')
    else:
        sys.argv.remove('-p')

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 = []
    profile = 0
    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)

if profile:
    if sys.hexversion > 0x02050000:
        python += ['-m', 'cProfile', '-o', '/tmp/%s.stats' % name]
    else:
        python += ['-m', 'profile', '-o', '/tmp/%s.stats' % name]
    
start(name, python+proc, bg, check)
