"""
File
----

Classes for representing and performing operations on files and directories.
"""
import os
import yaml
import copy
import logging
import logging.config
import inspect
import pkg_resources
import shutil

from .plugin import PluginManager

class File(object):
    """
    Represents a file that may or may not exist on the filesystem.

    Usually encapsulated by a Directory or an Environment.
    """
    def __init__(self, path=None, create=False, cleanup=False, parent=None):
        super(File, self).__init__()
        self._parent = parent
        self._fpath = path
        self._create = create
        self._cleanup = cleanup

        if self._fpath:
            self._fpath = os.path.expanduser(self._fpath)

    def __enter__(self):
        self.prepare()
        return self

    def __exit__(self, type, value, traceback):
        self.cleanup()

    def __str__(self):
        return self.path

    def apply_config(self, applicator):
        """
        Replace any config tokens in the file's path with values from the config.
        """
        if type(self._fpath) == str:
            self._fpath = applicator.apply(self._fpath)

    def create(self):
        """
        Create the file if it doesn't already exist.
        """
        open(self.path, 'a').close()

    def remove(self):
        """
        Remove the file if it exists.
        """
        if self.exists:
            os.unlink(self.path)

    def prepare(self):
        """
        Prepare the file for use in an Environment or Directory.

        This will create the file if the create flag is set.
        """
        if self._create:
            self.create()

    def cleanup(self):
        """
        Clean up the file after use in an Environment or Directory.

        This will remove the file if the cleanup flag is set.
        """
        if self._cleanup:
            self.remove()

    @property
    def path(self):
        """
        Get the path to the file relative to its parent.
        """
        if self._parent:
            return os.path.join(self._parent.path, self._fpath)
        else:
            return self._fpath

    @property
    def name(self):
        """
        Get the file name.
        """
        return os.path.basename(self.path)

    @property
    def ext(self):
        """
        Get the file's extension.
        """
        return os.path.splitext(self.path)[1]

    @property
    def content(self):
        """
        Property for the content of the file.
        """
        return self.read()

    @property
    def exists(self):
        """
        Whether or not the file exists.
        """
        return self.path and os.path.exists(self.path)

    def read(self):
        """
        Read and return the contents of the file.
        """
        with open(self.path) as f:
            d = f.read()
        return d

    def write(self, data, mode='w'):
        """
        Write data to the file.

        `data` is the data to write
        `mode` is the mode argument to pass to `open()`
        """
        with open(self.path, mode) as f:
            f.write(data)

class LogFile(File):
    """
    A log file to configure with Python's logging module.
    """
    def __init__(self, path=None, logger=None, loggers=[], formatter={}, format=None, *args, **kwargs):
        super(LogFile, self).__init__(path=path, *args, **kwargs)
        self._create = True
        self._cleanup = True
        self._formatter = formatter
        self._format = format

        if logger:
            self._loggers = [logger]
        else:
            self._loggers = loggers

    def prepare(self):
        """
        Configure the log file.
        """
        self.configure()

    def configure(self):
        """
        Configure the Python logging module for this file.
        """
        # build a file handler for this file
        handler = logging.FileHandler(self.path, delay=True)

        # if we got a format string, create a formatter with it
        if self._format:
            handler.setFormatter(logging.Formatter(self._format))

        # if we got a string for the formatter, assume it's the name of a
        # formatter in the environment's config
        if type(self._formatter) == str:
            if self._env and self._env.config.logging.dict_config.formatters[self._formatter]:
                d = self._env.config.logging.dict_config.formatters[self._formatter].to_dict()
                handler.setFormatter(logging.Formatter(**d))
        elif type(self._formatter) == dict:
            # if it's a dict it must be the actual formatter params
            handler.setFormatter(logging.Formatter(**self._formatter))

        # add the file handler to whatever loggers were specified
        if len(self._loggers):
            for name in self._loggers:
                logging.getLogger(name).addHandler(handler)
        else:
            # none specified, just add it to the root logger
            logging.getLogger().addHandler(handler)

class LockFile(File):
    """
    A file that is automatically created and cleaned up.
    """
    def __init__(self, *args, **kwargs):
        super(LockFile, self).__init__(*args, **kwargs)
        self._create = True
        self._cleanup = True

    def create(self):
        """
        Create the file.

        If the file already exists an exception will be raised
        """
        if not os.path.exists(self.path):
            open(self.path, 'a').close()
        else:
            raise Exception("File exists: {}".format(self.path))

class YamlFile(File):
    """
    A yaml file that is parsed into a dictionary.
    """
    @property
    def content(self):
        """
        Parse the file contents into a dictionary.
        """
        return yaml.safe_load(self.read())

class JsonFile(YamlFile):
    """
    A json file that is parsed into a dictionary.
    """

class PackageFile(File):
    """
    A file whose path is relative to a Python package.
    """
    def __init__(self, path=None, create=False, cleanup=False, parent=None, package=None):
        super(PackageFile, self).__init__(path=path, create=create, cleanup=cleanup, parent=PackageDirectory(package=package))

class Directory(object):
    """
    A filesystem directory.

    A Scruffy Environment usually encompasses a number of these. For example,
    the main Directory object may represent `~/.myproject`.

    >>> d = Directory({
    ...     path='~/.myproject',
    ...     create=True,
    ...     cleanup=False,
    ...     children=[
    ...     ...
    ...     ]
    ... })

    `path` can be either a string representing the path to the directory, or
    a nested Directory object. If a Directory object is passed as the `path`
    its path will be requested instead. This is so Directory objects can be
    wrapped in others to inherit their properties.
    """
    def __init__(self, path=None, base=None, create=True, cleanup=False, parent=None, **kwargs):
        self._path = path
        self._base = base
        self._create = create
        self._cleanup = cleanup
        self._pm = PluginManager()
        self._children = {}
        self._env = None
        self._parent = parent

        if self._path and type(self._path) == str:
            self._path = os.path.expanduser(self._path)

        self.add(**kwargs)

    def __enter__(self):
        self.create()
        return self

    def __exit__(self, type, value, traceback):
        self.cleanup()

    def __getitem__(self, key):
        return self._children[key]

    def __getattr__(self, key):
        return self._children[key]

    def apply_config(self, applicator):
        """
        Replace any config tokens with values from the config.
        """
        if type(self._path) == str:
            self._path = applicator.apply(self._path)

        for key in self._children:
            self._children[key].apply_config(applicator)

    @property
    def path(self):
        """
        Return the path to this directory.
        """
        p = ''

        if self._parent and self._parent.path:
            p = os.path.join(p, self._parent.path)
        if self._base:
            p = os.path.join(p, self._base)
        if self._path:
            p = os.path.join(p, self._path)

        return p

    def create(self):
        """
        Create the directory.

        Directory will only be created if the create flag is set.
        """
        if not self.exists:
            os.mkdir(self.path)

    def remove(self, recursive=True, ignore_error=True):
        """
        Remove the directory.
        """
        try:
            if recursive or self._cleanup == 'recursive':
                shutil.rmtree(self.path)
            else:
                os.rmdir(self.path)
        except Exception as e:
            if not ignore_error:
                raise e

    def prepare(self):
        """
        Prepare the Directory for use in an Environment.

        This will create the directory if the create flag is set.
        """
        if self._create:
            self.create()
        for k in self._children:
            self._children[k]._env = self._env
            self._children[k].prepare()

    def cleanup(self):
        """
        Clean up children and remove the directory.

        Directory will only be removed if the cleanup flag is set.
        """
        for k in self._children:
            self._children[k].cleanup()

        if self._cleanup:
            self.remove(True)

    def path_to(self, path):
        """
        Find the path to something inside this directory.
        """
        return os.path.join(self.path, str(path))

    @property
    def exists(self):
        """
        Check if the directory exists.
        """
        return os.path.exists(self.path)

    def list(self):
        """
        List the contents of the directory.
        """
        return [File(f, parent=self) for f in os.listdir(self.path)]

    def write(self, filename, data, mode='w'):
        """
        Write to a file in the directory.
        """
        with open(self.path_to(str(filename)), mode) as f:
            f.write(data)

    def read(self, filename):
        """
        Read a file from the directory.
        """
        with open(self.path_to(str(filename))) as f:
            d = f.read()
        return d

    def add(self, *args, **kwargs):
        """
        Add objects to the directory.
        """
        for key in kwargs:
            if isinstance(kwargs[key], str):
                self._children[key] = File(kwargs[key])
            else:
                self._children[key] = kwargs[key]
            self._children[key]._parent = self
            self._children[key]._env = self._env

        added = []
        for arg in args:
            if isinstance(arg, File):
                self._children[arg.name] = arg
                self._children[arg.name]._parent = self
                self._children[arg.name]._env = self._env
            elif isinstance(arg, str):
                f = File(arg)
                added.append(f)
                self._children[arg] = f
                self._children[arg]._parent = self
                self._children[arg]._env = self._env
            else:
                raise TypeError(type(arg))

        # if we were passed a single file/filename, return the File object for convenience
        if len(added) == 1:
            return added[0]
        if len(args) == 1:
            return args[0]

