#!/usr/bin/env python2

# -*- coding: utf-8; mode: python -*-
#
# Cherokee-admin
#
# Authors:
#      Alvaro Lopez Ortega <alvaro@alobbs.com>
#
# Copyright (C) 2001-2014 Alvaro Lopez Ortega
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License as published by the Free Software Foundation.
#
# 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 os
import re
import sys
import urllib2

APP_NAME = "Cherokee Tweak"

APP_COPY_NOTICE =                                                                  \
    "Written by Alvaro Lopez Ortega <alvaro@alobbs.com>\n\n"                       \
    "Copyright (C) 2001-2014 Alvaro Lopez Ortega.\n"                               \
    "This is free software; see the source for copying conditions.  There is NO\n" \
    "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"


USAGE =                                                                     \
    "Usage:\n"                                                              \
    "  cherokee-tweak [options] URL info \n"                                \
    "  cherokee-tweak [options] URL purge \n"                               \
    "  cherokee-tweak [options] URL restart \n"                             \
    "  cherokee-tweak [options] URL sources \n"                             \
    "  cherokee-tweak [options] URL kill-source ID \n"                      \
    "  cherokee-tweak [options] URL close-connection ID \n"                 \
    "  cherokee-tweak [options] URL logrotate   PATH \n"                    \
    "  cherokee-tweak [options] URL trace       TRACE \n\n"                 \
    " Commands:\n"                                                          \
    "  info                          Prints info about the server status\n" \
    "  purge                         Purge objects from the cache\n"        \
    "  restart                       Graceful restart of the webserver\n"   \
    "  sources                       Prints a list of the info. sources\n"  \
    "  kill-source                   Kill a remote information source\n"    \
    "  close-connection              Removes an active connection\n"        \
    "  logrotate                     Rotates a local log file\n"            \
    "  trace                         Manipulates the tracing mechanism\n"   \
    "                                (to disable: trace off)\n\n"           \
    " Parameters:\n"                                                        \
    "  -h,  --help                   Print this help\n"                     \
    "  -V,  --version                Print version and exit\n\n"            \
    " Options:\n"                                                           \
    "  -u,  --user=STRING            User name\n"                           \
    "  -p,  --password=STRING        Password\n\n"                          \
    "Report bugs to http://bugs.cherokee-project.com\n"


command = None
url = None
user = None
password = None
log = None
trace = None
ksource = None
cconnection = None

def parse_args():
    global command, url
    global user, password
    global log, trace, ksource, cconnection

    if '-h' in sys.argv or '--help' in sys.argv:
        print USAGE
        raise SystemExit

    if '-v' in sys.argv or '--version' in sys.argv:
        print APP_NAME
        print APP_COPY_NOTICE
        raise SystemExit

    # Check length
    if len(sys.argv) < 3:
        print USAGE
        raise SystemExit

    # Parse parameters
    argn = 1
    while argn < len(sys.argv):
        arg = sys.argv[argn]

        if arg.startswith('--user='):
            user = arg[7:]
            argn += 1
            continue
        elif arg == '-u':
            user = sys.argv[argn+1]
            argn += 2
            continue

        elif arg.startswith('--password='):
            password = arg[11:]
            argn += 1
            continue
        elif arg == '-p':
            password = sys.argv[argn+1]
            argn += 2
            continue

        else:
            if arg.startswith('-'):
                print USAGE
                raise SystemExit
            break

    if len(sys.argv) <= (argn + 1):
        return usage()

    # Mandatory parameter
    url     = sys.argv[argn]
    command = sys.argv[argn + 1]
    argn += 2

    # Check command
    if command == 'info':
        return
    elif command == 'purge':
        return
    elif command == 'restart':
        return
    elif command == 'sources':
        return
    elif command == 'trace':
        if len(sys.argv) == (argn + 1):
            trace = sys.argv[argn]
            argn += 1
        return

    if len(sys.argv) <= argn:
        return usage()

    if command == 'logrotate':
        log = sys.argv[argn]
        argn += 1
        return
    elif command == 'kill-source':
        ksource = sys.argv[argn]
        argn += 1
        return
    elif command == 'close-connection':
        cconnection = sys.argv[argn]
        argn += 1
        return

    return usage()


