<commit_before>#!/usr/bin/python3
# Original Script by Michael Shepanski (2013-08-01, python 2)
# Updated to work with Python 3
# Updated to use DigitalOcean API v2

import argparse
import ipaddress
import json
import sys
import urllib.request
from datetime import datetime

CHECKIP_URL = "http://ipinfo.io/ip"
APIURL = "https://api.digitalocean.com/v2"

def create_headers(token, extra_headers=None):
    rv = {'Authorization': "Bearer %s" % (token)}
    if extra_headers:
        rv.update(extra_headers)
    return rv

def get_url(url, headers=None):
    if headers:
        req = urllib.request.Request(url, headers=headers)
    else:
        req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as file:
        data = file.read()
        return data.decode('utf8')

def put_url(url, data, headers):
    req = urllib.request.Request(url, data=data, headers=headers)
    req.get_method = lambda: 'PUT'
    with urllib.request.urlopen(req) as file:
        data = file.read()
        return data.decode('utf8')

def get_external_ip(expected_rtype):
    """ Return the current external IP. """
    external_ip = get_url(CHECKIP_URL).rstrip()
    ip = ipaddress.ip_address(external_ip)
    if (ip.version == 4 and expected_rtype != 'A') or (ip.version == 6 and expected_rtype != 'AAAA'):
        raise Exception('Expected Rtype {} but got {}'.format(expected_rtype, external_ip))
    return external_ip

def get_domain(name, token):
    output('Fetching Domain ID for: {}', name)
    url = "%s/domains" % (APIURL)

    while True:
        result = json.loads(get_url(url, headers=create_headers(token)))

        for domain in result['domains']:
            if domain['name'] == name:
                return domain

        if 'pages' in result['links'] and 'next' in result['links']['pages']:
            url = result['links']['pages']['next']
            # Replace http to https.
            # DigitalOcean forces https request, but links are returned as http
            url = url.replace("http://", "https://")
        else:
            break

    raise Exception("Could not find domain: %s" % name)

def get_record(domain, name, rtype, token):
    output("Fetching Record ID for: {}", name)
    url = "%s/domains/%s/records" % (APIURL, domain['name'])

    while True:
        result = json.loads(get_url(url, headers=create_headers(token)))

        for record in result['domain_records']:
            if record['type'] == rtype and record['name'] == name:
                return record

        if 'pages' in result['links'] and 'next' in result['links']['pages']:
            url = result['links']['pages']['next']
            # Replace http to https.
            # DigitalOcean forces https request, but links are returned as http
            url = url.replace("http://", "https://")
        else:
            break

    raise Exception("Could not find record: %s" % name)

def set_record_ip(domain, record, ipaddr, token):
    print("Updating record {}.{} to {}".format(record['name'], domain['name'], ipaddr))

    url = "%s/domains/%s/records/%s" % (APIURL, domain['name'], record['id'])
    data = json.dumps({'data': ipaddr}).encode('utf-8')
    headers = create_headers(token, {'Content-Type': 'application/json'})

    result = json.loads(put_url(url, data, headers))
    if result['domain_record']['data'] == ipaddr:
        print("Success")

def output(line, *args):
    check = getattr(output, 'suppress', False)
    if check:
        return
    print(line.format(*args))

def process_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("token")
    parser.add_argument("domain")
    parser.add_argument("record")
    parser.add_argument("rtype", choices=['A', 'AAAA'])
    parser.add_argument("-q", "--quiet", action="store_true")
    return parser.parse_args()

def run():
    try:
        args = process_args()
        if args.quiet:
            output.suppress = True

        output("Update {}.{}: {}", args.record, args.domain, datetime.now())
        ipaddr = get_external_ip(args.rtype)
        domain = get_domain(args.domain, args.token)
        record = get_record(domain, args.record, args.rtype, args.token)
        if record['data'] == ipaddr:
            output("Records {}.{} already set to {}.", record['name'], domain['name'], ipaddr)
        else:
            set_record_ip(domain, record, ipaddr, args.token)
    except (Exception) as err:
        print("Error: ", err, file=sys.stderr)

if __name__ == '__main__':
    sys.exit(run())
<commit_msg>setup error codes, flag to return error code on IP change<commit_after>#!/usr/bin/python3
# Original Script by Michael Shepanski (2013-08-01, python 2)
# Updated to work with Python 3
# Updated to use DigitalOcean API v2

import argparse
import ipaddress
import json
import sys
import urllib.request
from datetime import datetime

CHECKIP_URL = "http://ipinfo.io/ip"
APIURL = "https://api.digitalocean.com/v2"

def create_headers(token, extra_headers=None):
    rv = {'Authorization': "Bearer %s" % (token)}
    if extra_headers:
        rv.update(extra_headers)
    return rv

def get_url(url, headers=None):
    if headers:
        req = urllib.request.Request(url, headers=headers)
    else:
        req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as file:
        data = file.read()
        return data.decode('utf8')

def put_url(url, data, headers):
    req = urllib.request.Request(url, data=data, headers=headers)
    req.get_method = lambda: 'PUT'
    with urllib.request.urlopen(req) as file:
        data = file.read()
        return data.decode('utf8')

def get_external_ip(expected_rtype):
    """ Return the current external IP. """
    external_ip = get_url(CHECKIP_URL).rstrip()
    ip = ipaddress.ip_address(external_ip)
    if (ip.version == 4 and expected_rtype != 'A') or (ip.version == 6 and expected_rtype != 'AAAA'):
        raise Exception('Expected Rtype {} but got {}'.format(expected_rtype, external_ip))
    return external_ip

def get_domain(name, token):
    output('Fetching Domain ID for: {}', name)
    url = "%s/domains" % (APIURL)

    while True:
        result = json.loads(get_url(url, headers=create_headers(token)))

        for domain in result['domains']:
            if domain['name'] == name:
                return domain

        if 'pages' in result['links'] and 'next' in result['links']['pages']:
            url = result['links']['pages']['next']
            # Replace http to https.
            # DigitalOcean forces https request, but links are returned as http
            url = url.replace("http://", "https://")
        else:
            break

    raise Exception("Could not find domain: %s" % name)

def get_record(domain, name, rtype, token):
    output("Fetching Record ID for: {}", name)
    url = "%s/domains/%s/records" % (APIURL, domain['name'])

    while True:
        result = json.loads(get_url(url, headers=create_headers(token)))

        for record in result['domain_records']:
            if record['type'] == rtype and record['name'] == name:
                return record

        if 'pages' in result['links'] and 'next' in result['links']['pages']:
            url = result['links']['pages']['next']
            # Replace http to https.
            # DigitalOcean forces https request, but links are returned as http
            url = url.replace("http://", "https://")
        else:
            break

    raise Exception("Could not find record: %s" % name)

def set_record_ip(domain, record, ipaddr, token):
    print("Updating record {}.{} to {}".format(record['name'], domain['name'], ipaddr))

    url = "%s/domains/%s/records/%s" % (APIURL, domain['name'], record['id'])
    data = json.dumps({'data': ipaddr}).encode('utf-8')
    headers = create_headers(token, {'Content-Type': 'application/json'})

    result = json.loads(put_url(url, data, headers))
    if result['domain_record']['data'] == ipaddr:
        print("Success")

def output(line, *args):
    check = getattr(output, 'suppress', False)
    if check:
        return
    print(line.format(*args))

def process_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("token")
    parser.add_argument("domain")
    parser.add_argument("record")
    parser.add_argument("rtype", choices=['A', 'AAAA'])
    parser.add_argument("-q", "--quiet", action="store_true", help='Only display output on IP change')
    parser.add_argument("-ecoc", "--error-code-on-change", action="store_true", help='return Error Code 1 on IP change')
    return parser.parse_args()

def run():
    try:
        args = process_args()
        if args.quiet:
            output.suppress = True

        output("Update {}.{}: {}", args.record, args.domain, datetime.now())
        ipaddr = get_external_ip(args.rtype)
        domain = get_domain(args.domain, args.token)
        record = get_record(domain, args.record, args.rtype, args.token)
        if record['data'] == ipaddr:
            output("Records {}.{} already set to {}.", record['name'], domain['name'], ipaddr)
            return 0

        set_record_ip(domain, record, ipaddr, args.token)
        ec = 1 if args.error_code_on_change else 0
        return ec

    except (Exception) as err:
        print("Error: ", err, file=sys.stderr)
        return -1

if __name__ == '__main__':
    sys.exit(run())
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright (c) 2014, Vispy Development Team.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.

"""
Implements the global singleton app object.

"""

from __future__ import division

import sys

from. import backends
from .backends import BACKENDS, BACKENDMAP, ATTEMPTED_BACKENDS
from .. import config
from .base import BaseApplicationBackend as ApplicationBackend  # noqa
from ..util import logger

