#!/usr/bin/python

policy = None

QUEUE_FILE = 'torrent.lst'
POLICY_FILE = 'policy.conf'
HISTORY_FILE = 'history.lst'
IP2CC_FILE = 'ip2cc.db'
UNKNOWN_ID_FILE = 'unknownid.log'

TORRENT_FORMAT = 'torrent_format'
#DEFAULT_TORRENT_FORMAT = 'r3,id:l20,title:r6,progress:r7,dlsize:l7,totalsize:l8,eta:r4,dlspeed:r4,ulspeed:r6,seeds:r6,peers:l10,btstatus:r6,ratio:l,activity'
DEFAULT_TORRENT_FORMAT = 'r,id:l20,title:r,progress:r,dlsize:l,totalsize:r,eta:r,dlspeed:r,ulspeed:r,seeds:r,peers:l,btstatus:r,ratio:l,activity'
SPEW_FORMAT = 'spew_format'
#DEFAULT_SPEW_FORMAT = 'l15,ip:r5,rport:l2,cc:l1,direction:r4,uprate:r4,downrate:r7,dtotal:r7,utotal:r7,completed:l12,client:l12,netname'
DEFAULT_SPEW_FORMAT = 'l,ip:r,rport:l,cc:l,direction:r,uprate:r,downrate:r,dtotal:r,utotal:r,completed:l12,client:l12,netname'
PEER_FORMAT = 'peer_format'
DEFAULT_PEER_FORMAT = 'l,ip:r,rport:l,cc:l,client:l,netname'

ALLOC_TYPE = 'alloc_type'
ALLOC_RATE = 'alloc_rate'
MAX_FILES_OPEN = 'max_files_open'

DEFAULT_COMMAND = 'default_command'
DEBUG_LEVEL = 'debug_level'
REASSIGN_ID = 'reassign_id'
USE_LOCAL_POLICY = 'use_local_policy'

USE_SINGLE_PORT = 'use_single_port'
REQUEST_BACKLOG = 'request_backlog'
SNUB_TIME = 'snub_time'
SCHEDULING_INTERVAL = 'scheduling_interval'
SCRAPE_INTERVAL = 'scrape_interval'
REREQUEST_INTERVAL = 'rerequest_interval'
IPV6_BINDS_V4 = 'ipv6_binds_v4'
UPNP_NAT_ACCESS = 'upnp_nat_access'
IDLE_TIMEOUT = 'idle_timeout'
IDLE_TIMEOUT_CHECK_INTERVAL = 'idle_timeout_check_interval'

PEER_ID = 'peer_id'
ENABLE_DHT = 'enable_dht'
DEFAULT_ENCODING = 'default_encoding'
FILESYSTEM_ENCODING = 'filesystem_encoding'
TRACKER_PROXY = 'tracker_proxy'

DEFAULT_SOCKET_TIMEOUT = 'default_socket_timeout'
MAX_JOB_RUN = 'max_run_job'
DEFAULT_PRIORITY = 'default_priority'
REPORT_IP = 'report_ip'
BIND_IP = 'bind_ip'
MIN_FREE_SPACE = 'min_free_space'
MIN_PORT = 'min_port'
MAX_PORT = 'max_port'
RANDOM_PORT = 'random_port'
MIN_PEER = 'min_peer'
MAX_PEER = 'max_peer'
MAX_INITIATE = 'max_initiate'
MAX_UPLOAD_RATE = 'max_upload_rate'
MAX_DOWNLOAD_RATE = 'max_download_rate'
MAX_SEED_RATE = 'max_seed_rate'
DEST_PATH = 'dest_path'
TORRENT_PATH = 'torrent_path'
WRITE_BUFFER_SIZE = 'write_buffer_size'
PRIORITIES = 'priorities'

MIN_SHARE_RATIO = 'min_share_ratio'
MAX_SHARE_RATIO = 'max_share_ratio'
MIN_SEED_TIME = 'min_seed_time'
MAX_SEED_TIME = 'max_seed_time'
MIN_SEEDER = 'min_seeder'
MAX_SEEDER = 'max_seeder'
MIN_PEER_RATIO = 'min_peer_ratio'
MAX_PEER_RATIO = 'max_peer_ratio'

LOG_UNKNOWN_ID = 'log_unknown_id'
IGNORE_WAITING_MEDIA = 'ignore_waiting_media'
ALLOW_ACL = 'allow_acl'
DENY_ACL = 'deny_acl'
ORDER_ACL = 'order_acl'
MAX_LAST_BANNED = 'max_last_banned'
MAX_LAST_MESSAGE = 'max_last_message'
VERIFY_LINK = 'verify_link'