class PluginDirectory(Directory):
    """
    A filesystem directory containing plugins.
    """
    def prepare(self):
        """
        Preparing a plugin directory just loads the plugins.
        """
        super(PluginDirectory, self).prepare()
        self.load()

    def load(self):
        """
        Load the plugins in this directory.
        """
        self._pm.load_plugins(self.path)

class PackageDirectory(Directory):
    """
    A filesystem directory relative to a Python package.
    """
    def __init__(self, path=None, package=None, *args, **kwargs):
        super(PackageDirectory, self).__init__(path=path, *args, **kwargs)

        # if we weren't passed a package name, walk up the stack and find the first non-scruffy package
        if not package:
            frame = inspect.currentframe()
            while frame:
                if frame.f_globals['__package__'] != 'scruffy':
                    package = frame.f_globals['__package__']
                    break
                frame = frame.f_back

        # if we found a package, set the path directory to the base dir for the package
        if package:
            self._base = pkg_resources.resource_filename(package, '')
        else:
            raise Exception('No package found')


"""
State
-----

Classes for storing a program's state.
"""
import os
import yaml

try:
    from sqlalchemy import create_engine, Column, Integer, String
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import sessionmaker, reconstructor
    Base = declarative_base()
    HAVE_SQL_ALCHEMY = True
except:
    HAVE_SQL_ALCHEMY = False

class State(object):
    """
    A program's state.

    Contains a dictionary that can be periodically saved and restored at startup.

    Maybe later this will be subclassed with database connectors and whatnot,
    but for now it'll just save to a yaml file.
    """
    @classmethod
    def state(cls, *args, **kwargs):
        return cls(*args, **kwargs)

    def __init__(self, path=None):
        self.path = path
        self.d = {}
        self.load()

    def __enter__(self):
        self.load()
        return self

    def __exit__(self, type, value, traceback):
        self.save()

    def __getitem__(self, key):
        try:
            return self.d[key]
        except KeyError:
            return None

    def __setitem__(self, key, value):
        self.d[key] = value

    def save(self):
        """
        Save the state to a file.
        """
        with open(self.path, 'w') as f:
            f.write(yaml.dump(dict(self.d)))

    def load(self):
        """
        Load a saved state file.
        """
        if os.path.exists(self.path):
            with open(self.path, 'r') as f:
                self.d = yaml.safe_load(f.read().replace('\t', ' '*4))

    def cleanup(self):
        """
        Clean up the saved state.
        """
        if os.path.exists(self.path):
            os.remove(self.path)

if HAVE_SQL_ALCHEMY:
    class DBState(State, Base):
        """
        State stored in a database, using SQLAlchemy.
        """
        __tablename__ = 'state'

        id = Column(Integer, primary_key=True)
        data = Column(String)

        session = None

        @classmethod
        def state(cls, url=None, *args, **kwargs):
            if not cls.session:
                engine = create_engine(url, echo=False)
                Base.metadata.create_all(engine)
                Session = sessionmaker(bind=engine)
                cls.session = Session()

            inst = cls.session.query(DBState).first()
            if inst:
                return inst
            else:
                return cls(*args, **kwargs)

        def __init__(self, *args, **kwargs):
            super(DBState, self).__init__(*args, **kwargs)
            self.d = {}
            self.data = '{}'

        def save(self):
            self.data = yaml.dump(self.d)
            self.session.add(self)
            self.session.commit()

        @reconstructor
        def load(self):
            if self.data:
                self.d = yaml.safe_load(self.data)

        def cleanup(self):
            self.d = {}
            self.save()


"""
Plugin
------

Classes for representing and loading plugins.
"""
import os
import imp
import six

class PluginRegistry(type):
    """
    Metaclass that registers any classes using it in the `plugins` array
    """
    plugins = []
    def __init__(cls, name, bases, attrs):
        if name != 'Plugin' and cls.__name__ not in map(lambda x: x.__name__, PluginRegistry.plugins):
            PluginRegistry.plugins.append(cls)

@six.add_metaclass(PluginRegistry)
class Plugin(object):
    """
    Top-level plugin class, using the PluginRegistry metaclass.

    All plugin modules must implement a single subclass of this class. This
    subclass will be the class collected in the PluginRegistry, and should
    contain references to any other resources required within the module.
    """

class PluginManager(object):
    """
    Loads plugins which are automatically registered with the PluginRegistry
    class, and provides an interface to the plugin collection.
    """
    def load_plugins(self, directory):
        """
        Loads plugins from the specified directory.

        `directory` is the full path to a directory containing python modules
        which each contain a subclass of the Plugin class.

        There is no criteria for a valid plugin at this level - any python
        module found in the directory will be loaded. Only modules that
        implement a subclass of the Plugin class above will be collected.

        The directory will be traversed recursively.
        """
        # walk directory
        for filename in os.listdir(directory):
            # path to file
            filepath = os.path.join(directory, filename)

            # if it's a file, load it
            modname, ext = os.path.splitext(filename)
            if os.path.isfile(filepath) and ext == '.py':
                file, path, descr = imp.find_module(modname, [directory])
                if file:
                    mod = imp.load_module(modname, file, path, descr)

            # if it's a directory, recurse into it
            if os.path.isdir(filepath):
                self.load_plugins(filepath)

    @property
    def plugins(self):
        return PluginRegistry.plugins


"""
Config
------

Classes for loading and accessing configuration data.
"""
import copy
import os
import ast
import yaml
import re

from .file import File