class Application(object):

    """ Representation of the vispy application. This wraps a native
    GUI application instance. Vispy has a default instance of this class
    at vispy.app.default_app.

    There are multiple stages for an Application object:
        * Backend-less - the state when it is just initialized
        * Backend selected - use() has been successfully called. Note that
          the Canvas calls use() without arguments reight before creating
          its backend widget.
        * Native application is created - the Canvas probes the
          Application,native property to ensure that there is a native
          application right before a native widget is created.

    """

    def __init__(self):
        self._backend_module = None
        self._backend = None

    def __repr__(self):
        name = self.backend_name
        if not name:
            return '<Vispy app with no backend>'
        else:
            return '<Vispy app, wrapping the %s GUI toolkit>' % name

    @property
    def backend_name(self):
        """ The name of the GUI backend that this app wraps.
        """
        if self._backend is not None:
            return self._backend._vispy_get_backend_name()
        else:
            return ''

    @property
    def backend_module(self):
        """ The module object that defines the backend.
        """
        return self._backend_module

    def process_events(self):
        """ Process all pending GUI events. If the mainloop is not
        running, this should be done regularly to keep the visualization
        interactive and to keep the event system going.
        """
        return self._backend._vispy_process_events()

    def create(self):
        """ Create the native application.
        """
        self.native

    def run(self):
        """ Enter the native GUI event loop.
        """
        return self._backend._vispy_run()

    def quit(self):
        """ Quit the native GUI event loop.
        """
        return self._backend._vispy_quit()

    @property
    def native(self):
        """ The native GUI application instance.
        """
        return self._backend._vispy_get_native_app()

    def use(self, backend_name=None):
        """ Select a backend by name. If the backend name is omitted,
        will chose a suitable backend automatically. It is an error to
        try to select a particular backend if one is already selected.
        Available backends: 'PySide', 'PyQt4', 'Glut', 'Pyglet', 'qt'.
        The latter will use PySide or PyQt4, whichever works.

        If a backend name is provided, and that backend could not be
        loaded, an error is raised.

        If no backend name is provided, this function will first check
        if the GUI toolkit corresponding to each backend is already
        imported, and try that backend first. If this is unsuccessful,
        it will try the 'default_backend' provided in the vispy config.
        If still not succesful, it will try each backend in a
        predetermined order.

        """
        if backend_name is not None:
            if backend_name.lower() not in BACKENDMAP:
                raise ValueError('backend_name must be one of %s or None, not '
                                 '%s' % (BACKENDMAP, backend_name))

        # Should we try and load any backend, or just this specific one?
        try_others = backend_name is None

        # Check if already selected
        if self._backend is not None:
            if backend_name and \
                    backend_name.lower() != self.backend_name.lower():
                raise RuntimeError('Can only select a backend once.')
            return

        # Get backends to try ...
        backends_to_try = []
        if not try_others:
            # Test if given name is ok
            if backend_name.lower() not in BACKENDMAP.keys():
                raise ValueError('Backend name not known: "%s"' % backend_name)
            # Add it
            backends_to_try.append(backend_name.lower())
        else:
            # See if a backend is loaded
            for name, module_name, native_module_name in BACKENDS:
                if native_module_name and native_module_name in sys.modules:
                    backends_to_try.append(name.lower())
            # See if a default is given
            default_backend = config['default_backend'].lower()
            if default_backend.lower() in BACKENDMAP.keys():
                if default_backend not in backends_to_try:
                    backends_to_try.append(default_backend)
            # After this, try each one
            for name, module_name, native_module_name in BACKENDS:
                name = name.lower()
                if name not in backends_to_try:
                    backends_to_try.append(name)

        # Now try each one
        for key in backends_to_try:
            # Get info for this backend
            try:
                name, module_name, native_module_name = BACKENDMAP[key]
            except KeyError:
                logger.warn('This should not happen, unknown backend: "".'
                            % key)
                continue
            # Try to import it ...

            try:
                ATTEMPTED_BACKENDS.append(name)
                mod_name = 'backends.' + module_name
                __import__(mod_name, globals(), level=1)
            except ImportError as err:
                msg = 'Could not import backend "%s":\n%s' % (name, str(err))
                if not try_others:
                    raise RuntimeError(msg)
            except Exception as err:
                msg = ('Error while importing backend "%s":\n%s'
                       % (name, str(err)))
                if not try_others:
                    raise RuntimeError(msg)
                else:
                    logger.info(msg)
            else:
                # Success!
                self._backend_module = getattr(backends, module_name)
                logger.info('Selected backend %s' % module_name)
                break
        else:
            raise RuntimeError('Could not import any of the backends.')

        # Store classes for app backend and canvas backend
        self._backend = self.backend_module.ApplicationBackend()
<commit_msg>ENH: Better debugging<commit_after># -*- coding: utf-8 -*-
# Copyright (c) 2014, Vispy Development Team.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.

"""
Implements the global singleton app object.

"""

from __future__ import division

import sys

from. import backends
from .backends import BACKENDS, BACKENDMAP, ATTEMPTED_BACKENDS
from .. import config
from .base import BaseApplicationBackend as ApplicationBackend  # noqa
from ..util import logger

class Application(object):

    """ Representation of the vispy application. This wraps a native
    GUI application instance. Vispy has a default instance of this class
    at vispy.app.default_app.

    There are multiple stages for an Application object:
        * Backend-less - the state when it is just initialized
        * Backend selected - use() has been successfully called. Note that
          the Canvas calls use() without arguments reight before creating
          its backend widget.
        * Native application is created - the Canvas probes the
          Application,native property to ensure that there is a native
          application right before a native widget is created.

    """

    def __init__(self):
        self._backend_module = None
        self._backend = None

    def __repr__(self):
        name = self.backend_name
        if not name:
            return '<Vispy app with no backend>'
        else:
            return '<Vispy app, wrapping the %s GUI toolkit>' % name

    @property
    def backend_name(self):
        """ The name of the GUI backend that this app wraps.
        """
        if self._backend is not None:
            return self._backend._vispy_get_backend_name()
        else:
            return ''

    @property
    def backend_module(self):
        """ The module object that defines the backend.
        """
        return self._backend_module

    def process_events(self):
        """ Process all pending GUI events. If the mainloop is not
        running, this should be done regularly to keep the visualization
        interactive and to keep the event system going.
        """
        return self._backend._vispy_process_events()

    def create(self):
        """ Create the native application.
        """
        self.native

    def run(self):
        """ Enter the native GUI event loop.
        """
        return self._backend._vispy_run()

    def quit(self):
        """ Quit the native GUI event loop.
        """
        return self._backend._vispy_quit()

    @property
    def native(self):
        """ The native GUI application instance.
        """
        return self._backend._vispy_get_native_app()

    def use(self, backend_name=None):
        """ Select a backend by name. If the backend name is omitted,
        will chose a suitable backend automatically. It is an error to
        try to select a particular backend if one is already selected.
        Available backends: 'PySide', 'PyQt4', 'Glut', 'Pyglet', 'qt'.
        The latter will use PySide or PyQt4, whichever works.

        If a backend name is provided, and that backend could not be
        loaded, an error is raised.

        If no backend name is provided, this function will first check
        if the GUI toolkit corresponding to each backend is already
        imported, and try that backend first. If this is unsuccessful,
        it will try the 'default_backend' provided in the vispy config.
        If still not succesful, it will try each backend in a
        predetermined order.

        """
        if backend_name is not None:
            if backend_name.lower() not in BACKENDMAP:
                raise ValueError('backend_name must be one of %s or None, not '
                                 '%s' % (BACKENDMAP, backend_name))

        # Should we try and load any backend, or just this specific one?
        try_others = backend_name is None

        # Check if already selected
        if self._backend is not None:
            if backend_name and \
                    backend_name.lower() != self.backend_name.lower():
                raise RuntimeError('Can only select a backend once.')
            return

        # Get backends to try ...
        backends_to_try = []
        if not try_others:
            # Test if given name is ok
            if backend_name.lower() not in BACKENDMAP.keys():
                raise ValueError('Backend name not known: "%s"' % backend_name)
            # Add it
            backends_to_try.append(backend_name.lower())
        else:
            # See if a backend is loaded
            for name, module_name, native_module_name in BACKENDS:
                if native_module_name and native_module_name in sys.modules:
                    backends_to_try.append(name.lower())
            # See if a default is given
            default_backend = config['default_backend'].lower()
            if default_backend.lower() in BACKENDMAP.keys():
                if default_backend not in backends_to_try:
                    backends_to_try.append(default_backend)
            # After this, try each one
            for name, module_name, native_module_name in BACKENDS:
                name = name.lower()
                if name not in backends_to_try:
                    backends_to_try.append(name)

        # Now try each one
        for key in backends_to_try:
            # Get info for this backend
            try:
                name, module_name, native_module_name = BACKENDMAP[key]
            except KeyError:
                logger.warn('This should not happen, unknown backend: "".'
                            % key)
                continue
            # Try to import it ...

            try:
                ATTEMPTED_BACKENDS.append(name)
                mod_name = 'backends.' + module_name
                __import__(mod_name, globals(), level=1)
            except ImportError as err:
                msg = 'Could not import backend "%s":\n%s' % (name, str(err))
                logger.info(msg)
                raise
            except Exception as err:
                msg = ('Error while importing backend "%s":\n%s'
                       % (name, str(err)))
                if not try_others:
                    logger.info(msg)
                    raise
                else:
                    logger.info(msg)
            else:
                # Success!
                self._backend_module = getattr(backends, module_name)
                logger.info('Selected backend %s' % module_name)
                break
        else:
            raise RuntimeError('Could not import any of the backends.')

        # Store classes for app backend and canvas backend
        self._backend = self.backend_module.ApplicationBackend()
<|endoftext|>


<commit_before>#!/usr/bin/env python

import time
import sqlite3
import sys
import os

class ProcessTrack(object):

    def process_check(self):
        self.create_db()
        db_list = self.create_db_list()
        tagged = []
        try:
            os.remove('/home/haumea/.data.txt')
        except OSError:
            pass
        while True:
            time.sleep(5)
            ps = [int(i) for i in os.listdir('/proc') if i.isdigit()]
            for i in ps:
                if os.path.isfile('/proc/%s/stat' % i):
                    try:
                        with open('/proc/%s/stat' % i, 'r+') as f:
                            name = f.readline()
                        f.close()
                        name = [j for j in name.split(' ') if '(' in j]
                        if name[0][1:-1] not in tagged:
                            tagged.append(name[0][1:-1])
                            with open('/home/haumea/.data.txt', 'a+') as f:
                                f.write(name[0][1:-1] + ': ' + time.ctime() + '\n')
                            f.close()
                        if name[0][1:-1] not in db_list:
                            self.enter_process(name[0][1:-1])
                            db_list.append(name[0][1:-1])
                    except AttributeError or IOError:
                        pass

    def create_db(self):
        con = sqlite3.connect('/home/haumea/.pidtrk.db')
        with con:
            cur = con.cursor()
            cur.execute("CREATE TABLE IF NOT EXISTS Process(pidname TEXT, time TEXT)")

    def create_db_list(self):
        con = sqlite3.connect('/home/haumea/.pidtrk.db')
        with con:
            cur = con.cursor()
            cur.execute("SELECT * FROM Process")
            prclst = [i[0] for i in cur.fetchall()]
        return prclst

    def enter_process(self, process):
        con = sqlite3.connect('/home/haumea/.pidtrk.db')
        with con:
            cur = con.cursor()
            cur.execute("INSERT INTO Process VALUES(?,?)", (process, time.ctime()))

if __name__ == '__main__':
    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError, e:
        print >>sys.stderr, 'fork #1 failed: %d (%s)' % (e.errno, e.strerror)
        sys.exit(1)

    os.chdir('/')
    os.setsid()
    os.umask(0)

    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError, e:
        print >>sys.stderr, 'fork #2 failed: %d (%s)' % (e.errno, e.strerror)
        sys.exit(1)

    pt = ProcessTrack()
    pt.process_check()