DAEMON_STDOUT = 'daemon_stdout'
DAEMON_STDERR = 'daemon_stderr'

XMLRPC_ID = 'xmlrpc_id'
XMLRPC_IP = 'xmlrpc_ip'
XMLRPC_PORT = 'xmlrpc_port'

WEBSERVICE_ = 'webservice_'
WEBSERVICE_ID = 'webservice_id'
WEBSERVICE_IP = 'webservice_ip'
WEBSERVICE_PORT = 'webservice_port'
WEBSERVICE_CLOSE = 'webservice_close'
WEBSERVICE_QUERY = 'webservice_query'
WEBSERVICE_ADD = 'webservice_add'
WEBSERVICE_DELETE = 'webservice_delete'
WEBSERVICE_PAUSE = 'webservice_pause'
WEBSERVICE_RESUME = 'webservice_resume'
WEBSERVICE_QUEUE = 'webservice_queue'
WEBSERVICE_VERSION = 'webservice_version'
WEBSERVICE_GSET = 'webservice_gset'

import sys,os
import math,re
import types
from ConfigParser import NoOptionError
from ip2cc import inet_aton

priority_cre = re.compile(r'(?P<range>\[((?P<begin>\d+)?(:(?P<end>\d+)?)?)\]:)?(?P<priority>-?[012])')

def get_policy(root_path=None,**kw):
    global policy
    if not policy:
        import sys
        if not root_path:
            root_path = os.path.dirname(os.path.abspath(sys.argv[0]))
        policy = apply(Policy,(root_path,),kw)
    return policy

def ip_to_int(ip):
    n = 0
    for i in ip.split('.'):
        n *= 256
        n += int(i) & 0xff
    return n

def int_to_ip(n):
    return '%d.%d.%d.%d' % (n>>24&0xff,n>>16&0xff,n>>8&0xff,n&0xff)

def extract_priority(priorities,maxlen):
    p = ['0']*maxlen
    index = 0
    for item in priorities.split(','):
        item = item.strip()
        if not item:
            continue
        m = priority_cre.match(item)
        if not m:
            continue
        priority = '0'
        if m.group('priority'):
            priority = m.group('priority')
        if m.group('range'):
            begin = end = 0
            if m.group('begin'):
                begin = int(m.group('begin'))
                end = begin+1
            if m.group('end'):
                end = int(m.group('end'))
            if begin == 0 and end == 0:
                begin = 0
                end = maxlen
        else:
            begin = index
            end = index+1
        for i in range(begin,end):
            p[i] = priority
        index += 1
    return ','.join(p)

class AccessControl:
    def __init__(self,acl):
        self.update(acl)

    def update(self,acl):
        self.acl = acl

    def exists(self,ip):
        return self.acl == ip

class AllAC(AccessControl):
    def exists(self,ip):
        return 1

class NoneAC(AccessControl):
    def exists(self,ip):
        return 1

class IPRangeAC(AccessControl):
    def update(self,acl):
        try:
            network,prefix = acl.split('/')
            self.netmask = ~long(math.pow(2,32-int(prefix))-1) & 4294967295L
            self.network = ip_to_int(network)
        except ValueError:
            try:
                self.network = ip_to_int(acl)
            except ValueError:
                self.network = 0
            self.netmask = 4294967295L

    def exists(self,ip):
        try:
            ipn = ip_to_int(ip)
        except ValueError:
            return 0
        return (ipn & self.netmask) == self.network

class NetNameAC(AccessControl):
    def __init__(self,acl,ipdb=None):
        AccessControl.__init__(self,acl)
        self.ipdb = ipdb

    def update(self,acl):
        self.acl = acl.upper()

    def exists(self,ip):
        try:
            cc,netname = self.ipdb[ip].split(':')
        except (KeyError,AssertionError,TypeError,IndexError):
            return 0
        else:
            return self.acl == netname

class CountryAC(AccessControl):
    def __init__(self,acl,ipdb=None):
        AccessControl.__init__(self,acl)
        self.ipdb = ipdb

    def update(self,acl):
        self.acl = acl.upper()

    def exists(self,ip):
        try:
            cc,netname = self.ipdb[ip].split(':')
        except (KeyError,AssertionError):
            return 0
        else:
            return self.acl == cc