class ConfigNode(object):
    """
    Represents a Scruffy config object.

    Can be accessed as a dictionary, like this:

        >>> config['top-level-section']['second-level-property']

    Or as a dictionary with a key path, like this:

        >>> config['top_level_section.second_level_property']

    Or as an object, like this:

        >>> config.top_level_section.second_level_property
    """
    def __init__(self, data={}, defaults={}, root=None, path=None):
        super(ConfigNode, self).__init__()
        self._root = root
        if not self._root:
            self._root = self
        self._path = path
        self._defaults = defaults
        self._data = copy.deepcopy(self._defaults)
        self.update(data)

    def __getitem__(self, key):
        c = self._child(key)
        v = c._get_value()
        if type(v) in [dict, list, type(None)]:
            return c
        else:
            return v

    def __setitem__(self, key, value):
        container, last = self._child(key)._resolve_path(create=True)
        container[last] = value

    def __getattr__(self, key):
        return self[key]

    def __setattr__(self, key, value):
        if key.startswith("_"):
            super(ConfigNode, self).__setattr__(key, value)
        else:
            self[key] = value

    def __str__(self):
        return str(self._get_value())

    def __repr__(self):
        return str(self._get_value())

    def __int__(self):
        return int(self._get_value())

    def __float__(self):
        return float(self._get_value())

    def __lt__(self, other):
        return self._get_value() < other

    def __le__(self, other):
        return self._get_value() <= other

    def __le__(self, other):
        return self._get_value() <= other

    def __eq__(self, other):
        return self._get_value() == other

    def __ne__(self, other):
        return self._get_value() != other

    def __gt__(self, other):
        return self._get_value() > other

    def __ge__(self, other):
        return self._get_value() >= other

    def __contains__(self, key):
        return key in self._get_value()

    def __nonzero__(self):
        return self._get_value() != None

    def __bool__(self):
        return self._get_value() != None

    def items(self):
        return self._get_value().items()

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

    def __iter__(self):
        return self._get_value().__iter__()

    def _child(self, path):
        """
        Return a ConfigNode object representing a child node with the specified
        relative path.
        """
        if self._path:
            path = '{}.{}'.format(self._path, path)
        return ConfigNode(root=self._root, path=path)

    def _resolve_path(self, create=False):
        """
        Returns a tuple of a reference to the last container in the path, and
        the last component in the key path.

        For example, with a self._value like this:

        {
            'thing': {
                'another': {
                    'some_leaf': 5,
                    'one_more': {
                        'other_leaf': 'x'
                    }
                }
            }
        }

        And a self._path of: 'thing.another.some_leaf'

        This will return a tuple of a reference to the 'another' dict, and
        'some_leaf', allowing the setter and casting methods to directly access
        the item referred to by the key path.
        """
        # Split up the key path
        if type(self._path) == str:
            key_path = self._path.split('.')
        else:
            key_path = [self._path]

        # Start at the root node
        node = self._root._data
        nodes = [self._root._data]

        # Traverse along key path
        while len(key_path):
            # Get the next key in the key path
            key = key_path.pop(0)

            # See if the test could be an int for array access, if so assume it is
            try:
                key = int(key)
            except:
                pass

            # If the next level doesn't exist, create it
            if create:
                if type(node) == dict and key not in node:
                    node[key] = {}
                elif type(node) == list and type(key) == int and len(node) < key:
                    node.append([None for i in range(key-len(node))])

            # Store the last node and traverse down the hierarchy
            nodes.append(node)
            try:
                node = node[key]
            except TypeError:
                if type(key) == int:
                    raise IndexError(key)
                else:
                    raise KeyError(key)

        return (nodes[-1], key)

    def _get_value(self):
        """
        Get the value represented by this node.
        """
        if self._path:
            try:
                container, last = self._resolve_path()
                return container[last]
            except KeyError:
                return None
            except IndexError:
                return None
        else:
            return self._data

    def update(self, data={}, options={}):
        """
        Update the configuration with new data.

        This can be passed either or both `data` and `options`.

        `options` is a dict of keypath/value pairs like this (similar to
        CherryPy's config mechanism:

            >>> c.update(options={
            ...     'server.port': 8080,
            ...     'server.host': 'localhost',
            ...     'admin.email': 'admin@lol'
            ... })

        `data` is a dict of actual config data, like this:

            >>> c.update(data={
            ...     'server': {
            ...         'port': 8080,
            ...         'host': 'localhost'
            ...     },
            ...     'admin': {
            ...         'email': 'admin@lol'
            ...     }
            ... })
        """
        # Handle an update with a set of options like CherryPy does
        for key in options:
            self[key] = options[key]

        # Merge in any data in `data`
        if isinstance(data, ConfigNode):
            data = data._get_value()
        update_dict(self._get_value(), data)

    def reset(self):
        """
        Reset the config to defaults.
        """
        self._data = copy.deepcopy(self._defaults)

    def to_dict(self):
        """
        Generate a plain dictionary.
        """
        return self._get_value()

class Config(ConfigNode):
    """
    Config root node class. Just for convenience.
    """

class ConfigEnv(ConfigNode):
    """
    Config based on based on environment variables.
    """
    def __init__(self, prefix='SCRUFFY', *args, **kwargs):
        super(ConfigEnv, self).__init__(*args, **kwargs)

        # build options dictionary from environment variables starting with the prefix
        options = {}
        for key in [v for v in os.environ if v.startswith('__SC_') or v.startswith(prefix + '_')]:
            try:
                val = ast.literal_eval(os.environ[key])
            except:
                val = os.environ[key]
            options[key.replace('__SC_', '').replace(prefix + '_', '').lower()] = val

        # update config with the values we've found
        self.update(options=options)

class ConfigFile(Config, File):
    """
    Config based on a loaded YAML or JSON file.
    """
    def __init__(self, path=None, defaults=None, load=False, apply_env=False, env_prefix='SCRUFFY', *args, **kwargs):
        self._loaded = False
        self._defaults_file = defaults
        self._apply_env = apply_env
        self._env_prefix = env_prefix
        Config.__init__(self)
        File.__init__(self, path=path, *args, **kwargs)

        if load:
            self.load()

    def load(self, reload=False):
        """
        Load the config and defaults from files.
        """
        if reload or not self._loaded:
            # load defaults
            if self._defaults_file and type(self._defaults_file) == str:
                self._defaults_file = File(self._defaults_file, parent=self._parent)
            defaults = {}
            if self._defaults_file:
                defaults = yaml.safe_load(self._defaults_file.read().replace('\t', '    '))

            # load data
            data = {}
            if self.exists:
                data = yaml.safe_load(self.read().replace('\t', '    '))

            # initialise with the loaded data
            self._defaults = defaults
            self._data = copy.deepcopy(self._defaults)
            self.update(data=data)

            # if specified, apply environment variables
            if self._apply_env:
                self.update(ConfigEnv(self._env_prefix))

            self._loaded = True

        return self

    def save(self):
        """
        Save the config back to the config file.
        """
        self.write(yaml.safe_dump(self._data))

    def prepare(self):
        """
        Load the file when the Directory/Environment prepares us.
        """
        self.load()

class ConfigApplicator(object):
    """
    Applies configs to other objects.
    """
    def __init__(self, config):
        self.config = config

    def apply(self, obj):
        """
        Apply the config to an object.
        """
        if type(obj) == str:
            return self.apply_to_str(obj)

    def apply_to_str(self, obj):
        """
        Apply the config to a string.
        """
        toks = re.split('({config:|})', obj)
        newtoks = []
        try:
            while len(toks):
                tok = toks.pop(0)
                if tok == '{config:':
                    # pop the config variable, look it up
                    var = toks.pop(0)
                    val = self.config[var]

                    # if we got an empty node, then it didn't exist
                    if type(val) == ConfigNode and val == None:
                        raise KeyError("No such config variable '{}'".format(var))

                    # add the value to the list
                    newtoks.append(str(val))

                    # pop the '}'
                    toks.pop(0)
                else:
                    # not the start of a config block, just append it to the list
                    newtoks.append(tok)
            return ''.join(newtoks)
        except IndexError:
            pass

        return obj