<commit_msg>cut out extra error message<commit_after>#!/usr/bin/env python

import time
import sqlite3
import sys
import os

class ProcessTrack(object):

    def process_check(self):
        self.create_db()
        db_list = self.create_db_list()
        tagged = []
        try:
            os.remove('/home/haumea/.data.txt')
        except OSError:
            pass
        while True:
            time.sleep(5)
            ps = [int(i) for i in os.listdir('/proc') if i.isdigit()]
            for i in ps:
                if os.path.isfile('/proc/%s/stat' % i):
                    try:
                        with open('/proc/%s/stat' % i, 'r+') as f:
                            name = f.readline()
                        f.close()
                        name = [j for j in name.split(' ') if '(' in j]
                        if name[0][1:-1] not in tagged:
                            tagged.append(name[0][1:-1])
                            with open('/home/haumea/.data.txt', 'a+') as f:
                                f.write(name[0][1:-1] + ': ' + time.ctime() + '\n')
                            f.close()
                        if name[0][1:-1] not in db_list:
                            self.enter_process(name[0][1:-1])
                            db_list.append(name[0][1:-1])
                    except AttributeError or IOError:
                        pass

    def create_db(self):
        con = sqlite3.connect('/home/haumea/.pidtrk.db')
        with con:
            cur = con.cursor()
            cur.execute("CREATE TABLE IF NOT EXISTS Process(pidname TEXT, time TEXT)")

    def create_db_list(self):
        con = sqlite3.connect('/home/haumea/.pidtrk.db')
        with con:
            cur = con.cursor()
            cur.execute("SELECT * FROM Process")
            prclst = [i[0] for i in cur.fetchall()]
        return prclst

    def enter_process(self, process):
        con = sqlite3.connect('/home/haumea/.pidtrk.db')
        with con:
            cur = con.cursor()
            cur.execute("INSERT INTO Process VALUES(?,?)", (process, time.ctime()))

if __name__ == '__main__':
    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError, e:
        sys.exit(1)

    os.chdir('/')
    os.setsid()
    os.umask(0)

    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError, e:
        sys.exit(1)

    pt = ProcessTrack()
    pt.process_check()
<|endoftext|>


<commit_before>#!/usr/bin/env python

import feedparser
import time
import hashlib
import os
from os.path import expanduser
import logging
from ConfigParser import ConfigParser
import urllib
import re
import urlparse

configpath=expanduser("~/.upodder.ini")

yes = [1,"1","on","yes","Yes","YES","y","Y","true","True","TRUE","t","T"]
no = [0,"0","off","no","No","NO","n","N","false","False","FALSE","f","F"]
configcomment = ['#',';','$',':','"',"'"]
badfnchars = re.compile('[^\w]+',re.LOCALE)

defaults = {
		'basedir': '~/.upodder',
		'podcastsdir': '~/PODCASTS',
		'filename': '%(today)s/%(id)s.mp3',
		'subscriptions': '%(basedir)s/subscriptions',
		'seendb': '%(basedir)s/seen.db',
		'tmpdir': '%(basedir)s/temp',
		'logfile': '%(basedir)s/upodder.log',
		'logtofile': "no",
		'logtoconsole': "yes",
		'oldness': 1209600,
		'loglevel': logging.DEBUG,
		'reverseorder': "yes",
}

def purgeSeenDB(oldness):
	"""Rids of records older than oldness argument in seendb"""
	if not os.path.exists(seendb):
		l.info("Creating empty seen database file %s"%seendb)
		open(seendb,'a').close()
		return
	newseendb = seendb + '.tmp'
	ns = open(newseendb,'a')
	now = time.time()
	for s in open(seendb,'r'):
		if len(s) >= 52 and int(now - int(s[41:52])) < oldness: ns.write(s)
		else: l.debug("Expiring (%s)"%s.strip())
	ns.close()
	os.remove(seendb)
	os.rename(newseendb,seendb)

def entryId(entry):
	"""Returns ID of entry"""
	return hashlib.sha1(entry.get('id')).hexdigest()

def entryTime(entry):
	"""Returns tuple in time.localtime() format"""
	return entry.get('updated_parsed',entry.get('published_parsed',entry.get('created_parsed',time.localtime())))

def markEntrySeen(entry):
	"""Marks entry as seen in seen DB"""
	eid = entryId(entry)
	l.info("Marking [%s] (%s) as seen"%(entry.get('title'),eid))
	open(seendb,'a').write("%s %011i\n"%(eid,int(time.time())))

def isEntrySeen(entry):
	"""Returns True if entry already seen. False otherwise"""
	if not os.path.exists(seendb):
		l.info("Creating empty seen database file %s"%seendb)
		open(seendb,'a').close()
		return False
	eid = entryId(entry)
	for s in filter(lambda x: len(x) >= 40,open(seendb,'r')):
		if eid == s[0:40]:
			l.debug("Already seen: [%s] (%s)"%(entry.get('title'),eid))
			return True
	return False

def isEntryOld(entry, oldness):
	"""Returns True if entry is too old (defined by oldness variable), False otherwise"""
	result = False
	entrytime = entryTime(entry)
	if (time.time() - time.mktime(entrytime)) > oldness:
		l.info("Too old for us: [%s] <%s>)"%(entry.get('title'),time.asctime(entrytime)))
		result = True
	return result