def usage ():
    print USAGE
    raise SystemExit


def http_request (url_py, post_info=None, method=None):
    # Authtentication
    if user or password:
        passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
        passman.add_password (None, url, user, password)

        authhandler_digest = urllib2.HTTPDigestAuthHandler (passman)
        authhandler_basic  = urllib2.HTTPBasicAuthHandler  (passman)

        opener = urllib2.build_opener (authhandler_digest, authhandler_basic)
        urllib2.install_opener (opener)
    else:
        opener = urllib2.build_opener (urllib2.HTTPHandler)

    # Request
    request = urllib2.Request (url_py, post_info)
    if method:
        request.get_method = lambda: method

    # Perform
    try:
        conn = urllib2.urlopen (request)
    except urllib2.HTTPError, e:
        if e.code == 401:
            print >> sys.stderr, "Error in Authentication"
            raise SystemExit
        raise

    return conn.read()


def request (commands):
    tmp       = url + '/py'
    url_py    = tmp[:7] + tmp[7:].replace('//','/')
    post_info = "%s\n" %('\n'.join(commands))

    try:
        return http_request (url_py, post_info)
    except urllib2.HTTPError, e:
        if e.code == 404:
            print >> sys.stderr, "Invalid URL"
            sys.exit(1)
        raise


def eval_safe (txt):
    try:
        return eval(txt)
    except:
        print "Failed to evaluate: ========"
        print txt
        print "============================"
        raise

#
# Sources
#

def print_sources():
    # Query
    responses_str = request (['get server.sources'])
    responses = eval_safe(responses_str)
    sources = responses[0]

    # Print
    for source in sources:
        type_ = source.get('type')
        print "\tID:            %s" %(source.get('id'))
        print "\tType:          %s" %(type_)
        print "\tBind:          %s" %(source.get('bind') or '')
        if type_ == 'interpreter':
            print "\tPID:           %d" %(source.get('PID') or -1)
            print "\tTimeout:       %d" %(source.get('timeout') or -1)
            print "\tDebug:         %s" %(('No','Yes')[int(source.get('debug'))])
            print "\tInterpreter:   %s" %(source.get('interpreter') or '')

        if sources.index(source) < len(sources)-1:
            print "\t---------------------"
    print

def kill_source():
    # Query
    responses_str = request (['kill server.source %s'%(ksource)])
    responses = eval_safe(responses_str)

    # Report
    print "Source %s: %s" %(ksource, responses[0]['source'])
    print


#
# Close Connection
#

def close_connection():
    # Query
    responses_str = request (['close server.connection %s'%(cconnection)])
    responses = eval_safe(responses_str)

    # Report
    print "Close connection %s: %s" %(cconnection, responses[0]['close'])
    print

#
# Info
#

def print_info():
    # Query
    info_str = request (['get server.ports',
                         'get server.traffic',
                         'get server.thread_num',
                         'get server.connections'])
    info = eval_safe(info_str)
    ports   = info[0]
    traffic = info[1]
    threads = info[2]
    conns   = info[3]

    # General
    print "General:"
    print "\tThread number: %d" %(threads.get('thread_num'))
    print

    # Traffic
    if traffic.get('tx') != -1 and traffic.get('rx') != -1:
        print "Traffic:"
        print "\tTX:            %d" %(traffic.get('tx'))
        print "\tTX formatted:  %s" %(traffic.get('tx_formatted') or '')
        print "\tRX:            %d" %(traffic.get('rx'))
        print "\tRX formatted:  %s" %(traffic.get('rx_formatted') or '')
        print

    # Incoming Ports
    print "Ports:"
    for port in ports:
        print "\tID:            %d" %(port.get('id'))
        print "\tPort:          %d" %(port.get('port'))
        print "\tTLS:           %s" %(('No','Yes')[int(port.get('tls'))])
        print "\tBind:          %s" %(port.get('bind') or '')
        if ports.index(port) < len(ports)-1:
            print "\t---------------------"
    print

    # Connection details
    print "Connections:"
    for conn in conns:
        print "\tID:            %s" %(conn.get('id'))
        print "\tRequest:       %s" %(conn.get('request',''))
        print "\tHandler:       %s" %(conn.get('handler'))
        print "\tPhase:         %s" %(conn.get('phase'))
        print "\tIncoming IP:   %s" %(conn.get('ip'))
        print "\tRX:            %s" %(conn.get('rx'))
        print "\tTX:            %s" %(conn.get('tx'))
        print "\tTotal size:    %s" %(conn.get('total_size') or '')
        print "\tPercentage:    %s" %(conn.get('percent') or '')
        print "\tIcon:          %s" %(conn.get('icon'))
        if conns.index(conn) < len(conns)-1:
            print "\t---------------------"
    print