def update_dict(target, source):
    """
    Recursively merge values from a nested dictionary into another nested
    dictionary.

    For example:

    >>> target = {
    ...     'thing': 123,
    ...     'thang': {
    ...         'a': 1,
    ...         'b': 2
    ...     }
    ... }
    >>> source = {
    ...     'thang': {
    ...         'a': 666,
    ...         'c': 777
    ...     }
    ... }
    >>> update_dict(target, source)
    >>> target
    {
        'thing': 123,
        'thang': {
            'a': 666,
            'b': 2,
            'c': 777
        }
    }
    """
    for k,v in source.items():
        if isinstance(v, dict) and k in target and isinstance(source[k], dict):
            update_dict(target[k], v)
        else:
            target[k] = v


import functools
import logging

from django.conf import settings
from django.http import HttpResponse, HttpResponseBadRequest
from django.utils.decorators import available_attrs, method_decorator
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt

from rapidsms.backends.http.views import GenericHttpBackendView
from twilio.util import RequestValidator

from rtwilio.models import TwilioResponse
from rtwilio.forms import StatusCallbackForm, TwilioForm

logger = logging.getLogger(__name__)

def validate_twilio_signature(func=None, backend_name='twilio-backend'):
    """View decorator to validate requests from Twilio per http://www.twilio.com/docs/security."""

    def _dec(view_func):
        @functools.wraps(view_func, assigned=available_attrs(view_func))
        def _wrapped_view(request, *args, **kwargs):
            backend = kwargs.get('backend_name', backend_name)
            config = settings.INSTALLED_BACKENDS[backend]['config']
            validator = RequestValidator(config['auth_token'])
            signature = request.META.get('HTTP_X_TWILIO_SIGNATURE', '')
            url = request.build_absolute_uri()
            body = {}
            if request.method == 'POST':
                body = request.POST
            require_validation = config.get('validate', True)
            if validator.validate(url, body, signature) or not require_validation:
                return view_func(request, *args, **kwargs)
            else:
                return HttpResponseBadRequest()
        return _wrapped_view

    if func is None:
        return _dec
    else:
        return _dec(func)

class TwilioBackendView(GenericHttpBackendView):
    """
    Backend view for processing incoming messages from Twilio.
    https://www.twilio.com/docs/api/twiml/sms/twilio_request
    """

    http_method_names = ['post']
    form_class = TwilioForm
    backend_name = 'twilio-backend'

    @method_decorator(validate_twilio_signature)
    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        return super(TwilioBackendView, self).dispatch(*args, **kwargs)

    def form_valid(self, form):
        super(TwilioBackendView, self).form_valid(form)
        return HttpResponse('')

@require_POST
@csrf_exempt
@validate_twilio_signature
def status_callback(request, backend_name=None):
    form = StatusCallbackForm(request.POST or None)
    if form.is_valid():
        try:
            TwilioResponse.objects.create(
                pk=form.cleaned_data['SmsSid'],
                account=form.cleaned_data['AccountSid'],
                sender=form.cleaned_data['From'],
                recipient=form.cleaned_data['To'],
                status=form.cleaned_data['SmsStatus'],
            )
        except:
            logger.debug("callback data")
            logger.debug(form.cleaned_data)
            raise
        return HttpResponse('')
    else:
        return HttpResponseBadRequest()


#-*- coding: utf-8 -*-

from __future__ import unicode_literals

from decimal import Decimal
import logging

from django.conf import settings
from django.core.urlresolvers import reverse
from django.utils.encoding import force_text

from twilio.rest import TwilioRestClient

from .models import OutgoingSMS

logger = logging.getLogger("django-twilio-sms.utils")

def build_callback_url(request, urlname, message):
    """
    Build Twilio callback url for confirming message delivery status

    :type message: OutgoingSMS
    """
    location = reverse(urlname, kwargs={"pk": message.pk})

    callback_domain = getattr(settings, "TWILIO_CALLBACK_DOMAIN", None)
    if callback_domain:
        url = "{}://{}{}".format(
            "https" if getattr(settings, "TWILIO_CALLBACK_USE_HTTPS", False) else "http",
            callback_domain,
            location
        )

    elif request is not None:
        url = request.build_absolute_uri(location)
    else:
        raise ValueError(
            "Unable to build callback url. Configure TWILIO_CALLBACK_DOMAIN "
            "or pass request object to function call"
        )
    return url