def retrieveURL(url,file):
	"""Downloads URL to file, returns file name of download (from URL or Content-Disposition)"""
	if not os.path.exists(os.path.dirname(file)): os.makedirs(os.path.dirname(file),0700)
	(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
	filename = path.split("/")[-1]
	try:
		l.debug("Downloading {%s} to {%s}"%(url,os.path.basename(file)))
		urllib.urlretrieve(url,file)
	except Exception, e:
		l.error("Download exception occured: %s"%e)
		return False

	if not filename:
		filename = "Untitled.mp3"
	return filename

def downloadEnclosure(enclosure, entry, feed):
	"""Performs downloading of specified file. Returns True on success and False in other case"""

	# Donloading enclosure to specified file
	downloadto = tmpdir + os.sep + entryId(entry)

	try:
		filename = retrieveURL(enclosure.get('href'),downloadto)
	except KeyboardInterrupt:
		l.info("Download aborted by Ctrl+c")
		try:
			user_wish = raw_input("Do you like to mark item as read? (y/N): ")
			if user_wish in yes:
				return True
		except KeyboardInterrupt:
			print "No"
		return False

	if not filename: return False

	# Move downloaded file to its final destination
	moveto = podcastsdir + os.sep + generateFileName(filename, entry, feed)
	l.debug("Moving {%s} to {%s}"%(downloadto,moveto))
	if not os.path.exists(os.path.dirname(moveto)): os.makedirs(os.path.dirname(moveto),0750)
	os.rename(downloadto,moveto)
	return True

def generateFileName(filename, entry, feed):
	"""Generates file name for this enclosure based on config settins"""
	(year,month,day,hour,minute,second,weekday,yearday,leap) = time.localtime()
	subst = {
		'today': '%i-%02i-%02i'%(year,month,day),
		'entry_date': '%i-%02i-%02i'%entryTime(entry)[0:3],
		'id': entryId(entry),
		'entry_title': re.sub(badfnchars,'_',entry.get('title')),
		'feed_href': re.sub(badfnchars,'_',feed.href.split('://')[-1]),
		'feed_title': re.sub(badfnchars,'_',feed.feed.get('title',feed.href)),
		'original_filename': re.sub(badfnchars,'_',filename),
	}
	return c.get('DEFAULT','filename',vars=subst)

def manageEntry(entry, feed):
	"""We'll deal with this entry"""

	# Let's check if we worked on this entry earlier...
	if isEntrySeen(entry): return

	# Let's check the entry's date
	if isEntryOld(entry, int(c.get('DEFAULT','oldness'))):
		markEntrySeen(entry)
		return

	# This post is neither seen, nor too old for us.
	l.info("Recent podcast: [%s]"%(entry.get('title')))
	# Search for mpeg enclosures
	for enclosure in filter(lambda x: x.get('type') == 'audio/mpeg',entry.get('enclosures',[])):
		# Work only with first found audio/mpeg enclosure (Bad Thing? maybe :( )
		if downloadEnclosure(enclosure,entry,feed): markEntrySeen(entry)
		break

def manageFeed(url):
	"""Let's deal with this podcast feed"""
	feed = feedparser.parse(url)
	if feed.bozo:
		l.error("Erroneous feed URL: %s (%s)"%(url,feed.bozo_exception))
		return
	l.info("Checking feed: {%s}"%feed.feed.title)
	if c.get('DEFAULT','reverseorder') in yes:
		feed.entries.reverse()
	for entry in feed.entries: manageEntry(entry, feed)

# Initializing config file
c = ConfigParser(defaults)
if not os.path.exists(configpath):
	c.write(open(configpath,'a'))
c.read(configpath)

# Initializing logging
l = logging.Logger('upodder',int(c.get('DEFAULT','loglevel')))

if c.get('DEFAULT','logtoconsole') in yes:
	stderrHandler = logging.StreamHandler()
	stderrHandler.setFormatter(logging.Formatter('%(message)s'))
	l.addHandler(stderrHandler)

if c.get('DEFAULT','logtofile') in yes:
	fileHandler = logging.FileHandler(expanduser(c.get('DEFAULT','logfile')),'a')
	fileHandler.setFormatter(logging.Formatter('%(asctime)s %(name)s (%(levelname)s): %(message)s'))
	l.addHandler(fileHandler)

# Initializing necessary files and directories
basedir =	expanduser(c.get('DEFAULT','basedir'))
podcastsdir =	expanduser(c.get('DEFAULT','podcastsdir'))
tmpdir =	expanduser(c.get('DEFAULT','tmpdir'))
subscriptions =	expanduser(c.get('DEFAULT','subscriptions'))
seendb =	expanduser(c.get('DEFAULT','seendb'))

if not os.path.exists(basedir):
	l.info("Creating base dir %s"%basedir)
	os.makedirs(basedir,0700)

if not os.path.exists(subscriptions):
	l.info("Creating empty subscriptions file %s"%subscriptions)
	open(subscriptions,'a').write("# Add your RSS/ATOM subscriptions here.\n\n")

#Processing feed URLs
for url in map(lambda x: x.strip(), open(subscriptions)):
	if not url: continue
	if url[0] in configcomment: continue
	manageFeed(url)

l.info("Purging seendb")
purgeSeenDB(int(c.get('DEFAULT','oldness') * 2))

<commit_msg>Fixes issue 001.<commit_after>#!/usr/bin/env python

import feedparser
import time
import hashlib
import os
from os.path import expanduser
import logging
from ConfigParser import ConfigParser
import urllib
import re
import urlparse

configpath=expanduser("~/.upodder.ini")

yes = [1,"1","on","yes","Yes","YES","y","Y","true","True","TRUE","t","T"]
no = [0,"0","off","no","No","NO","n","N","false","False","FALSE","f","F"]
configcomment = ['#',';','$',':','"',"'"]
badfnchars = re.compile('[^\w]+',re.LOCALE)

defaults = {
		'basedir': '~/.upodder',
		'podcastsdir': '~/PODCASTS',
		'filename': '%(today)s/%(id)s.mp3',
		'subscriptions': '%(basedir)s/subscriptions',
		'seendb': '%(basedir)s/seen.db',
		'tmpdir': '%(basedir)s/temp',
		'logfile': '%(basedir)s/upodder.log',
		'logtofile': "no",
		'logtoconsole': "yes",
		'oldness': 1209600,
		'loglevel': logging.DEBUG,
		'reverseorder': "yes",
}

def purgeSeenDB(oldness):
	"""Rids of records older than oldness argument in seendb"""
	if not os.path.exists(seendb):
		l.info("Creating empty seen database file %s"%seendb)
		open(seendb,'a').close()
		return
	newseendb = seendb + '.tmp'
	ns = open(newseendb,'a')
	now = time.time()
	for s in open(seendb,'r'):
		if len(s) >= 52 and int(now - int(s[41:52])) < oldness: ns.write(s)
		else: l.debug("Expiring (%s)"%s.strip())
	ns.close()
	os.remove(seendb)
	os.rename(newseendb,seendb)

def entryId(entry):
	"""Returns ID of entry"""
	return hashlib.sha1(entry.get('id')).hexdigest()

def entryTime(entry):
	"""Returns tuple in time.localtime() format"""
	return entry.get('updated_parsed',entry.get('published_parsed',entry.get('created_parsed',time.localtime())))

def markEntrySeen(entry):
	"""Marks entry as seen in seen DB"""
	eid = entryId(entry)
	l.info("Marking [%s] (%s) as seen"%(entry.get('title'),eid))
	open(seendb,'a').write("%s %011i\n"%(eid,int(time.time())))

def isEntrySeen(entry):
	"""Returns True if entry already seen. False otherwise"""
	if not os.path.exists(seendb):
		l.info("Creating empty seen database file %s"%seendb)
		open(seendb,'a').close()
		return False
	eid = entryId(entry)
	for s in filter(lambda x: len(x) >= 40,open(seendb,'r')):
		if eid == s[0:40]:
			l.debug("Already seen: [%s] (%s)"%(entry.get('title'),eid))
			return True
	return False

def isEntryOld(entry, oldness):
	"""Returns True if entry is too old (defined by oldness variable), False otherwise"""
	result = False
	entrytime = entryTime(entry)
	if (time.time() - time.mktime(entrytime)) > oldness:
		l.info("Too old for us: [%s] <%s>)"%(entry.get('title'),time.asctime(entrytime)))
		result = True
	return result

def retrieveURL(url,file):
	"""Downloads URL to file, returns file name of download (from URL or Content-Disposition)"""
	if not os.path.exists(os.path.dirname(file)): os.makedirs(os.path.dirname(file),0700)
	(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
	filename = path.split("/")[-1]
	try:
		l.debug("Downloading {%s} to {%s}"%(url,os.path.basename(file)))
		urllib.urlretrieve(url,file)
	except Exception, e:
		l.error("Download exception occured: %s"%e)
		return False

	if not filename:
		filename = "Untitled.mp3"
	return filename

def downloadEnclosure(enclosure, entry, feed):
	"""Performs downloading of specified file. Returns True on success and False in other case"""

	# Donloading enclosure to specified file
	downloadto = tmpdir + os.sep + entryId(entry)

	try:
		filename = retrieveURL(enclosure.get('href'),downloadto)
	except KeyboardInterrupt:
		l.info("Download aborted by Ctrl+c")
		try:
			user_wish = raw_input("Do you like to mark item as read? (y/N): ")
			if user_wish in yes:
				return True
		except KeyboardInterrupt:
			print "No"
		return False

	if not filename: return False

	# Move downloaded file to its final destination
	moveto = podcastsdir + os.sep + generateFileName(filename, entry, feed)
	l.debug("Moving {%s} to {%s}"%(downloadto,moveto))
	if not os.path.exists(os.path.dirname(moveto)): os.makedirs(os.path.dirname(moveto),0750)
	os.rename(downloadto,moveto)
	return True

def generateFileName(filename, entry, feed):
	"""Generates file name for this enclosure based on config settins"""
	(year,month,day,hour,minute,second,weekday,yearday,leap) = time.localtime()
	subst = {
		'today': '%i-%02i-%02i'%(year,month,day),
		'entry_date': '%i-%02i-%02i'%entryTime(entry)[0:3],
		'id': entryId(entry),
		'entry_title': re.sub(badfnchars,'_',entry.get('title')),
		'feed_href': re.sub(badfnchars,'_',feed.href.split('://')[-1]),
		'feed_title': re.sub(badfnchars,'_',feed.feed.get('title',feed.href)),
		'original_filename': re.sub(badfnchars,'_',filename),
	}
	return c.get('DEFAULT','filename',vars=subst)

def manageEntry(entry, feed):
	"""We'll deal with this entry"""

	# Let's check if we worked on this entry earlier...
	if isEntrySeen(entry): return

	# Let's check the entry's date
	if isEntryOld(entry, int(c.get('DEFAULT','oldness'))):
		markEntrySeen(entry)
		return

	# This post is neither seen, nor too old for us.
	l.info("Recent podcast: [%s]"%(entry.get('title')))
	# Search for mpeg enclosures
	for enclosure in filter(lambda x: x.get('type') == 'audio/mpeg',entry.get('enclosures',[])):
		# Work only with first found audio/mpeg enclosure (Bad Thing? maybe :( )
		if downloadEnclosure(enclosure,entry,feed): markEntrySeen(entry)
		break

def manageFeed(url):
	"""Let's deal with this podcast feed"""
	feed = feedparser.parse(url)
	if feed.bozo and type(feed.bozo_exception) != type(feedparser.CharacterEncodingOverride()):
		l.error("Erroneous feed URL: %s (%s)"%(url,feed.bozo_exception))
		return
	l.info("Checking feed: {%s}"%feed.feed.title)
	if c.get('DEFAULT','reverseorder') in yes:
		feed.entries.reverse()
	for entry in feed.entries: manageEntry(entry, feed)

# Initializing config file
c = ConfigParser(defaults)
if not os.path.exists(configpath):
	c.write(open(configpath,'a'))
c.read(configpath)

# Initializing logging
l = logging.Logger('upodder',int(c.get('DEFAULT','loglevel')))

if c.get('DEFAULT','logtoconsole') in yes:
	stderrHandler = logging.StreamHandler()
	stderrHandler.setFormatter(logging.Formatter('%(message)s'))
	l.addHandler(stderrHandler)

if c.get('DEFAULT','logtofile') in yes:
	fileHandler = logging.FileHandler(expanduser(c.get('DEFAULT','logfile')),'a')
	fileHandler.setFormatter(logging.Formatter('%(asctime)s %(name)s (%(levelname)s): %(message)s'))
	l.addHandler(fileHandler)

# Initializing necessary files and directories
basedir =	expanduser(c.get('DEFAULT','basedir'))
podcastsdir =	expanduser(c.get('DEFAULT','podcastsdir'))
tmpdir =	expanduser(c.get('DEFAULT','tmpdir'))
subscriptions =	expanduser(c.get('DEFAULT','subscriptions'))
seendb =	expanduser(c.get('DEFAULT','seendb'))

if not os.path.exists(basedir):
	l.info("Creating base dir %s"%basedir)
	os.makedirs(basedir,0700)

if not os.path.exists(subscriptions):
	l.info("Creating empty subscriptions file %s"%subscriptions)
	open(subscriptions,'a').write("# Add your RSS/ATOM subscriptions here.\n\n")

#Processing feed URLs
for url in map(lambda x: x.strip(), open(subscriptions)):
	if not url: continue
	if url[0] in configcomment: continue
	manageFeed(url)

l.info("Purging seendb")
purgeSeenDB(int(c.get('DEFAULT','oldness') * 2))

<|endoftext|>


<commit_before>from __future__ import absolute_import
from builtins import range
from builtins import object
import munge
import types
import os
import os.path

import vodka.app
import vodka.exceptions
import vodka.log
import vodka.util

from . import validators

raw = {}

instance = {
}

class Attribute(object):

    """
    A configuration attribute
    """

    def __init__(self, expected_type, default=None, help_text=None, handler=None, choices=None, prepare=None):
        """
        Args:
            expected_type (class or function): type expected for this attribute, if specified
                as a function the result of the function will determine whether or not the value
                passed type validation.

        Kwargs:
            default: if specified this value will be used as a default value, if not specified then
                configuration of this attribute is treated as mandatory
            help_text (str): explains the attribute
            choices (list): list of valid value choices for this attribute, if set any value not
                matching any of the choices will raise a configuration error
            handler (function): when the value for this attribute is a collection of configuration
                attributes (e.g. nested config) use this function to return the aproporiate config
                handler class to use to validate them
            prepare (function): allows you to prepare value for this attribute
        """
        self.expected_type = expected_type
        self.default = default
        self.help_text = help_text
        self.handler = handler
        self.choices = choices
        self.prepare = prepare or []

class Handler(object):

    """
    Can be attached to any vodka application class or vodka
    plugin and allows to setup default values and config
    sanity checking
    """

    @classmethod
    def check(cls, cfg, key_name, path):
        """
        Checks that the config values specified in key name is
        sane according to config attributes defined as properties
        on this class
        """

        attr = getattr(cls, key_name, None)

        if path != "":
            attr_full_name = "%s.%s" % (path, key_name)
        else:
            attr_full_name = key_name

        if not attr:
            # attribute specified by key_name is unknown, warn
            raise vodka.exceptions.ConfigErrorUnknown(attr_full_name)

        # prepare data
        for prepare in attr.prepare:
            cfg[key_name] = prepare(cfg[key_name])

        if hasattr(cls, "prepare_%s" % key_name):
            prepare = getattr(cls, "prepare_%s" % key_name)
            cfg[key_name] = prepare(cfg[key_name], config=cfg)

        value = cfg.get(key_name)

        if isinstance(attr.expected_type, types.FunctionType):
            # expected type holds a validator function
            p, reason = attr.expected_type(value)
            if not p:
                # validator did not pass
                raise vodka.exceptions.ConfigErrorValue(
                    attr_full_name,
                    attr,
                    value,
                    reason=reason
                )

        elif attr.expected_type != type(value):
            # attribute type mismatch
            raise vodka.exceptions.ConfigErrorType(
                attr_full_name,
                attr
            )

        if attr.choices and value not in attr.choices:
            # attribute value not valid according to
            # available choices
            raise vodka.exceptions.ConfigErrorValue(
                attr_full_name,
                attr,
                value
            )

        if hasattr(cls, "validate_%s" % key_name):
            # custom validator for this attribute was found
            validator = getattr(cls, "validate_%s" % key_name)
            valid, reason = validator(value)
            if not valid:
                # custom validator failed
                raise vodka.exceptions.ConfigErrorValue(
                    attr_full_name,
                    attr,
                    value,
                    reason=reason
                )

        num_crit = 0
        num_warn = 0
        if value in [dict, list] and attr.handler:
            if type(value) == dict:
                keys = list(value.keys())
            elif type(value) == list:
                keys = list(range(0, len(value)))
            else:
                return
            for k in keys:
                if value[k] not in [dict, list]:
                    continue
                handler = attr.handler(k, value[k])
                if issubclass(handler, Handler):
                    h = handler
                else:
                    h = getattr(handler, "Configuration", None)

                #h = getattr(attr.handler(k, value[k]), "Configuration", None)
                if h:
                    if type(k) == int and type(value[k]) == dict and value[k].get("name"):
                        _path = "%s.%s" % (
                            attr_full_name, value[k].get("name"))
                    else:
                        _path = "%s.%s" % (attr_full_name, k)
                    _num_crit, _num_warn = h.validate(value[k], path=_path)
                    num_crit += _num_crit
                    num_warn += _num_warn

        return (num_crit, num_warn)

    @classmethod
    def validate(cls, cfg, path=""):
        """
        Validates a second of a config dict. Will automatically
        validate child sections as well if their attribute pointers
        are instantiated with a handler property
        """

        # number of critical errors found
        num_crit = 0

        # number of non-critical errors found
        num_warn = 0

        if type(cfg) in [dict, Config]:
            keys = list(cfg.keys())
        elif type(cfg) == list:
            keys = list(range(0, len(cfg)))
        else:
            raise ValueError("Cannot validate non-iterable config value")

        # check for missing keys in the config
        for name in dir(cls):
            try:
                attr = getattr(cls, name)
                if isinstance(attr, Attribute):
                    if attr.default is None and name not in cfg:
                        # no default value defined, which means its required
                        # to be set in the config file
                        if path:
                            attr_full_name = "%s.%s" % (path, name)
                        else:
                            attr_full_name = name
                        raise vodka.exceptions.ConfigErrorMissing(
                            attr_full_name, attr)
            except vodka.exceptions.ConfigErrorMissing as inst:
                if inst.level == "warn":
                    vodka.log.warn(inst.explanation)
                    num_warn += 1
                elif inst.level == "critical":
                    vodka.log.error(inst.explanation)
                    num_crit += 1

        # validate existing keys in the config
        for key in keys:
            try:
                _num_crit, _num_warn = cls.check(cfg, key, path)
                num_crit += _num_crit
                num_warn += _num_warn
            except (
                vodka.exceptions.ConfigErrorUnknown,
                vodka.exceptions.ConfigErrorValue,
                vodka.exceptions.ConfigErrorType
            ) as inst:
                if inst.level == "warn":
                    vodka.log.warn(inst.explanation)
                    num_warn += 1
                elif inst.level == "critical":
                    vodka.log.error(inst.explanation)
                    num_crit += 1

        return num_crit, num_warn

    @classmethod
    def default(cls, key_name, inst=None):
        attr = getattr(cls, key_name, None)
        if not attr:
            raise KeyError(
                "No config attribute defined with the name '%s'" % key_name)

        if attr.default and callable(attr.default):
            return attr.default(key_name, inst)
        return attr.default

    @classmethod
    def attributes(cls):
        """
        yields tuples for all attributes defined on this handler

        tuple yielded:
            name (str), attribute (Attribute)
        """

        for k in dir(cls):
            v = getattr(cls, k)
            if isinstance(v, Attribute):
                yield k,v

class ComponentHandler(Handler):

    """
    This is the base config handler that will be attached to any
    vodka application or plugin
    """

    # config attribute: enabled
    enabled = Attribute(
        bool, default=True, help_text="specifies whether or not this component should be initialized and started")

class InstanceHandler(Handler):

    """
    This is the config handler for the vodka main config
    """

    apps = Attribute(
        dict,
        help_text="Holds the registered applications",
        default={},
        handler=lambda k, v: vodka.app.get_application(k)
    )
    plugins = Attribute(
        list,
        help_text="Holds the registered plugins",
        default=[],
        handler=lambda k, v: vodka.plugin.get_plugin_class(v.get("type"))
    )
    data = Attribute(
        list,
        help_text="Data type configuration",
        default=[]
    )
    logging = Attribute(
        dict, help_text="Python logger configuration", default={"version": 1})

    @classmethod
    def configure_plugins(cls, configurator, cfg, path):
        configurator.echo("")
        configurator.echo("Configure plugins")
        configurator.echo("")
        plugin_type = configurator.prompt("Add plugin", default="skip")
        if "plugins" not in cfg:
            cfg["plugins"] = []

        while plugin_type != "skip":
            plugin_name = configurator.prompt("Name", default=plugin_type)
            try:
                plugin_class = configurator.plugin_manager.get_plugin_class(plugin_type)
                plugin_cfg = {"type":plugin_type, "name":plugin_name}
                configurator.configure(plugin_cfg, plugin_class.Configuration, path="%s.%s"%(path, plugin_name))
                cfg["plugins"].append(plugin_cfg)
            except Exception as inst:
                configurator.echo(inst)
            plugin_type = configurator.prompt("Add plugin", default="skip")

    @classmethod
    def configure_apps(cls, configurator, cfg, path):
        configurator.echo("")
        configurator.echo("Configure applications")
        configurator.echo("")

        if "apps" not in cfg:
            cfg["apps"] = {}

        name = configurator.prompt("Add application (name)", default="skip")
        while name != "skip":
            app_cfg = {}
            configurator.configure(app_cfg, vodka.app.Application.Configuration, path="%s.%s" % (path,name))
            vodka.app.load(name, app_cfg)
            app = vodka.app.get_application(name)
            configurator.configure(app_cfg, app.Configuration, path="%s.%s" % (path,name))
            cfg["apps"][name] = app_cfg
            name = configurator.prompt("Add application (name)", default="skip")

class Config(munge.Config):
    defaults = {
        'config': {},
        'config_dir': '~/.vodka',
        'codec': 'yaml'
    }

    def read(self, config_dir=None, clear=False, config_file=None):
        """
        The munge Config's read function only allows to read from
        a config directory, but we also want to be able to read
        straight from a config file as well
        """

        if config_file:
            data_file = os.path.basename(config_file)
            data_path = os.path.dirname(config_file)

            if clear:
                self.clear()

            config = munge.load_datafile(data_file, data_path, default=None)

            if not config:
                raise IOError("Config file not found: %s" % config_file)

            munge.util.recursive_update(self.data, config)
            self._meta_config_dir = data_path
            return
        else:
            return super(Config, self).read(config_dir=config_dir, clear=clear)
<commit_msg>fix config validation error introduced by py3 support<commit_after>from __future__ import absolute_import
from builtins import range
from builtins import object
import munge
import types
import os
import os.path

import vodka.app
import vodka.exceptions
import vodka.log
import vodka.util

from . import validators

raw = {}

instance = {
}

def is_config_container(v):
    """
    checks whether v is of type list,dict or Config
    """

    cls = type(v)

    return (
        issubclass(cls, list) or
        issubclass(cls, dict) or
        issubclass(cls, Config)
    )

class Attribute(object):

    """
    A configuration attribute
    """

    def __init__(self, expected_type, default=None, help_text=None, handler=None, choices=None, prepare=None):
        """
        Args:
            expected_type (class or function): type expected for this attribute, if specified
                as a function the result of the function will determine whether or not the value
                passed type validation.

        Kwargs:
            default: if specified this value will be used as a default value, if not specified then
                configuration of this attribute is treated as mandatory
            help_text (str): explains the attribute
            choices (list): list of valid value choices for this attribute, if set any value not
                matching any of the choices will raise a configuration error
            handler (function): when the value for this attribute is a collection of configuration
                attributes (e.g. nested config) use this function to return the aproporiate config
                handler class to use to validate them
            prepare (function): allows you to prepare value for this attribute
        """
        self.expected_type = expected_type
        self.default = default
        self.help_text = help_text
        self.handler = handler
        self.choices = choices
        self.prepare = prepare or []

class Handler(object):

    """
    Can be attached to any vodka application class or vodka
    plugin and allows to setup default values and config
    sanity checking
    """

    @classmethod
    def check(cls, cfg, key_name, path):
        """
        Checks that the config values specified in key name is
        sane according to config attributes defined as properties
        on this class
        """

        attr = getattr(cls, key_name, None)

        if path != "":
            attr_full_name = "%s.%s" % (path, key_name)
        else:
            attr_full_name = key_name

        if not attr:
            # attribute specified by key_name is unknown, warn
            raise vodka.exceptions.ConfigErrorUnknown(attr_full_name)

        # prepare data
        for prepare in attr.prepare:
            cfg[key_name] = prepare(cfg[key_name])

        if hasattr(cls, "prepare_%s" % key_name):
            prepare = getattr(cls, "prepare_%s" % key_name)
            cfg[key_name] = prepare(cfg[key_name], config=cfg)

        value = cfg.get(key_name)

        if isinstance(attr.expected_type, types.FunctionType):
            # expected type holds a validator function
            p, reason = attr.expected_type(value)
            if not p:
                # validator did not pass
                raise vodka.exceptions.ConfigErrorValue(
                    attr_full_name,
                    attr,
                    value,
                    reason=reason
                )

        elif attr.expected_type != type(value):
            # attribute type mismatch
            raise vodka.exceptions.ConfigErrorType(
                attr_full_name,
                attr
            )

        if attr.choices and value not in attr.choices:
            # attribute value not valid according to
            # available choices
            raise vodka.exceptions.ConfigErrorValue(
                attr_full_name,
                attr,
                value
            )

        if hasattr(cls, "validate_%s" % key_name):
            # custom validator for this attribute was found
            validator = getattr(cls, "validate_%s" % key_name)
            valid, reason = validator(value)
            if not valid:
                # custom validator failed
                raise vodka.exceptions.ConfigErrorValue(
                    attr_full_name,
                    attr,
                    value,
                    reason=reason
                )

        num_crit = 0
        num_warn = 0
        if is_config_container(value) and attr.handler:
            if type(value) == dict or issubclass(type(value), Config):
                keys = list(value.keys())
            elif type(value) == list:
                keys = list(range(0, len(value)))
            else:
                return
            for k in keys:
                if not is_config_container(value[k]):
                    continue
                handler = attr.handler(k, value[k])
                if issubclass(handler, Handler):
                    h = handler
                else:
                    h = getattr(handler, "Configuration", None)

                #h = getattr(attr.handler(k, value[k]), "Configuration", None)
                if h:
                    if type(k) == int and type(value[k]) == dict and value[k].get("name"):
                        _path = "%s.%s" % (
                            attr_full_name, value[k].get("name"))
                    else:
                        _path = "%s.%s" % (attr_full_name, k)
                    _num_crit, _num_warn = h.validate(value[k], path=_path)
                    num_crit += _num_crit
                    num_warn += _num_warn

        return (num_crit, num_warn)

    @classmethod
    def validate(cls, cfg, path=""):
        """
        Validates a second of a config dict. Will automatically
        validate child sections as well if their attribute pointers
        are instantiated with a handler property
        """

        # number of critical errors found
        num_crit = 0

        # number of non-critical errors found
        num_warn = 0

        if type(cfg) in [dict, Config]:
            keys = list(cfg.keys())
        elif type(cfg) == list:
            keys = list(range(0, len(cfg)))
        else:
            raise ValueError("Cannot validate non-iterable config value")

        # check for missing keys in the config
        for name in dir(cls):
            try:
                attr = getattr(cls, name)
                if isinstance(attr, Attribute):
                    if attr.default is None and name not in cfg:
                        # no default value defined, which means its required
                        # to be set in the config file
                        if path:
                            attr_full_name = "%s.%s" % (path, name)
                        else:
                            attr_full_name = name
                        raise vodka.exceptions.ConfigErrorMissing(
                            attr_full_name, attr)
            except vodka.exceptions.ConfigErrorMissing as inst:
                if inst.level == "warn":
                    vodka.log.warn(inst.explanation)
                    num_warn += 1
                elif inst.level == "critical":
                    vodka.log.error(inst.explanation)
                    num_crit += 1

        # validate existing keys in the config
        for key in keys:
            try:
                _num_crit, _num_warn = cls.check(cfg, key, path)
                num_crit += _num_crit
                num_warn += _num_warn
            except (
                vodka.exceptions.ConfigErrorUnknown,
                vodka.exceptions.ConfigErrorValue,
                vodka.exceptions.ConfigErrorType
            ) as inst:
                if inst.level == "warn":
                    vodka.log.warn(inst.explanation)
                    num_warn += 1
                elif inst.level == "critical":
                    vodka.log.error(inst.explanation)
                    num_crit += 1

        return num_crit, num_warn

    @classmethod
    def default(cls, key_name, inst=None):
        attr = getattr(cls, key_name, None)
        if not attr:
            raise KeyError(
                "No config attribute defined with the name '%s'" % key_name)

        if attr.default and callable(attr.default):
            return attr.default(key_name, inst)
        return attr.default

    @classmethod
    def attributes(cls):
        """
        yields tuples for all attributes defined on this handler

        tuple yielded:
            name (str), attribute (Attribute)
        """

        for k in dir(cls):
            v = getattr(cls, k)
            if isinstance(v, Attribute):
                yield k,v

class ComponentHandler(Handler):

    """
    This is the base config handler that will be attached to any
    vodka application or plugin
    """

    # config attribute: enabled
    enabled = Attribute(
        bool, default=True, help_text="specifies whether or not this component should be initialized and started")

class InstanceHandler(Handler):

    """
    This is the config handler for the vodka main config
    """

    apps = Attribute(
        dict,
        help_text="Holds the registered applications",
        default={},
        handler=lambda k, v: vodka.app.get_application(k)
    )
    plugins = Attribute(
        list,
        help_text="Holds the registered plugins",
        default=[],
        handler=lambda k, v: vodka.plugin.get_plugin_class(v.get("type"))
    )
    data = Attribute(
        list,
        help_text="Data type configuration",
        default=[]
    )
    logging = Attribute(
        dict, help_text="Python logger configuration", default={"version": 1})

    @classmethod
    def configure_plugins(cls, configurator, cfg, path):
        configurator.echo("")
        configurator.echo("Configure plugins")
        configurator.echo("")
        plugin_type = configurator.prompt("Add plugin", default="skip")
        if "plugins" not in cfg:
            cfg["plugins"] = []

        while plugin_type != "skip":
            plugin_name = configurator.prompt("Name", default=plugin_type)
            try:
                plugin_class = configurator.plugin_manager.get_plugin_class(plugin_type)
                plugin_cfg = {"type":plugin_type, "name":plugin_name}
                configurator.configure(plugin_cfg, plugin_class.Configuration, path="%s.%s"%(path, plugin_name))
                cfg["plugins"].append(plugin_cfg)
            except Exception as inst:
                configurator.echo(inst)
            plugin_type = configurator.prompt("Add plugin", default="skip")

    @classmethod
    def configure_apps(cls, configurator, cfg, path):
        configurator.echo("")
        configurator.echo("Configure applications")
        configurator.echo("")

        if "apps" not in cfg:
            cfg["apps"] = {}

        name = configurator.prompt("Add application (name)", default="skip")
        while name != "skip":
            app_cfg = {}
            configurator.configure(app_cfg, vodka.app.Application.Configuration, path="%s.%s" % (path,name))
            vodka.app.load(name, app_cfg)
            app = vodka.app.get_application(name)
            configurator.configure(app_cfg, app.Configuration, path="%s.%s" % (path,name))
            cfg["apps"][name] = app_cfg
            name = configurator.prompt("Add application (name)", default="skip")

class Config(munge.Config):
    defaults = {
        'config': {},
        'config_dir': '~/.vodka',
        'codec': 'yaml'
    }

    def read(self, config_dir=None, clear=False, config_file=None):
        """
        The munge Config's read function only allows to read from
        a config directory, but we also want to be able to read
        straight from a config file as well
        """

        if config_file:
            data_file = os.path.basename(config_file)
            data_path = os.path.dirname(config_file)

            if clear:
                self.clear()

            config = munge.load_datafile(data_file, data_path, default=None)

            if not config:
                raise IOError("Config file not found: %s" % config_file)

            munge.util.recursive_update(self.data, config)
            self._meta_config_dir = data_path
            return
        else:
            return super(Config, self).read(config_dir=config_dir, clear=clear)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""
---------------------
volt.test.test_config
---------------------

Tests for the volt.config module.

:copyright: (c) 2012 Wibowo Arindrarto <bow@bow.web.id>
:license: BSD

"""

import os
import unittest

from volt.config import SessionConfig, ConfigNotFoundError
from volt.test import INSTALL_DIR, USER_DIR

class TestSessionConfig(unittest.TestCase):

    def setUp(self):
        self.CONFIG = SessionConfig(default_dir=INSTALL_DIR, start_dir=USER_DIR)

    def tearDown(self):
        # destroy default config so default values are reset
        del self.CONFIG._default
        del self.CONFIG

    def test_load(self):
        # test if title is overwritten
        self.assertEqual(self.CONFIG.SITE.TITLE, 'Title in user')
        # test if default conf is preserved
        self.assertEqual(self.CONFIG.SITE.DESC, 'Desc in default')
        # test if user-defined path resolution works
        self.assertEqual(self.CONFIG.VOLT.CUSTOM_DIR, \
                os.path.join(USER_DIR, 'custom_dir_user'))
        self.assertEqual(self.CONFIG.BLOG.CUSTOM_DIR, \
                os.path.join(USER_DIR, 'custom_dir_user', 'user_join'))
        # test for user-only defined Config
        self.assertEqual(self.CONFIG.ADDON.TITLE, 'Only in user')
        # test for different URL possibilities
        self.assertEqual(self.CONFIG.SITE.URL, 'http://foo.com')
        self.assertEqual(self.CONFIG.SITE.B_URL, 'http://foo.com')
        self.assertEqual(self.CONFIG.SITE.C_URL, '')
        # test for lazy loading flag
        self.assertTrue(self.CONFIG._loaded)

    def test_get_root_dir(self):
        # test if exception is properly raised if dir is not a Volt dir
        self.assertRaises(ConfigNotFoundError, SessionConfig().get_root_dir, INSTALL_DIR)
        # test if root path resolution works properly for all dirs in project dir
        self.assertEqual(self.CONFIG.VOLT.ROOT_DIR, USER_DIR)
        self.assertEqual(self.CONFIG.VOLT.ROOT_DIR, SessionConfig().get_root_dir(\
                os.path.join(USER_DIR, "content")))

    def test_set_plugin_defaults(self):
        default_args = {"BAR" : "baz", "QUX": "qux"}
        # check if PLUGINS is loaded correctly from user conf
        self.assertEqual(self.CONFIG.PLUGINS.FOO, "foo")
        self.assertEqual(self.CONFIG.PLUGINS.BAR, "bar")
        self.CONFIG.set_plugin_defaults(default_args)
        # check plugin values after function is run
        self.assertEqual(self.CONFIG.PLUGINS.FOO, "foo")
        # declared items should not be changed
        self.assertEqual(self.CONFIG.PLUGINS.BAR, "bar")
        # items previously undeclared should be added
        self.assertEqual(self.CONFIG.PLUGINS.QUX, "qux")
<commit_msg>added: new tests for volt.config<commit_after># -*- coding: utf-8 -*-
"""
---------------------
volt.test.test_config
---------------------

Tests for the volt.config module.

:copyright: (c) 2012 Wibowo Arindrarto <bow@bow.web.id>
:license: BSD

"""

import os
import unittest
from inspect import ismodule

from volt.config import SessionConfig, ConfigNotFoundError
from volt.config.base import path_import, get_configs
from volt.test import INSTALL_DIR, USER_DIR

class TestSessionConfig(unittest.TestCase):

    def setUp(self):
        self.CONFIG = SessionConfig(default_dir=INSTALL_DIR, start_dir=USER_DIR)

    def tearDown(self):
        # destroy default config so default values are reset
        del self.CONFIG._default
        del self.CONFIG

    def test_load(self):
        # test if title is overwritten
        self.assertEqual(self.CONFIG.SITE.TITLE, 'Title in user')
        # test if default conf is preserved
        self.assertEqual(self.CONFIG.SITE.DESC, 'Desc in default')
        # test if user-defined path resolution works
        self.assertEqual(self.CONFIG.VOLT.CUSTOM_DIR, \
                os.path.join(USER_DIR, 'custom_dir_user'))
        self.assertEqual(self.CONFIG.BLOG.CUSTOM_DIR, \
                os.path.join(USER_DIR, 'custom_dir_user', 'user_join'))
        # test for user-only defined Config
        self.assertEqual(self.CONFIG.ADDON.TITLE, 'Only in user')
        # test for different URL possibilities
        self.assertEqual(self.CONFIG.SITE.URL, 'http://foo.com')
        self.assertEqual(self.CONFIG.SITE.B_URL, 'http://foo.com')
        self.assertEqual(self.CONFIG.SITE.C_URL, '')
        # test for lazy loading flag
        self.assertTrue(self.CONFIG._loaded)

    def test_get_root_dir(self):
        # test if exception is properly raised if dir is not a Volt dir
        self.assertRaises(ConfigNotFoundError, SessionConfig().get_root_dir, INSTALL_DIR)
        # test if root path resolution works properly for all dirs in project dir
        self.assertEqual(self.CONFIG.VOLT.ROOT_DIR, USER_DIR)
        self.assertEqual(self.CONFIG.VOLT.ROOT_DIR, SessionConfig().get_root_dir(\
                os.path.join(USER_DIR, "content")))

    def test_set_plugin_defaults(self):
        default_args = {"BAR" : "baz", "QUX": "qux"}
        # check if PLUGINS is loaded correctly from user conf
        self.assertEqual(self.CONFIG.PLUGINS.FOO, "foo")
        self.assertEqual(self.CONFIG.PLUGINS.BAR, "bar")
        self.CONFIG.set_plugin_defaults(default_args)
        # check plugin values after function is run
        self.assertEqual(self.CONFIG.PLUGINS.FOO, "foo")
        # declared items should not be changed
        self.assertEqual(self.CONFIG.PLUGINS.BAR, "bar")
        # items previously undeclared should be added
        self.assertEqual(self.CONFIG.PLUGINS.QUX, "qux")

class TestConfigBase(unittest.TestCase):

    def test_path_import(self):
        self.assertTrue(ismodule(path_import('in_install', \
                os.path.join(INSTALL_DIR, 'engines'))))
        self.assertTrue(ismodule(path_import('in_both', \
                [os.path.join(x, 'engines') for x in [INSTALL_DIR, USER_DIR]])))

    def test_get_configs(self):
        mod = path_import('voltconf', [USER_DIR])
        total = 0
        for i in get_configs(mod): total += 1
        self.assertEqual(5, total)
<|endoftext|>


<commit_before>#!/usr/bin/env python
import web
import json
import os
import random
from templatecache import TemplateCache
import urllib
import subprocess
import shlex
import textwrap
import settings
import datetime
import databaseconnect
import logger

" Load Data "
with open("./data/data.json") as f:
    meps = json.load(f)

db = databaseconnect.connect(settings.DATABASE_URL)

tc = TemplateCache()

def weighted_choice(ff=lambda x: x):
    """ Pick a MEP based on the score weight """
    lm = filter(ff,meps)
    ts = sum((i['score'] for i in lm))
    r = random.uniform(0,ts)
    n = 0
    for c in lm:
        n = n + c['score']
        if n>r and c.get('fax_bxl',None):
            return c
    return False

def unquote(a):
    return (a[0],unicode(urllib.unquote_plus(a[1]).decode("utf-8")))

def decode_args(a):
    return dict((unquote(i.split("=")) for i in a.split("&")))

def get_mep_by_id(id):
    for m in meps:
        if m['id']==id:
            return m
    return None

def get_filter(wi):
        if hasattr(wi,'country'):
            country = wi.country
        else:
            country = None
        if hasattr(wi,'group'):
            group = wi.group
        else:
            group = None
        if hasattr(wi, 'id'):
            id = wi.id
        else:
            id = None
        if id:
            ff = lambda x: x.get('id',None) == id
        elif country and group:
            ff = lambda x: x.get('country_short',None) == country and x.get('group_short',None) == group
        elif country:
            ff = lambda x: x.get('country_short',None) == country
        elif group:
            ff = lambda x: x.get('group_short',None) == group
        else:
            ff = lambda x: x
        return ff

def create_error(wi,ms=""):
        if hasattr(wi,'country'):
            country = wi.country
        else:
            country = None
        if hasattr(wi,'group'):
            group = wi.group
        else:
            group = None
        if hasattr(wi, 'id'):
            id = wi.id
        else:
            id = None
        if id:
            return "This MEP is not %s"%(ms if ms else "in our database")
        elif country and group:
            return "No MEP of group %s in country %s %s"%(group,country,ms)
        else:
            return "No MEP %s found :/"%(ms)

class Fax:
    """ Handle the Fax Widget """
    def GET(self):
        """ display the fax widget """
        web.header("Content-Type", "text/html;charset=utf-8")
        template = tc.get("fax.tmpl")
        m = weighted_choice(get_filter(web.input()))
        if not m:
            return create_error(web.input())
        return template.render(m)
    def POST(self):
        "send out the fax"
        args=decode_args(web.data())
        m = get_mep_by_id(args['id'])
        if settings.TEST:
            fax = '100'
        else:
            fax = m[settings.FAX_FIELD].replace(" ","").replace("+","00")
        db.query(u"""INSERT INTO faxes (message, faxnr, create_date, campaign_id)
            VALUES ($m,$f,$d,$s)""",
                vars = {
                "m" : textwrap.fill(args['body'],replace_whitespace=False).replace('<','<').replace('>','>'),
                "f" : fax,
                "d" : datetime.datetime.now(),
                's' : settings.campaign_id})
        template = tc.get("fax-sent.tmpl")
        web.header("Content-Type", "text/html;charset=utf-8")
        return template.render(m)

class Tweet:
    def GET(self):
        """display the tweet widget"""
        ff = get_filter(web.input())
        web.header("Content-Type","text/html;charset=utf-8")
        template = tc.get("tweet.tmpl")
        m = weighted_choice(lambda x: x.get('twitter',None) and ff(x))
        if not m:
            return create_error(web.input(),"using Twitter")
        return template.render(m)

class Subscribe:
    def GET(self):
        """log an action"""
        web.header("Content-Type","application/javascript;charset=utf-8")
        web.header("Access-Control-Allow-Origin", "*")
        wi = web.input()
        if hasattr(wi,'mail'):
            mail = wi.mail;
        else:
            return """{status: 'error',
                     message: 'no mail'}"""
        if hasattr(wi,'country'):
            country = wi.country
        else:
            country = ""
        logger.log(db,mail,country)
        return """{status: 'success',
                   message: 'logged mail %s from country %s'
                   }"""%(action,country)

class mail:
    """ Handle Requests for Mail """
    def GET(self):
        """ Handle GET Requests """
        web.header("Content-Type", "text/html;charset=utf-8")
        template = tc.get("mail.tmpl")
        m = weighted_choice(get_filter(web.input()))
        if not m:
            return create_error(web.input())
        return template.render(m)

urls = ('/' + settings.campaign_path + '/mail/', 'mail',
        '/' + settings.campaign_path + '/fax/', 'Fax',
        '/' + settings.campaign_path + '/subscribe/', Subscribe,
        '/' + settings.campaign_path + '/tweet/','Tweet',)

web.config.debug = settings.DEVELOPMENT
app = web.application(urls,globals())

if __name__ == "__main__":
#    if not settings.TEST:
#        pid = os.fork()
#    else:
#        pid = None
#    if not pid:
#        app.run()
#    else:
#        with open(".pid","wb") as f:
#            f.write(str(pid))
    if not settings.DEVELOPMENT:
        web.wsgi.runwsgi = lambda func, addr=None: web.wsgi.runfcgi(func, addr)
    app.run()

<commit_msg>bugfix<commit_after>#!/usr/bin/env python
import web
import json
import os
import random
from templatecache import TemplateCache
import urllib
import subprocess
import shlex
import textwrap
import settings
import datetime
import databaseconnect
import logger

" Load Data "
with open("./data/data.json") as f:
    meps = json.load(f)

db = databaseconnect.connect(settings.DATABASE_URL)

tc = TemplateCache()

def weighted_choice(ff=lambda x: x):
    """ Pick a MEP based on the score weight """
    lm = filter(ff,meps)
    ts = sum((i['score'] for i in lm))
    r = random.uniform(0,ts)
    n = 0
    for c in lm:
        n = n + c['score']
        if n>r and c.get('fax_bxl',None):
            return c
    return False

def unquote(a):
    return (a[0],unicode(urllib.unquote_plus(a[1]).decode("utf-8")))

def decode_args(a):
    return dict((unquote(i.split("=")) for i in a.split("&")))

def get_mep_by_id(id):
    for m in meps:
        if m['id']==id:
            return m
    return None

def get_filter(wi):
        if hasattr(wi,'country'):
            country = wi.country
        else:
            country = None
        if hasattr(wi,'group'):
            group = wi.group
        else:
            group = None
        if hasattr(wi, 'id'):
            id = wi.id
        else:
            id = None
        if id:
            ff = lambda x: x.get('id',None) == id
        elif country and group:
            ff = lambda x: x.get('country_short',None) == country and x.get('group_short',None) == group
        elif country:
            ff = lambda x: x.get('country_short',None) == country
        elif group:
            ff = lambda x: x.get('group_short',None) == group
        else:
            ff = lambda x: x
        return ff

def create_error(wi,ms=""):
        if hasattr(wi,'country'):
            country = wi.country
        else:
            country = None
        if hasattr(wi,'group'):
            group = wi.group
        else:
            group = None
        if hasattr(wi, 'id'):
            id = wi.id
        else:
            id = None
        if id:
            return "This MEP is not %s"%(ms if ms else "in our database")
        elif country and group:
            return "No MEP of group %s in country %s %s"%(group,country,ms)
        else:
            return "No MEP %s found :/"%(ms)

class Fax:
    """ Handle the Fax Widget """
    def GET(self):
        """ display the fax widget """
        web.header("Content-Type", "text/html;charset=utf-8")
        template = tc.get("fax.tmpl")
        m = weighted_choice(get_filter(web.input()))
        if not m:
            return create_error(web.input())
        return template.render(m)
    def POST(self):
        "send out the fax"
        args=decode_args(web.data())
        m = get_mep_by_id(args['id'])
        if settings.TEST:
            fax = '100'
        else:
            fax = m[settings.FAX_FIELD].replace(" ","").replace("+","00")
        db.query(u"""INSERT INTO faxes (message, faxnr, create_date, campaign_id)
            VALUES ($m,$f,$d,$s)""",
                vars = {
                "m" : textwrap.fill(args['body'],replace_whitespace=False).replace('<','<').replace('>','>'),
                "f" : fax,
                "d" : datetime.datetime.now(),
                's' : settings.campaign_id})
        template = tc.get("fax-sent.tmpl")
        web.header("Content-Type", "text/html;charset=utf-8")
        return template.render(m)

class Tweet:
    def GET(self):
        """display the tweet widget"""
        ff = get_filter(web.input())
        web.header("Content-Type","text/html;charset=utf-8")
        template = tc.get("tweet.tmpl")
        m = weighted_choice(lambda x: x.get('twitter',None) and ff(x))
        if not m:
            return create_error(web.input(),"using Twitter")
        return template.render(m)

class Subscribe:
    def GET(self):
        """subscribe to newsletter"""
        web.header("Content-Type","application/javascript;charset=utf-8")
        web.header("Access-Control-Allow-Origin", "*")
        wi = web.input()
        if hasattr(wi,'mail'):
            mail = wi.mail;
        else:
            return """{status: 'error',
                     message: 'no mail'}"""
        if hasattr(wi,'country'):
            country = wi.country
        else:
            country = ""
        logger.log(db,mail,country)
        return """{status: 'success',
                   message: 'logged mail %s from country %s'
                   }"""%(mail,country)

class mail:
    """ Handle Requests for Mail """
    def GET(self):
        """ Handle GET Requests """
        web.header("Content-Type", "text/html;charset=utf-8")
        template = tc.get("mail.tmpl")
        m = weighted_choice(get_filter(web.input()))
        if not m:
            return create_error(web.input())
        return template.render(m)

urls = ('/' + settings.campaign_path + '/mail/', 'mail',
        '/' + settings.campaign_path + '/fax/', 'Fax',
        '/' + settings.campaign_path + '/subscribe/', Subscribe,
        '/' + settings.campaign_path + '/tweet/','Tweet',)

web.config.debug = settings.DEVELOPMENT
app = web.application(urls,globals())

if __name__ == "__main__":
#    if not settings.TEST:
#        pid = os.fork()
#    else:
#        pid = None
#    if not pid:
#        app.run()
#    else:
#        with open(".pid","wb") as f:
#            f.write(str(pid))
    if not settings.DEVELOPMENT:
        web.wsgi.runwsgi = lambda func, addr=None: web.wsgi.runfcgi(func, addr)
    app.run()

<|endoftext|>


<commit_before>from mpd import MPDClient
from threading import Lock
from book import Book

class LockableMPDClient(MPDClient):
    def __init__(self, use_unicode=False):
        super(LockableMPDClient, self).__init__()
        self.use_unicode = use_unicode
        self._lock = Lock()
    def acquire(self):
        self._lock.acquire()
    def release(self):
        self._lock.release()
    def __enter__(self):
        self.acquire()
    def __exit__(self, type, value, traceback):
        self.release()

class Player(object):

    """The class responsible for playing the audio books"""

    def __init__(self, conn_details):
        """Setup a connection to MPD to be able to play audio.

        Also update the MPD database with any new MP3 files that may have been added
        and clear any existing playlists.
        """
        self.book = Book()

        self.mpd_client = LockableMPDClient()
        with self.mpd_client:
            self.mpd_client.connect(**conn_details)

            self.mpd_client.update()
            self.mpd_client.clear()
            self.mpd_client.setvol(100)

    def toggle_pause(self, channel):
        """Toggle playback status between play and pause"""

        with self.mpd_client:
            state = self.mpd_client.status()['state']
            if state == 'play':
                self.mpd_client.pause()
            elif state == 'pause':
                self.mpd_client.play()

    def rewind(self, channel):
        if self.is_playing():
            with self.mpd_client:
                seek = max(int(self.book.elapsed) - 20, 0)
                self.mpd_client.seek(int(self.book.part) - 1, seek)

    def volume_up(self, channel):

        volume = int(self.get_status()['volume'])
        self.set_volume(min(volume + 10, 100))

    def volume_down(self, channel):
        self.player.volume_down()

        volume = int(self.get_status()['volume'])
        self.set_volume(max(volume - 10, 0))

    def set_volume(self, volume):
        """Set the volume on the MPD client"""

        with self.mpd_client:
            self.mpd_client.setvol(volume)
            print "volume set to %d" % volume

    def stop(self):
        """On stopping, reset the current playback and stop and clear the playlist

        In contract to pausing, stopping is actually meant to completely stop playing
        the current book and start listening to another"""

        self.playing = False
        self.book.reset()

        with self.mpd_client:
            self.mpd_client.stop()
            self.mpd_client.clear()

    def play(self, book_id, progress=None):
        """Play the book as defined in self.book

        1. Get the parts from the current book and add them to the playlsit
        2. Start playing the playlist
        3. Immediately set the position the last know position to resume playback where
           we last left off"""

        with self.mpd_client:
            self.mpd_client.clear()

            parts = self.mpd_client.search('filename', book_id)

            if not parts:
                print "Unused book id: %d" % book_id
                return

            for part in parts:
                self.mpd_client.add(part['file'])

            # stop the currently playing song
            self.mpd_client.stop()

            self.mpd_client.play()

            self.book.book_id = book_id

            if progress:
                # resume at last known position
                self.book.set_progress(progress)
                self.mpd_client.seek(int(self.book.part) - 1, int(self.book.elapsed))

    def is_playing(self):
        return self.get_status()['state'] == 'play'

    def get_status(self):
        with self.mpd_client:
            return self.mpd_client.status()

    def close(self):
        self.stop()
        self.mpd_client.close()
        self.mpd_client.disconnect()
<commit_msg>fix playing known book after unknown<commit_after>from mpd import MPDClient
from threading import Lock
from book import Book

class LockableMPDClient(MPDClient):
    def __init__(self, use_unicode=False):
        super(LockableMPDClient, self).__init__()
        self.use_unicode = use_unicode
        self._lock = Lock()
    def acquire(self):
        self._lock.acquire()
    def release(self):
        self._lock.release()
    def __enter__(self):
        self.acquire()
    def __exit__(self, type, value, traceback):
        self.release()

class Player(object):

    """The class responsible for playing the audio books"""

    def __init__(self, conn_details):
        """Setup a connection to MPD to be able to play audio.

        Also update the MPD database with any new MP3 files that may have been added
        and clear any existing playlists.
        """
        self.book = Book()

        self.mpd_client = LockableMPDClient()
        with self.mpd_client:
            self.mpd_client.connect(**conn_details)

            self.mpd_client.update()
            self.mpd_client.clear()
            self.mpd_client.setvol(100)

    def toggle_pause(self, channel):
        """Toggle playback status between play and pause"""

        with self.mpd_client:
            state = self.mpd_client.status()['state']
            if state == 'play':
                self.mpd_client.pause()
            elif state == 'pause':
                self.mpd_client.play()

    def rewind(self, channel):
        if self.is_playing():
            with self.mpd_client:
                seek = max(int(self.book.elapsed) - 20, 0)
                self.mpd_client.seek(int(self.book.part) - 1, seek)

    def volume_up(self, channel):

        volume = int(self.get_status()['volume'])
        self.set_volume(min(volume + 10, 100))

    def volume_down(self, channel):
        self.player.volume_down()

        volume = int(self.get_status()['volume'])
        self.set_volume(max(volume - 10, 0))

    def set_volume(self, volume):
        """Set the volume on the MPD client"""

        with self.mpd_client:
            self.mpd_client.setvol(volume)
            print "volume set to %d" % volume

    def stop(self):
        """On stopping, reset the current playback and stop and clear the playlist

        In contract to pausing, stopping is actually meant to completely stop playing
        the current book and start listening to another"""

        self.playing = False
        self.book.reset()

        with self.mpd_client:
            self.mpd_client.stop()
            self.mpd_client.clear()

    def play(self, book_id, progress=None):
        """Play the book as defined in self.book

        1. Get the parts from the current book and add them to the playlsit
        2. Start playing the playlist
        3. Immediately set the position the last know position to resume playback where
           we last left off"""

        with self.mpd_client:
            self.mpd_client.clear()

            parts = self.mpd_client.search('filename', book_id)

            if not parts:
                print "Unused book id: %d" % book_id
                self.book.book_id = None
                return

            for part in parts:
                self.mpd_client.add(part['file'])

            # stop the currently playing song
            self.mpd_client.stop()

            self.mpd_client.play()

            self.book.book_id = book_id

            if progress:
                # resume at last known position
                self.book.set_progress(progress)
                self.mpd_client.seek(int(self.book.part) - 1, int(self.book.elapsed))

    def is_playing(self):
        return self.get_status()['state'] == 'play'

    def get_status(self):
        with self.mpd_client:
            return self.mpd_client.status()

    def close(self):
        self.stop()
        self.mpd_client.close()
        self.mpd_client.disconnect()
<|endoftext|>