#
# Trace
#

def print_trace():
    trace_str = request (['get server.trace'])
    traces = eval_safe(trace_str)
    print "Trace: %s" %(traces[0].get('trace') or 'Disabled')
    print

def set_trace():
    global trace
    if trace == "off":
        trace = ""
    trace_str = request (['set server.trace %s'%(trace)])
    traces = eval_safe(trace_str)
    print "Trace set: %s" %(('Failed', 'Succeed')[int(traces[0].get('set'))])
    print


#
# Log Rotation
#

def do_logrotate():
    # Look for the new log name
    dirname     = os.path.dirname (log) or '.'
    basename    = os.path.basename (log)
    num         = 0
    new_logname = None

    similar = filter (lambda x: x.startswith(basename), os.listdir(dirname))
    for filename in similar:
        tmp = re.findall ('%s\.(\d)+'%(basename), filename)
        if tmp:
            num = max (int(tmp[0]), num)

    dot = basename.rfind('.')
    if dot != -1:
        if basename[dot:].isdigit():
            new_logname = '%s.%d' %(basename[dot:], num+1)

    if not new_logname:
        new_logname = '%s.%d' %(basename, num+1)

    new_logpath = os.path.join (dirname, new_logname)

    # Enable Backup-mode
    print "Enabling Back-up mode.."
    backup_str = request (['set server.backup_mode on'])
    backup = eval_safe(backup_str)
    assert backup[0]['backup_mode'], "Couldn't set backup mode"

    # Rotate the log
    print "Moving %s to %s" %(log, new_logpath)
    os.rename (log, new_logpath)

    # Enable Backup-mode
    print "Disabling Back-up mode.."
    backup_str = request (['set server.backup_mode off'])
    backup = eval_safe(backup_str)
    assert not backup[0]['backup_mode'], "Couldn't unset backup mode"


#
# Purge from Cache
#

def do_purge():
    try:
        response_py = http_request (url, method="PURGE")
    except urllib2.HTTPError, e:
        if e.code == 404:
            print >> sys.stderr, "Object not found in the cache"
            sys.exit(1)
        raise

#
# Restart the server gracefully
#

def do_restart():
    response_py = request (['restart'])
    print


def main():
    parse_args()

    if command == 'info':
        print_info()
    elif command == 'purge':
        do_purge()
    elif command == 'restart':
        do_restart()
    elif command == 'sources':
        print_sources()
    elif command == 'kill-source':
        if ksource:
            kill_source()
        else:
            print "ERROR: Missing source ID parameter"

    elif command == 'close-connection':
        if cconnection:
            close_connection()
        else:
            print "ERROR: Missing connection ID parameter"

    elif command == 'trace':
        if trace:
            set_trace()
        else:
            print_trace()
    elif command == 'logrotate':
        if log:
            do_logrotate()
        else:
            print "ERROR: Missing LOG parameter"
    else:
        print "ERROR: Command not found: %s\n" %(command)
        print USAGE
        raise SystemExit


if __name__ == "__main__":
    main()