class ACL(AccessControl):

    ip_cre = re.compile(r'\d+\.\d+\.\d+\.\d+(/\d+)?',re.I)

    def __init__(self,acl,ipdb=None):
        self.ipdb = ipdb
        AccessControl.__init__(self,acl)

    def update(self,acl):
        self.acls = []
        for item in acl.split(','):
            item = item.strip()
            if not item:
                continue
            if len(item) == 2:
                o = CountryAC(item,self.ipdb)
            elif item.upper() == 'ALL':
                o = AllAC(item)
            elif item.upper() == 'NONE':
                o = NoneAC(item)
            elif self.ip_cre.match(item):
                o = IPRangeAC(item)
            else:
                o = NetNameAC(item,self.ipdb)
            self.acls.append(o)

    def exists(self,ip):
        for o in self.acls:
            if o.exists(ip):
                return 1
        return 0

class EntryPolicy:
    def __init__(self,conf=None,section=None):
        self.params = {MIN_SHARE_RATIO: 1.0,
                       MAX_SHARE_RATIO: 2.0,
                       MIN_SEED_TIME: 0,
                       MAX_SEED_TIME: 86400,
                       MIN_SEEDER: 0,
                       MAX_SEEDER: 4,
                       MIN_PEER_RATIO: 0,
                       MAX_PEER_RATIO: 4,

                       ENABLE_DHT: 1,
                       FILESYSTEM_ENCODING: '',
                       TRACKER_PROXY: '',

                       PRIORITIES: '',
                       USE_LOCAL_POLICY: 0,
                      }
        pol = get_policy()
        for key in self.params.keys():
            value = pol(key)
            if value != None:
                self.params[key] = value
        if conf and section:
            self.load(conf,section)

    def load(self,conf,section):
        for key in self.params.keys():
            try:
                self.update(key,conf.get(section,key))
            except NoOptionError: 
                pass

    def save(self,conf,section):
        for key in self.params.keys():
            conf.set(section,key,str(self.params[key]))

    def update(self,key,value):
        if not self.params.has_key(key):
            self.params[key] = value
            return
        if type(self.params[key]) == type(0):
            self.params[key] = int(value)
        elif type(self.params[key]) == type(0.0):
            self.params[key] = float(value)
        else:
            self.params[key] = value

    def __call__(self,args):
        return self.get(args)

    def keys(self):
        return self.params.keys()

    def get(self,key):
        try:
            ret = self.params[key]
        except KeyError:
            ret = get_policy().get(key)
        return ret

