#!/usr/bin/python
"""
Runs a squid instance to cache TKLBAM's duplicity archives
Arguments:

    <http_port>         [hostname:]port (e.g., 3128, 127.0.0.1:33128)
    <cachesize>         Size of squid cache (can be absolute or relative size)
        
                        1000MB - 1000 MBs
                        2GB - 2000 MBs

                        50% - 50% of free space in spool location

Environment variables:

    TKLBAM_SQUID_CACHE_DIR      location of cache dir (default: %s)
    TKLBAM_SQUID_USER           user under which we execute squid (default: %s)
    TKLBAM_SQUID_BIN            path to tklbam squid binary (default: %s)

"""

import os
import sys
import string
import statvfs

import re

from executil import system
from temp import TempFile

import pwd

TKLBAM_SQUID_BIN = "/usr/lib/tklbam/deps"
TKLBAM_SQUID_CACHE_DIR = "/var/spool/tklbam-squid"
TKLBAM_SQUID_USER = "proxy"
TKLBAM_SQUID_BIN = "/usr/lib/tklbam/deps/usr/sbin/squid"

TKLBAM_CONF_TPL = \
"""
# autogenerated file, do not edit

cache_dir ufs $CACHE_DIR $CACHE_SIZE 16 256
maximum_object_size $MAXIMUM_OBJECT_SIZE MB

# static

http_port $HTTP_PORT
icp_port 0

acl all src all
acl localhost src 127.0.0.1/32

http_access allow localhost
http_access deny all

cache_mem 0 MB
maximum_object_size_in_memory 0 KB

refresh_pattern -i duplicity.*\.gpg$$ 86400 90% 604800 ignore-auth

cache_log /dev/null
access_log none
cache_store_log none
pid_filename none
netdb_filename none
"""

class Error(Exception):
    pass

def get_freespace(path):
    stats = os.statvfs(path)
    freespace = stats[statvfs.F_BFREE] * stats[statvfs.F_BSIZE]
    return freespace

def parse_cache_size(sizestr, cache_dir):
    """Parse cache size string and return the size of the cache in megabytes.

    Valid format examples:
    
    100     100 megabytes

    50%     50% of free space on cache_dir filesystem

    100M    100 megabytes
    100MB   100 megabytes
    100mb   100 megabytes

    10G     10 gigabytes
    10GB    10 gigabytes
    10gb    10 gigabytes
    """

    try:
        return int(sizestr)
    except ValueError:
        pass
    
    m = re.match(r'^(.*)MB?$', sizestr, re.IGNORECASE)
    if m:
        size = int(m.group(1))
        return size + 16 # why 16 extra MB? make room for the cache structure overhead

    m = re.match(r'^(.*)GB?$', sizestr, re.IGNORECASE)
    if m:
        size = int(m.group(1))
        return size * 1024
    m = re.match(r'^(.*)%$', sizestr, re.IGNORECASE)
    if m:
        percent = int(m.group(1))
        if percent > 100 or percent < 0:
            raise Error("bad percent %d%% - should be between 0 and 100" % percent)

        return int((get_freespace(cache_dir) * percent / 100.0) / (1024 * 1024))

    raise Error("illegal cache_size value '%s'" % sizestr)

def user_exists(user):
    try:
        pwd.getpwnam(user)
        return True
    except KeyError:
        return False

def is_other_user(user):
    if pwd.getpwnam(user).pw_uid == os.getuid():
        return False
    else:
        return True

def usage(e=None):
    if e:   
        print >> sys.stderr, "error: " + str(e)
    print >> sys.stderr, "Syntax: %s <http_port> <cache_size>" % sys.argv[0]
    print >> sys.stderr, __doc__.strip() % (TKLBAM_SQUID_CACHE_DIR, TKLBAM_SQUID_USER, TKLBAM_SQUID_BIN)
    sys.exit(1)

def fatal(e):
    print >> sys.stderr, "error: " + str(e)
    sys.exit(1)

def main():
    args = sys.argv[1:]
    if len(args) != 2:
        usage("incorrect number of arguments")

    user = os.environ.get('TKLBAM_SQUID_USER', TKLBAM_SQUID_USER)
    if not user_exists(user):
        fatal("no such user '%s'" % user)
    
    cache_dir = os.environ.get('TKLBAM_SQUID_CACHE_DIR', TKLBAM_SQUID_CACHE_DIR)
    if os.path.exists(cache_dir):
        if not os.path.isdir(cache_dir):
            usage("'%s' - not a directory" % cache_dir)
    else:
        os.makedirs(cache_dir)

    os.chmod(cache_dir, 0750)
    if is_other_user(user):
        pw = pwd.getpwnam(user)
        os.chown(cache_dir, pw.pw_uid, pw.pw_gid)

    http_port = args[0]
    try:
        cache_size = parse_cache_size(args[1], cache_dir)
    except ValueError:
        usage("bad cache_size '%s'" % args[1])

    conf_tpl = string.Template(TKLBAM_CONF_TPL)

    if cache_size > 2000 and (not sys.maxsize > 2 ** 32):
        print >> sys.stderr, "Detected 32-bit system, limiting maximum_object_size to 2000 MB"
        maximum_object_size = 2000
    else:
        maximum_object_size = cache_size

    conf = conf_tpl.substitute(HTTP_PORT=http_port, 
                               CACHE_DIR=cache_dir, 
                               CACHE_SIZE=cache_size, 
                               MAXIMUM_OBJECT_SIZE=maximum_object_size) 

    conf_tmp = TempFile("tklbam-squid-conf-")
    file(conf_tmp.path, "w").write(conf)
    os.chmod(conf_tmp.path, 0644)

    squid_bin = os.environ.get('TKLBAM_SQUID_BIN', TKLBAM_SQUID_BIN)
    system("%s -f %s -z" % (squid_bin, conf_tmp.path))
    system("%s -f %s -N -D -d 1" % (squid_bin, conf_tmp.path))

if __name__ == "__main__":
    main()