def send_sms(request, to_number, body, callback_urlname="sms_status_callback"):
    """
    Create :class:`OutgoingSMS` object and send SMS using Twilio.
    """
    client = TwilioRestClient(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
    from_number = settings.TWILIO_PHONE_NUMBER

    message = OutgoingSMS.objects.create(
        from_number=from_number,
        to_number=to_number,
        body=body,
    )

    status_callback = None
    if callback_urlname:
        status_callback = build_callback_url(request, callback_urlname, message)

    logger.debug("Sending SMS message to %s with callback url %s: %s.",
                 to_number, status_callback, body)

    if not getattr(settings, "TWILIO_DRY_MODE", False):
        sent = client.sms.messages.create(
            to=to_number,
            from_=from_number,
            body=body,
            status_callback=status_callback
        )
        logger.debug("SMS message sent: %s", sent.__dict__)

        message.sms_sid = sent.sid
        message.account_sid = sent.account_sid
        message.status = sent.status
        message.to_parsed = sent.to
        if sent.price:
            message.price = Decimal(force_text(sent.price))
            message.price_unit = sent.price_unit
        message.sent_at = sent.date_created
        message.save(update_fields=[
            "sms_sid", "account_sid", "status", "to_parsed",
            "price", "price_unit", "sent_at"
        ])
    else:
        logger.info("SMS: from %s to %s: %s", from_number, to_number, body)
    return message


from gevent import monkey
monkey.patch_all()

import io
import os
import re
import sys
import time
import webbrowser
from functools import partial
from traceback import format_exc

import jinja2
from bottle import default_app as Bottle, response, static_file
from bottle import jinja2_view
from gevent.pywsgi import WSGIServer

import misaka
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter
from cgi import escape as escape_html

import click

__author__ = "Linjie Ding "
__license__ = "MIT"
__version__ = "0.5.4"

# Logging ---------------------------------------------------------------------

def logger(msg, level, fg, bg=None, action=None):
    level = click.style(level, fg=fg, bg=bg, bold=True)
    click.echo("%s - %s" % (level, msg), err=True)
    if callable(action):
        action()

def die():
    click.secho(format_exc(), fg='red', bold=True, err=True)
    sys.exit(-1)

ok = partial(logger, level='ok', fg='green')
info = partial(logger, level='info', fg='cyan')
error = partial(logger, level='error', fg='red', action=lambda: sys.exit(-1))

# Markdown --------------------------------------------------------------------

RENDER_EXTENSIONS = misaka.EXT_FENCED_CODE       \
                  | misaka.EXT_NO_INTRA_EMPHASIS \
                  | misaka.EXT_AUTOLINK          \
                  | misaka.EXT_STRIKETHROUGH     \
                  | misaka.EXT_SUPERSCRIPT       \
                  | misaka.EXT_TABLES
HTML_TITLE = re.compile(r"\s*
([^<]*)
")

class HtmlRenderer(misaka.HtmlRenderer):
    def blockcode(self, text, lang=''):
        if not lang:
            return "\n
%s
\n" % escape_html(text)
        lexer = get_lexer_by_name(lang, stripall=True)
        formatter = HtmlFormatter()
        return highlight(text, lexer, formatter)

markdown = misaka.Markdown(HtmlRenderer(), RENDER_EXTENSIONS)

def render(text, filename=""):
    title = os.path.basename(filename)
    yaml_title, text = strip_yaml(text)
    if yaml_title is not None:
        title = yaml_title
    html = markdown(text)
    title_match = HTML_TITLE.match(html)
    if title_match is not None and yaml_title is None:
        title = title_match.group(1)
    return { "title": title, "html": html }

YAML_BOUNDARY = re.compile(r"^-{3,}$", re.MULTILINE)
YAML_TITLE = re.compile(r"\s*title:\s*(.+)", re.MULTILINE)

def strip_yaml(text):
    try:
        prelude, frontmatter, content = YAML_BOUNDARY.split(text, 2)
        if prelude.strip():
            return None, text
    except ValueError:
        return None, text
    title_match = YAML_TITLE.search(frontmatter)
    if title_match is not None:
        return title_match.group(1), content
    else:
        return None, content

# Preview ---------------------------------------------------------------------

SRC_ROOT = os.path.dirname(os.path.realpath(__file__))
STATIC_ROOT = os.path.join(SRC_ROOT, 'static')
TEMPLATE_ROOT = os.path.join(SRC_ROOT, 'templates')

view = partial(jinja2_view, template_lookup=[TEMPLATE_ROOT])
open = partial(io.open, encoding="utf-8")

def preview(filename, options={}):
    path = os.path.abspath(filename)
    dirpath = os.path.dirname(filename)
    realpath = os.path.realpath(path)

    if not os.path.isfile(realpath):
        error("%s is not a regular file" % filename)

    app = Bottle()

    @app.route('/')
    @view('index')
    def index():
        return { 'css': options.get('css', True) }

    @app.route('/content')
    def content():
        with open(path, 'rt') as fp:
            return render(fp.read(), path)

    @app.route('/update-event')
    def update():
        response.set_header('Content-Type', 'text/event-stream')
        response.set_header('Cache-Control', 'no-cache')
        mtime = lambda: os.stat(path).st_mtime
        last_mtime = mtime()
        while True:
            if not os.path.exists(path):
                error("%s no longer exists" % filename)
            current_mtime = mtime()
            if current_mtime > last_mtime:
                last_mtime = current_mtime
                ok('%s updated' % filename)
                yield 'data: update\n\n'
            time.sleep(0.25)

    @app.route('/__moo__/<res:path>')
    def moo_static(res):
        return static_file(res, root=STATIC_ROOT)

    @app.route('/<res:path>')
    def static(res):
        return static_file(res, root=dirpath)

    server = WSGIServer(("127.0.0.1", options.get('port', 0)), app, log=None)
    server.start()

    url = 'http://127.0.0.1:%d' % server.server_port
    info('Server listening at %s. Press Ctrl-C to stop.' % url)

    webbrowser.open(url)
    server.serve_forever()

# Export ----------------------------------------------------------------------

def export_file(infile, outfile, template, options, filename=""):
    text = infile.read()
    payload = render(text, filename)
    payload.update(options)
    html = template.render(**payload)
    outfile.write(html)
    outfile.flush()

def export_files(files, options={}):
    loader = jinja2.FileSystemLoader(TEMPLATE_ROOT)
    env = jinja2.Environment(loader=loader)
    template = env.get_template('export.html')

    def include_file(name):
        return jinja2.Markup(loader.get_source(env, name)[0])
    env.globals['include_file'] = include_file

    for infile in files:
        if infile == '-':
            export_file(sys.stdin, sys.stdout, template, options)
            ok("STDIN -> STDOUT")
        else:
            outfile = os.path.splitext(infile)[0] + '.html'
            with open(infile, 'rt') as i, open(outfile, 'wt') as o:
                export_file(i, o, template, options, filename=infile)
                ok("%s -> %s" % (infile, outfile))

# Main ------------------------------------------------------------------------

def version(ctx, param, value):
    if not value or ctx.resilient_parsing:
        return
    click.echo(__version__)
    ctx.exit()

@click.command()
@click.option('-p', '--port', default=0,
              help='server port (default: random)')
@click.option('-e', '--export', is_flag=True, default=False,
              help='export rendered HTML instead of preview')
@click.option('--css/--no-css', default=True,
              help='css styling (default: enabled)')
@click.option('--version', is_flag=True, callback=version,
              expose_value=False, is_eager=True, help='print the version')
@click.argument('files', nargs=-1,
                type=click.Path(exists=True, dir_okay=False))
def main(port, export, css, files):
    """
    \b
    Examples:
    $ moo README.md                     # live preview README.md
    $ moo -e *.md                       # export all markdown files
    $ moo --no-css -e README.md         # export README.md without CSS
    $ cat README.md | moo -e - | less   # export STDIN to STDOUT
    """
    options = { 'css': css, 'port': port }
    try:
        if not export:
            if len(files) != 1:
                error("please specify just one file to preview")
            preview(files[0], options)
        else:
            export_files(files, options)
    except KeyboardInterrupt:
        sys.exit(0)
    except Exception as exc:
        die()

if __name__ == '__main__':
    main()


#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
"""
This module implements a simple RPC server that can be used with the example
RPC client.  The client sends a 'method call'
to the server, and waits for a response.  The method call is a map of the form:
{'method': '<name of method on server>',
 'args': {<map of name=value arguments for the call}
}
The server replies to the client using a map that contains a copy of the method
map sent in the request.
"""

import errno
import logging
import optparse
import re
import socket
import select
import sys
import time
import uuid
# import gc

# from guppy import hpy
# hp = hpy()

from proton import Message, Condition
import pyngus

LOG = logging.getLogger()
LOG.addHandler(logging.StreamHandler())

# Maps of outgoing and incoming links.  These are indexed by
# (remote-container-name, link-name)
sender_links = {}
receiver_links = {}

# links that have closed and need to be destroyed:
dead_links = set()

# Map reply-to address to the proper sending link (indexed by address)
reply_senders = {}

# database of all active SocketConnections
socket_connections = {}  # indexed by name

class SocketConnection(pyngus.ConnectionEventHandler):
    """Associates a pyngus Connection with a python network socket"""

    def __init__(self, name, socket_, container, conn_properties):
        self.name = name
        self.socket = socket_
        self.connection = container.create_connection(name, self,
                                                      conn_properties)
        self.connection.user_context = self
        self.connection.open()
        self.done = False

    def destroy(self):
        self.done = True
        if self.connection:
            self.connection.destroy()
            self.connection = None
        if self.socket:
            self.socket.close()
            self.socket = None

    def fileno(self):
        """Allows use of a SocketConnection in a select() call.
        """
        return self.socket.fileno()

    def process_input(self):
        """Called when socket is read-ready"""
        try:
            pyngus.read_socket_input(self.connection, self.socket)
        except Exception as e:
            LOG.error("Exception on socket read: %s", str(e))
            self.connection.close_input()
            self.connection.close()
        self.connection.process(time.time())

    def send_output(self):
        """Called when socket is write-ready"""
        try:
            pyngus.write_socket_output(self.connection,
                                       self.socket)
        except Exception as e:
            LOG.error("Exception on socket write: %s", str(e))
            self.connection.close_output()
            self.connection.close()
        self.connection.process(time.time())

    # ConnectionEventHandler callbacks:

    def connection_active(self, connection):
        LOG.debug("Connection active callback")

    def connection_remote_closed(self, connection, reason):
        LOG.debug("Connection remote closed callback")
        assert self.connection is connection
        self.connection.close()

    def connection_closed(self, connection):
        LOG.debug("connection closed.")
        # main loop will destroy
        self.done = True

    def connection_failed(self, connection, error):
        LOG.error("connection failed! error=%s", str(error))
        self.connection_closed(connection)

    def sender_requested(self, connection, link_handle,
                         name, requested_source, properties):
        LOG.debug("sender requested callback")
        global sender_links
        global reply_senders

        # reject if name conflict
        remote_container = connection.remote_container
        ident = (remote_container, name)
        if ident in sender_links:
            connection.reject_sender(link_handle, "link name in use")
            return

        # allow for requested_source address if it doesn't conflict with an
        # existing address, otherwise override
        if not requested_source or requested_source in reply_senders:
            requested_source = uuid.uuid4().hex
            assert requested_source not in reply_senders

        sender = MySenderLink(ident, connection, link_handle, requested_source)
        sender_links[ident] = sender
        reply_senders[requested_source] = sender
        print("New Sender link created, source=%s" % requested_source)

    def receiver_requested(self, connection, link_handle,
                           name, requested_target, properties):
        LOG.debug("receiver requested callback")
        global receiver_links

        # reject if name conflict
        remote_container = connection.remote_container
        ident = (remote_container, name)
        if ident in receiver_links:
            connection.reject_sender(link_handle, "link name in use")
            return

        # I don't use the target address, but supply one if necessary
        if not requested_target:
            requested_target = uuid.uuid4().hex

        receiver = MyReceiverLink(ident, connection,
                                  link_handle, requested_target)
        receiver_links[ident] = receiver
        print("New Receiver link created, target=%s" % requested_target)

    # SASL callbacks:

    def sasl_step(self, connection, pn_sasl):
        LOG.debug("SASL step callback")
        pn_sasl.done(pn_sasl.OK)

    def sasl_done(self, connection, pn_sasl, result):
        LOG.debug("SASL done callback, result=%s", str(result))

class MySenderLink(pyngus.SenderEventHandler):
    """Link for sending RPC replies."""
    def __init__(self, ident, connection, link_handle,
                 source_address, properties=None):

        self._ident = ident
        self._source_address = source_address
        self.sender_link = connection.accept_sender(link_handle,
                                                    source_address,
                                                    self,
                                                    properties)
        self.sender_link.open()

    @property
    def closed(self):
        if self.sender_link:
            return self.sender_link.closed
        return True

    # SenderEventHandler callbacks:

    def sender_active(self, sender_link):
        LOG.debug("sender active callback")

    def sender_remote_closed(self, sender_link, error):
        LOG.debug("sender remote closed callback")
        self.sender_link.close()

    def sender_closed(self, sender_link):
        LOG.debug("sender closed callback")
        global sender_links
        global reply_senders
        global dead_links

        if self._ident in sender_links:
            del sender_links[self._ident]
        if self._source_address in reply_senders:
            del reply_senders[self._source_address]

        dead_links.add(self.sender_link)
        self.sender_link = None

    # 'message sent' callback:

    def __call__(self, sender, handle, status, error=None):
        LOG.debug("message sent callback, status=%s", str(status))

class MyReceiverLink(pyngus.ReceiverEventHandler):
    """
    """
    def __init__(self, ident, connection, link_handle, target_address,
                 properties=None):
        self._ident = ident
        self._target_address = target_address
        self._link = connection.accept_receiver(link_handle,
                                                target_address,
                                                self,
                                                properties)
        self._link.open()

    @property
    def closed(self):
        if self._link:
            return self._link.closed
        return True

    # ReceiverEventHandler callbacks:
    def receiver_active(self, receiver_link):
        LOG.debug("receiver active callback")
        self._link.add_capacity(5)

    def receiver_remote_closed(self, receiver_link, error):
        LOG.debug("receiver remote closed callback")
        self._link.close()

    def receiver_closed(self, receiver_link):
        LOG.debug("receiver closed callback")
        global receiver_links
        global dead_links

        if self._ident in receiver_links:
            del receiver_links[self._ident]

        dead_links.add(self._link)
        self._link = None

    def message_received(self, receiver_link, message, handle):
        LOG.debug("message received callback")

        global reply_senders

        # extract to reply-to, correlation id
        reply_to = message.reply_to
        if not reply_to or reply_to not in reply_senders:
            LOG.error("sender for reply-to not found, reply-to=%s",
                      str(reply_to))
            info = Condition("not-found",
                             "Bad reply-to address: %s" % str(reply_to))
            self._link.message_rejected(handle, info)
        else:
            my_sender = reply_senders[reply_to]
            correlation_id = message.correlation_id
            method_map = message.body
            if (not isinstance(method_map, dict) or
                    'method' not in method_map):
                LOG.error("no method given, map=%s", str(method_map))
                info = Condition("invalid-field",
                                 "no method given, map=%s" % str(method_map))
                self._link.message_rejected(handle, info)
            else:
                response = Message()
                response.address = reply_to
                response.subject = message.subject
                response.correlation_id = correlation_id
                response.body = {"response": method_map}

                print("RPC request received, msg=%s" % str(method_map))
                print("  to address=%s" % str(message.address))
                print("  replying to=%s" % str(reply_to))
                link = my_sender.sender_link
                # @todo send timeouts
                # link.send( response, my_sender,
                #            message, time.time() + 5.0)
                link.send(response, my_sender, message)

                self._link.message_accepted(handle)

        if self._link.capacity == 0:
            LOG.debug("increasing credit...")
            self._link.add_capacity(5)

def main(argv=None):

    _usage = """Usage: %prog [options]"""
    parser = optparse.OptionParser(usage=_usage)
    parser.add_option("-a", dest="address", type="string",
                      default="amqp://0.0.0.0:5672",
                      help="""The socket address this server will listen on
 [amqp://0.0.0.0:5672]""")
    parser.add_option("--idle", dest="idle_timeout", type="float",
                      help="timeout for an idle link, in seconds")
    parser.add_option("--trace", dest="trace", action="store_true",
                      help="enable protocol tracing")
    parser.add_option("--debug", dest="debug", action="store_true",
                      help="enable debug logging")
    parser.add_option("--cert",
                      help="PEM File containing the server's certificate")
    parser.add_option("--key",
                      help="PEM File containing the server's private key")
    parser.add_option("--keypass",
                      help="Password used to decrypt key file")

    opts, arguments = parser.parse_args(args=argv)
    if opts.debug:
        LOG.setLevel(logging.DEBUG)

    # Create a socket for inbound connections
    #
    regex = re.compile(r"^amqp://([a-zA-Z0-9.]+)(:([\d]+))?$")
    LOG.debug("Listening on %s", opts.address)
    x = regex.match(opts.address)
    if not x:
        raise Exception("Bad address syntax: %s" % opts.address)
    matches = x.groups()
    host = matches[0]
    port = int(matches[2]) if matches[2] else None
    addr = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
    if not addr:
        raise Exception("Could not translate address '%s'" % opts.address)
    my_socket = socket.socket(addr[0][0], addr[0][1], addr[0][2])
    my_socket.setblocking(0)  # 0=non-blocking
    try:
        my_socket.bind((host, port))
        my_socket.listen(10)
    except socket.error as e:
        if e.errno != errno.EINPROGRESS:
            raise

    # create an AMQP container that will 'provide' the RPC service
    #
    container = pyngus.Container("example RPC service")
    global socket_connections
    global dead_links

    while True:

        #
        # Poll for I/O & timers
        #

        readfd = [my_socket]
        writefd = []
        readers, writers, timers = container.need_processing()

        # map pyngus Connections back to my SocketConnections
        for c in readers:
            sc = c.user_context
            assert sc and isinstance(sc, SocketConnection)
            readfd.append(sc)
        for c in writers:
            sc = c.user_context
            assert sc and isinstance(sc, SocketConnection)
            writefd.append(sc)

        timeout = None
        if timers:
            deadline = timers[0].next_tick  # 0 == next expiring timer
            now = time.time()
            timeout = 0 if deadline <= now else deadline - now

        LOG.debug("select() start (t=%s)", str(timeout))
        readable, writable, ignore = select.select(readfd,
                                                   writefd,
                                                   [],
                                                   timeout)
        LOG.debug("select() returned")

        worked = []
        for r in readable:
            if r is my_socket:
                # new inbound connection request
                client_socket, client_address = my_socket.accept()
                name = uuid.uuid4().hex
                assert name not in socket_connections
                conn_properties = {'x-server': True}
                if opts.idle_timeout:
                    conn_properties["idle-time-out"] = opts.idle_timeout
                if opts.trace:
                    conn_properties["x-trace-protocol"] = True
                if opts.cert:
                    conn_properties["x-ssl-server"] = True
                    identity = (opts.cert, opts.key, opts.keypass)
                    conn_properties["x-ssl-identity"] = identity
                socket_connections[name] = SocketConnection(name,
                                                            client_socket,
                                                            container,
                                                            conn_properties)
                LOG.debug("new connection created name=%s", name)

            else:
                assert isinstance(r, SocketConnection)
                r.process_input()
                worked.append(r)

        for t in timers:
            now = time.time()
            if t.next_tick > now:
                break
            t.process(now)
            sc = t.user_context
            assert isinstance(sc, SocketConnection)
            worked.append(sc)

        for w in writable:
            assert isinstance(w, SocketConnection)
            w.send_output()
            worked.append(w)

        # first, free any closed links:
        while dead_links:
            dead_links.pop().destroy()

        # then nuke any completed connections:
        closed = False
        while worked:
            sc = worked.pop()
            if sc.done:
                if sc.name in socket_connections:
                    del socket_connections[sc.name]
                sc.destroy()
                closed = True
        if closed:
            LOG.debug("%d active connections present", len(socket_connections))

    return 0

if __name__ == "__main__":
    sys.exit(main())


#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
""" This module implements a simple RPC client.  The client sends a 'method
call' to a server, and waits for a response.  The method call is a map of the
form: {'method': '<name of method on server>', 'args': {<map of name=value
arguments for the call} }
"""

import errno
import logging
import optparse
import re
import socket
import select
import sys
import time
import uuid

from proton import Message
import pyngus

LOG = logging.getLogger()
LOG.addHandler(logging.StreamHandler())

class MyConnection(pyngus.ConnectionEventHandler):

    def __init__(self, name, container, properties):
        self.name = name
        self.container = container
        self.properties = properties
        self.socket = None
        self.caller = None
        self.connection = None

    def reset(self):
        if self.caller:
            self.caller.reset()
        if self.connection:
            self.connection.user_context = None
            self.connection.destroy()
            self.connection = None
        if self.socket:
            self.socket.close()
            self.socket = None

    def connect(self, socket_):
        self.reset()
        self.socket = socket_
        self.connection = self.container.create_connection(self.name,
                                                           self,
                                                           self.properties)
        self.connection.user_context = self
        self.connection.open()

    def process(self):
        """ Do connection-based processing (I/O and timers) """
        readfd = []
        writefd = []
        if self.connection.needs_input > 0:
            readfd = [self.socket]
        if self.connection.has_output > 0:
            writefd = [self.socket]

        timeout = None
        deadline = self.connection.next_tick
        if deadline:
            now = time.time()
            timeout = 0 if deadline <= now else deadline - now

        LOG.debug("select() start (t=%s)", str(timeout))
        readable, writable, ignore = select.select(readfd,
                                                   writefd,
                                                   [],
                                                   timeout)
        LOG.debug("select() returned")

        if readable:
            try:
                pyngus.read_socket_input(self.connection,
                                         self.socket)
            except Exception as e:
                LOG.error("Exception on socket read: %s", str(e))
                self.connection.close_input()
                self.connection.close()

        self.connection.process(time.time())
        if writable:
            try:
                pyngus.write_socket_output(self.connection,
                                           self.socket)
            except Exception as e:
                LOG.error("Exception on socket write: %s", str(e))
                self.connection.close_output()
                self.connection.close()

    def close(self, error=None):
        self.connection.close(error)

    @property
    def closed(self):
        return self.connection.closed

    def destroy(self, error=None):
        self.reset()
        self.caller.destroy()
        self.caller = None
        self.container = None

    def create_caller(self, method_map, source_addr, target_addr,
                      receiver_properties, sender_properties):
        """ Caller factory
        """
        if self.caller:
            self.caller.destroy()
        self.caller = MyCaller(method_map, self, source_addr, target_addr,
                               receiver_properties, sender_properties)
        return self.caller

    # Connection callbacks:

    def connection_active(self, connection):
        """connection handshake completed"""
        LOG.debug("Connection active callback")

    def connection_remote_closed(self, connection, reason):
        LOG.debug("Connection remote closed callback")
        connection.close()

    def connection_closed(self, connection):
        LOG.debug("Connection closed callback")

    def connection_failed(self, connection, error):
        LOG.error("Connection failed! error=%s", str(error))
        raise Exception("Connection failure: %s" % str(error))

    def sender_requested(self, connection, link_handle, name,
                         requested_source, properties):
        # call accept_sender to accept new link,
        # reject_sender to reject it.
        assert False, "Not expected"

    def receiver_requested(self, connection, link_handle, name,
                           requested_target, properties):
        # call accept_sender to accept new link,
        # reject_sender to reject it.
        assert False, "Not expected"

    def sasl_done(self, connection, pn_sasl, result):
        LOG.debug("SASL done, result=%s", str(result))

class MyCaller(pyngus.SenderEventHandler,
               pyngus.ReceiverEventHandler):
    """Implements state for a single RPC call."""

    def __init__(self, method_map, my_connection,
                 my_source, my_target,
                 receiver_properties, sender_properties):
        # self._name = uuid.uuid4().hex
        self._my_connection = my_connection
        self._source_addr = my_source
        self._target_addr = my_target
        self._receiver_properties = receiver_properties
        self._sender_properties = sender_properties
        self._method = method_map
        self._sender = None
        self._receiver = None
        self.reset()

    def reset(self):
        LOG.debug("Resetting my-caller")
        # TODO(kgiusti: for now, use new name as engine isn't cleaning up link
        # state properly...

        self._name = uuid.uuid4().hex
        self._reply_to = None
        self._to = None
        self._send_completed = False
        self._response_received = False

        if self._sender:
            self._sender.destroy()
            self._sender = None

        if self._receiver:
            self._receiver.destroy()
            self._receiver = None

    def connect(self):
        self.reset()
        LOG.debug("Connecting my-caller")
        conn = self._my_connection.connection
        self._sender = conn.create_sender(
            self._source_addr,
            target_address=None,
            event_handler=self,
            # name=self._source_addr,
            name=self._name,
            properties=self._sender_properties)
        self._receiver = conn.create_receiver(
            self._target_addr,
            source_address=None,
            event_handler=self,
            # name=self._target_addr,
            name=self._name,
            properties=self._receiver_properties)
        self._sender.open()
        self._receiver.add_capacity(1)
        self._receiver.open()

    def done(self):
        return self._send_completed and self._response_received

    def close(self):
        LOG.debug("Closing my-caller")
        self._sender.close(None)
        self._receiver.close(None)

    def closed(self):
        return self._sender.closed and self._receiver.closed

    def destroy(self):
        LOG.debug("Destroying my-caller")
        self.reset()
        self._my_connection = None

    def _send_request(self):
        """Send a message containing the RPC method call
        """
        msg = Message()
        msg.subject = "An RPC call!"
        msg.address = self._to
        msg.reply_to = self._reply_to
        msg.body = self._method
        msg.correlation_id = 5  # whatever...

        print("sending RPC call request: %s" % str(self._method))
        # @todo send timeout self._sender.send(msg, self, None, time.time() +
        # 10)
        self._sender.send(msg, self)

    # SenderEventHandler callbacks:

    def sender_active(self, sender_link):
        LOG.debug("Sender active callback")
        assert sender_link is self._sender
        self._to = sender_link.target_address
        assert self._to, "Expected a target address!!!"
        if self._reply_to:
            self._send_request()

    def sender_remote_closed(self, sender_link, error):
        LOG.debug("Sender remote closed callback")
        assert sender_link is self._sender
        self._sender.close()

    def sender_closed(self, sender_link):
        LOG.debug("Sender closed callback")
        assert sender_link is self._sender
        self._send_completed = True

    # send complete callback:

    def __call__(self, sender_link, handle, status, error):
        LOG.debug("Sender message sent callback, status=%s", str(status))
        self._send_completed = True

    # ReceiverEventHandler callbacks:

    def receiver_active(self, receiver_link):
        LOG.debug("Receiver active callback")
        assert receiver_link is self._receiver
        self._reply_to = receiver_link.source_address
        assert self._reply_to, "Expected a source address!!!"
        if self._to:
            self._send_request()

    def receiver_remote_closed(self, receiver_link, error):
        LOG.debug("receiver remote closed callback")
        assert receiver_link is self._receiver
        self._receiver.close()

    def receiver_closed(self, receiver_link):
        LOG.debug("Receiver closed callback")
        assert receiver_link is self._receiver
        self._response_received = True

    def message_received(self, receiver_link, message, handle):
        LOG.debug("Receiver message received callback")
        assert receiver_link is self._receiver
        print("RPC call response received: %s" % str(message))
        receiver_link.message_accepted(handle)
        self._response_received = True

def main(argv=None):

    _usage = """Usage: %prog [options] method arg1 value1 [arg2 value2] ..."""
    parser = optparse.OptionParser(usage=_usage)
    parser.add_option("-a", dest="server", type="string",
                      default="amqp://0.0.0.0:5672",
                      help="The address of the server [amqp://0.0.0.0:5672]")
    parser.add_option("-t", "--timeout", dest="timeout", type="int",
                      help="timeout used when waiting for reply, in seconds")
    parser.add_option("--repeat", dest="repeat", type="int",
                      default=1,
                      help="Repeat the RPC call REPEAT times (0 == forever)")
    parser.add_option("--trace", dest="trace", action="store_true",
                      help="enable protocol tracing")
    parser.add_option("--debug", dest="debug", action="store_true",
                      help="enable debug logging")
    parser.add_option("--ca",
                      help="Certificate Authority PEM file")

    opts, method_info = parser.parse_args(args=argv)
    if not method_info:
        assert False, "No method info specified!"
    if len(method_info) % 2 != 1:
        assert False, "An even number of method arguments are required!"

    if opts.debug:
        LOG.setLevel(logging.DEBUG)

    # Create a socket connection to the server
    #
    regex = re.compile(r"^amqp://([a-zA-Z0-9.]+)(:([\d]+))?$")
    LOG.debug("Connecting to %s", opts.server)
    x = regex.match(opts.server)
    if not x:
        raise Exception("Bad address syntax: %s" % opts.server)
    matches = x.groups()
    host = matches[0]
    port = int(matches[2]) if matches[2] else None
    addr = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
    if not addr:
        raise Exception("Could not translate address '%s'" % opts.server)
    my_socket = socket.socket(addr[0][0], addr[0][1], addr[0][2])
    my_socket.setblocking(0)  # 0=non-blocking
    try:
        my_socket.connect(addr[0][4])
    except socket.error as e:
        if e.errno != errno.EINPROGRESS:
            raise

    # create AMQP container, connection, sender and receiver
    #
    container = pyngus.Container(uuid.uuid4().hex)
    conn_properties = {}
    if opts.trace:
        conn_properties["x-trace-protocol"] = True
    if opts.ca:
        conn_properties["x-ssl-ca-file"] = opts.ca

    my_connection = MyConnection("to-server", container, conn_properties)

    # Create the RPC caller
    method = {'method': method_info[0],
              'args': dict([(method_info[i], method_info[i + 1])
                            for i in range(1, len(method_info), 2)])}
    my_caller = my_connection.create_caller(method,
                                            "my-source-address",
                                            "my-target-address",
                                            receiver_properties={},
                                            sender_properties={})
    try:
        my_connection.connect(my_socket)
        repeat = 0
        while opts.repeat == 0 or repeat < opts.repeat:

            LOG.debug("Requesting RPC...")

            my_caller.connect()
            while not my_caller.done():
                my_connection.process()

            LOG.debug("RPC completed!  Closing caller...")

            my_caller.close()

            while not my_caller.closed():
                my_connection.process()

            LOG.debug("Caller closed cleanly!")

            repeat += 1

        print("Closing connections")
        my_connection.close()
        while not my_connection.closed:
            my_connection.process()

        LOG.debug("Connection closed")
        my_caller.destroy()
        my_connection.destroy()
    except Exception as e:
        LOG.error("Unexpected exception occured: %s", str(e))
        return -1

    return 0

if __name__ == "__main__":
    sys.exit(main())


#    Licensed to the Apache Software Foundation (ASF) under one
#    or more contributor license agreements.  See the NOTICE file
#    distributed with this work for additional information
#    regarding copyright ownership.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
"""helper methods that provide boilerplate socket I/O and Connection
    processing.
"""

__all__ = [
    "read_socket_input",
    "write_socket_output"
]

import errno
import logging
import socket

from pyngus.connection import Connection

LOG = logging.getLogger(__name__)

def read_socket_input(connection, socket_obj):
    """Read from the network layer and processes all data read.  Can
    support both blocking and non-blocking sockets.
    Returns the number of input bytes processed, or EOS if input processing
    is done.  Any exceptions raised by the socket are re-raised.
    """
    count = connection.needs_input
    if count <= 0:
        return count  # 0 or EOS

    while True:
        try:
            sock_data = socket_obj.recv(count)
            break
        except socket.timeout as e:
            LOG.debug("Socket timeout exception %s", str(e))
            raise  # caller must handle
        except socket.error as e:
            err = e.errno
            if err in [errno.EAGAIN,
                       errno.EWOULDBLOCK,
                       errno.EINTR]:
                # try again later
                return 0
            # otherwise, unrecoverable, caller must handle
            LOG.debug("Socket error exception %s", str(e))
            raise
        except Exception as e:  # beats me... assume fatal
            LOG.debug("unknown socket exception %s", str(e))
            raise  # caller must handle

    if len(sock_data) > 0:
        count = connection.process_input(sock_data)
    else:
        LOG.debug("Socket closed")
        count = Connection.EOS
        connection.close_input()
        connection.close_output()
    return count

def write_socket_output(connection, socket_obj):
    """Write data to the network layer.  Can support both blocking and
    non-blocking sockets.
    Returns the number of output bytes sent, or EOS if output processing
    is done.  Any exceptions raised by the socket are re-raised.
    """
    count = connection.has_output
    if count <= 0:
        return count  # 0 or EOS

    data = connection.output_data()
    if not data:
        # error - has_output > 0, but no data?
        return Connection.EOS

    while True:
        try:
            count = socket_obj.send(data)
            break
        except socket.timeout as e:
            LOG.debug("Socket timeout exception %s", str(e))
            raise  # caller must handle
        except socket.error as e:
            err = e.errno
            if err in [errno.EAGAIN,
                       errno.EWOULDBLOCK,
                       errno.EINTR]:
                # try again later
                return 0
            # else assume fatal let caller handle it:
            LOG.debug("Socket error exception %s", str(e))
            raise
        except Exception as e:  # beats me... assume fatal
            LOG.debug("unknown socket exception %s", str(e))
            raise

    if count > 0:
        connection.output_written(count)
    elif data:
        LOG.debug("Socket closed")
        count = Connection.EOS
        connection.close_output()
        connection.close_input()
    return count