class Policy:
    def __init__(self,root_path,updated=None):
        self.root_path = root_path
        self.updated = updated
        self.share_path = os.path.join(sys.prefix,'share','BTQueue')
        self.program_path = os.path.dirname(os.path.abspath(sys.argv[0]))
        self.file = self.get_path(POLICY_FILE)
        if sys.platform == 'win32' or not os.environ.get('HOME'):
            default_path = self.program_path
        else:
            default_path = os.environ.get('HOME')
        self.params = {MAX_JOB_RUN: 5,
                       USE_SINGLE_PORT: 1,
                       DEFAULT_COMMAND: 'list running seeding',
                       DEFAULT_SOCKET_TIMEOUT: 60,
                       DEFAULT_PRIORITY: 5,

                       ALLOC_TYPE: 'normal',
                       ALLOC_RATE: 2.0,
                       MAX_FILES_OPEN: 50,

                       TORRENT_FORMAT: DEFAULT_TORRENT_FORMAT,
                       SPEW_FORMAT: DEFAULT_SPEW_FORMAT,
                       PEER_FORMAT: DEFAULT_PEER_FORMAT,

                       SCHEDULING_INTERVAL: 30,
                       SCRAPE_INTERVAL: 20*60,
                       REREQUEST_INTERVAL: 20*60,
                       REQUEST_BACKLOG: 2,

                       IDLE_TIMEOUT: 300,
                       IDLE_TIMEOUT_CHECK_INTERVAL: 60,
                       SNUB_TIME: 30,

                       DEBUG_LEVEL: 0,
                       REASSIGN_ID: 1,
                       LOG_UNKNOWN_ID: 0,

                       REPORT_IP: '',
                       BIND_IP: '',
                       IPV6_BINDS_V4: 0,
                       UPNP_NAT_ACCESS: 1,

                       PEER_ID: 'btq',
                       ENABLE_DHT: 1,
                       DEFAULT_ENCODING: 'tis-620',
                       FILESYSTEM_ENCODING: '',
                       TRACKER_PROXY: '',

                       MIN_PORT: 6881,
                       MAX_PORT: 6999,
                       RANDOM_PORT: 1,

                       MIN_FREE_SPACE: 100*1024*1024,
                       MIN_PEER: 20,
                       MAX_PEER: 90,
                       MAX_INITIATE: 40,
                       MAX_UPLOAD_RATE: 10,
                       MAX_DOWNLOAD_RATE: 20,
                       MAX_SEED_RATE: 10,
                       MIN_SHARE_RATIO: 1.0,
                       MAX_SHARE_RATIO: 2.0,
                       MIN_SEED_TIME: 0,
                       MAX_SEED_TIME: 86400,
                       MIN_SEEDER: 0,
                       MAX_SEEDER: 4,
                       MIN_PEER_RATIO: 0,
                       MAX_PEER_RATIO: 4,

                       DEST_PATH: os.path.join(default_path,'incoming'),
                       TORRENT_PATH: os.path.join(default_path,'torrent'),
                       IGNORE_WAITING_MEDIA: 0,

                       ALLOW_ACL: 'ALL',
                       DENY_ACL: 'NONE',
                       ORDER_ACL: 'allow,deny',
                       MAX_LAST_BANNED: 20,
                       MAX_LAST_MESSAGE: 40,

                       VERIFY_LINK: 0,
                       DAEMON_STDOUT: '',
                       DAEMON_STDERR: '',
                       WRITE_BUFFER_SIZE: 4,

                       XMLRPC_IP: '127.0.0.1',
                       XMLRPC_PORT: 19413,
                       XMLRPC_ID: 'xmlrpcbt',

                       WEBSERVICE_IP: '127.0.0.1',
                       WEBSERVICE_PORT: 19412,
                       WEBSERVICE_ID: 'wsbt',
                       WEBSERVICE_CLOSE: 0,
                       WEBSERVICE_QUERY: 1,
                       WEBSERVICE_ADD: 1,
                       WEBSERVICE_DELETE: 1,
                       WEBSERVICE_PAUSE: 1,
                       WEBSERVICE_RESUME: 1,
                       WEBSERVICE_QUEUE: 1,
                       WEBSERVICE_VERSION: 1,
                       WEBSERVICE_GSET: 0,
                      }
        self.load()

    def set_default(self):
        global policy
        policy = self

    def set_handler(self,updated):
        self.updated = updated

    def load(self):
        try:
            fd = open(self.file,'r')
            while 1:
                line = fd.readline()
                if not line:
                    break
                if line.strip()[0] == '#':
                    continue
                try:
                    key,value = line.split('=')
                except ValueError:
                    continue
                key = key.strip()
                value = value.strip()
                self.update(key,value)
            fd.close()
        except IOError:
            return

    def save(self):
        try:
            fd = open(self.file,'w')
            keys = self.params.keys()
            keys.sort()
            for key in keys:
                fd.write('%s=%s\n' % (key,str(self.params[key])))
            fd.close()
        except Exception,why:
            return

    def update(self,key,value):
        if not self.params.has_key(key):
            return
        if type(self.params[key]) == type(0):
            if type(value) in types.StringTypes:
                lv = value.lower()
                if lv == 'true':
                    value = '1'
                elif lv == 'false':
                    value = '0'
            self.params[key] = int(value)
        elif type(self.params[key]) == type(0.0):
            self.params[key] = float(value)
        else:
            self.params[key] = value
        if self.updated:
            self.updated(key)

    def __call__(self,args):
        return self.get(args)

    def keys(self):
        return self.params.keys()

    def get(self,key):
        try:
            ret = self.params[key]
        except KeyError:
            ret = None
        return ret

    def get_path(self,basename,writable=0):
        if not os.path.exists(self.root_path):
            os.mkdir(self.root_path)
        for prefix in [self.root_path,self.share_path,self.program_path]:
            path = os.path.join(prefix,basename)
            if os.path.exists(path):
                if writable and \
                   (not os.access(path,os.W_OK) or prefix != self.root_path):
                    continue
                return path
        return os.path.join(self.root_path,basename)

def test_acl():
    import ip2cc
    ipdb = ip2cc.CountryByIP('ip2cc.db')
    acl = IPRangeAC('158.108.0.0/16')
    print acl.exists('158.108.34.1')
    print acl.exists('158.109.34.1')
    acl = NetNameAC('teleglobe-as',ipdb)
    print acl.exists('203.113.22.1')
    print acl.exists('158.108.34.1')
    acl = CountryAC('th',ipdb)
    print acl.exists('203.113.22.1')
    print acl.exists('158.108.34.1')
    acl = ACL('teleglobe-as',ipdb)
    print acl.exists('203.113.22.1')
    print acl.exists('158.108.34.1')

if __name__ == '__main__':
    test_acl()
