<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

""" Analyze the type of error made (which symbols get confused?)."""

import logging
import sys
import os
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
                    level=logging.DEBUG,
                    stream=sys.stdout)
import yaml
import subprocess
import time
from collections import defaultdict
# mine
import utils
import csv
import itertools

from collections import OrderedDict, Callable

class DefaultOrderedDict(OrderedDict):
    # Source: http://stackoverflow.com/a/6190500/562769
    def __init__(self, default_factory=None, *a, **kw):
        if (default_factory is not None and
           not isinstance(default_factory, Callable)):
            raise TypeError('first argument must be callable')
        OrderedDict.__init__(self, *a, **kw)
        self.default_factory = default_factory

    def __getitem__(self, key):
        try:
            return OrderedDict.__getitem__(self, key)
        except KeyError:
            return self.__missing__(key)

    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        self[key] = value = self.default_factory()
        return value

    def __reduce__(self):
        if self.default_factory is None:
            args = tuple()
        else:
            args = self.default_factory,
        return type(self), args, None, None, self.items()

    def copy(self):
        return self.__copy__()

    def __copy__(self):
        return type(self)(self.default_factory, self)

    def __deepcopy__(self, memo):
        import copy
        return type(self)(self.default_factory,
                          copy.deepcopy(self.items()))

    def __repr__(self):
        return 'OrderedDefaultDict(%s, %s)' % (self.default_factory,
                                               OrderedDict.__repr__(self))

def get_test_results(model_folder, basename, test_file):
    model_src = utils.get_latest_model(model_folder, basename)
    if model_src is None:
        logging.error("No model with basename '%s' found in '%s'.",
                      basename,
                      model_folder)
    else:
        PROJECT_ROOT = utils.get_project_root()
        time_prefix = time.strftime("%Y-%m-%d-%H-%M")
        logging.info("Evaluate '%s'...", model_src)
        logfile = os.path.join(PROJECT_ROOT,
                               "archive/logs/%s-error-evaluation.log" %
                               time_prefix)
        with open(logfile, "w") as log, open(model_src, "r") as model_src_p:
            p = subprocess.Popen(['nntoolkit', 'run',
                                  '--batch-size', '1', '-f%i', test_file],
                                 stdin=model_src_p,
                                 stdout=log)
            ret = p.wait()
            if ret != 0:
                logging.error("nntoolkit finished with ret code %s", str(ret))
                sys.exit()
        return logfile
        # Get the error
        #with open(logfile) as f:
        #    log_content = f.read()
        #pattern = re.compile("errors = (\d\.\d+)")
        #error = float(pattern.findall(log_content)[-1])
        #return error  # TODO: Adjust

def make_all(tuplelist):
    t = []
    for confusiongroup in tuplelist:
        for x, y in itertools.permutations(confusiongroup, 2):
            t.append((x, y))
    return t

def create_report(true_data, eval_data, index2latex):
    # Gather data
    correct = []
    wrong = []
    confusing = [('\perp', '\\bot'),
                 ('\Sigma', '\sum'),
                 ('\Pi', '\prod', '\sqcap'),
                 ('\coprod', '\\amalg', '\\sqcup'),
                 ('\models', '\\vDash'), ('\mid', '|'),
                 ('\Delta', '\\triangle', '\\vartriangle'),
                 ('\parallel', '\|'),
                 ('\ohm', '\Omega'),
                 ('\\backslash', '\setminus'),
                 ('\checked', '\checkmark'),
                 ('\with', '\&'),
                 ('\sharp', '\#'),
                 ('\mathsection', '\S'),
                 ('\lhd', '\\triangleleft', '\\vartriangleleft'),
                 ('\\varoiint', '\\oiint'),
                 ('\mathbb{R}', '\mathds{R}')]
    understandable = [('\\alpha', '\propto', '\\varpropto'),
                      ('\epsilon', '\in'),
                      ('\Lambda', '\wedge'),
                      ('\emptyset', '\O', '\diameter', '\o', '\\varnothing'),
                      ('\mathcal{E}', '\\varepsilon', '\epsilon'),
                      ('o', 'O', '0', '\circ', '\degree', '\\fullmoon', '\mathcal{O}'),
                      ('\\rightarrow', '\longrightarrow', '\shortrightarrow'),
                      ('\mathbb{1}', '\mathds{1}'),
                      ('\geqslant', '\geq'),
                      ('\leqslant', '\leq'),
                      ('\Pi', '\pi', '\prod'),
                      ('\psi', '\Psi'),
                      ('\phi', '\Phi'),
                      ('\odot', '\\astrosun'),
                      ('\\bullet', '\cdot'),
                      ('\\theta', '\Theta'),
                      ('\Longrightarrow', '\Rightarrow'),
                      ('\Longleftrightarrow', '\Leftrightarrow'),
                      ('\longmapsto', '\mapsto'),
                      ('\mathscr{L}', '\mathcal{L}'),
                      ('\phi', '\emptyset'),
                      ('\\beta', '\ss'),
                      ('\male', '\mars'),
                      ('\\female', '\\venus'),
                      ('\\triangledown', '\\nabla'),
                      ('\\mathdsz', '\\nabla'),
                      ('\Bowtie', '\\bowtie'),
                      ('\\rho', '\\varrho'),
                      ('\\mathds{Q}', '\\mathbb{Q}'),
                      ('\\mathds{Z}', '\\mathbb{Z}', '\\mathcal{Z}'),
                      ('\diamond', '\diamondsuit', '\lozenge'),
                      ('\dots', '\dotsc', '\dotsc', '\cdot'),
                      ('\mathcal{T}', '\\tau'),
                      ('\mathcal{Adjust}', '\mathscr{A}', 'A'),
                      ('\mathcal{D}', '\mathscr{D}', 'D'),
                      ('\mathcal{N}', '\mathscr{N}', 'N'),
                      ('\mathcal{R}', '\mathscr{R}', 'R'),
                      ('\succeq', '\geq'),
                      ('\mathcal{C}', 'C'),
                      ('x', '\\times'),
                      ('\\alpha', '\\ltimes'),
                      ('\\chi', '\\mathcal{X}')]
    confusing = make_all(confusing) + make_all(understandable)
    for known, evaluated in zip(true_data, eval_data):
        if known['index'] == evaluated:
            correct.append(known)
        elif (index2latex[known['index']],
              index2latex[evaluated]) in confusing:  # Some confusions are ok!
            correct.append(known)
        else:
            formula_id = index2latex[evaluated]
            known['confused'] = formula_id  # That's an index!
            wrong.append(known)
    classification_error = (len(wrong) / float(len(wrong) + len(correct)))
    logging.info("Classification error: %0.4f (%i wrong)" %
                 (classification_error, len(wrong)))

    # Get the data
    errors_by_correct_classification = DefaultOrderedDict(list)
    errors_by_wrong_classification = DefaultOrderedDict(list)
    for el in wrong:
        errors_by_correct_classification[el['latex']].append(el)
        errors_by_wrong_classification[el['confused']].append(el)

    # Sort errors_by_correct_classification
    errors_by_correct_classification = OrderedDict(sorted(errors_by_correct_classification.iteritems(),
                                                   key=lambda n: len(n[1]),
                                                   reverse=True))
    for key in errors_by_correct_classification:
        errors_by_correct_classification[key] = sorted(errors_by_correct_classification[key],
                                                       key=lambda n: n['confused'])
    # Sort errors_by_wrong_classification
    errors_by_wrong_classification = OrderedDict(sorted(errors_by_wrong_classification.iteritems(),
                                                 key=lambda n: len(n[1]),
                                                 reverse=True))
    for key in errors_by_wrong_classification:
        errors_by_wrong_classification[key] = sorted(errors_by_wrong_classification[key],
                                                     key=lambda n: n['latex'])

    # Get the tempalte
    PROJECT_ROOT = utils.get_project_root()
    template = os.path.join(PROJECT_ROOT,
                            "tools/templates/classification-error-report.html")
    with open(template) as f:
        template = f.read()

    # Find right place for report file
    time_prefix = time.strftime("%Y-%m-%d-%H-%M")
    target = os.path.join(PROJECT_ROOT,
                          ("archive/reports/"
                           "%s-classification-error-report.html") %
                          time_prefix)
    # Fill the template
    from jinja2 import FileSystemLoader
    from jinja2.environment import Environment
    env = Environment()
    env.loader = FileSystemLoader('templates/')
    t = env.get_template('classification-error-report.html')
    rendered = t.render(wrong=wrong, correct=correct,
                        classification_error=classification_error,
                        errors_by_correct_classification=
                        errors_by_correct_classification,
                        errors_by_wrong_classification=
                        errors_by_wrong_classification)
    with open(target, "w") as f:
        f.write(rendered)

def analyze_results(translation_csv, what_evaluated_file, evaluation_file):
    index2latex = {}
    with open(translation_csv) as csvfile:
        spamreader = csv.DictReader(csvfile, delimiter=',', quotechar='"')
        for row in spamreader:
            index2latex[int(row['index'])] = row['latex']
    with open(evaluation_file) as f:
        eval_data = map(int, f.readlines())  # Has no heading
    true_data = []
    with open(what_evaluated_file) as csvfile:
        spamreader = csv.DictReader(csvfile, delimiter=',', quotechar='"')
        for row in spamreader:
            row['index'] = int(row['index'])
            true_data.append(row)

    create_report(true_data, eval_data, index2latex)

def main(model_description_file, translation_file):
    PROJECT_ROOT = utils.get_project_root()
    # Read the model description file
    with open(model_description_file, 'r') as ymlfile:
        model_description = yaml.load(ymlfile)
    model_folder = os.path.join(PROJECT_ROOT,
                                model_description['model']['folder'])
    test_data_path = os.path.join(PROJECT_ROOT,
                                  model_description['data']['testing'])
    evaluation_file = get_test_results(model_folder,
                                       model_description['model']['basename'],
                                       test_data_path)
    translation_csv = os.path.join(PROJECT_ROOT,
                                   "archive/logs/index2formula_id.csv")
    a = os.path.join(PROJECT_ROOT, "archive/logs")
    b = "testdata"
    what_evaluated_file = os.path.join(PROJECT_ROOT,
                                       "%s/translation-%s.csv" % (a, b))
    analyze_results(translation_csv, what_evaluated_file, evaluation_file)

def is_valid_file(parser, arg):
    """Check if arg is a valid file that already exists on the file
       system.
    """
    arg = os.path.abspath(arg)
    if not os.path.exists(arg):
        parser.error("The file %s does not exist!" % arg)
    else:
        return arg

if __name__ == "__main__":
    PROJECT_ROOT = utils.get_project_root()

    # Get latest model description file
    models_folder = os.path.join(PROJECT_ROOT, "archive/models")
    latest_model = utils.get_latest_in_folder(models_folder, ".yml")

    from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
    parser = ArgumentParser(description=__doc__,
                            formatter_class=ArgumentDefaultsHelpFormatter)
    parser.add_argument("-m", "--model_description_file",
                        dest="model_description_file",
                        help="where is the model description YAML file?",
                        metavar="FILE",
                        type=lambda x: utils.is_valid_file(parser, x),
                        default=latest_model)
    parser.add_argument("-t", "--translation_file",
                        dest="translation_file",
                        help="index2formula_id.csv - where is it?",
                        metavar="FILE",
                        type=lambda x: utils.is_valid_file(parser, x),
                        default=latest_model)
    args = parser.parse_args()
    main(args.model_description_file, args.translation_file)
<commit_msg>analyze-errors: Allow choice of set; adjusted confusing / understandable sets<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

""" Analyze the type of error made (which symbols get confused?)."""

import logging
import sys
import os
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
                    level=logging.DEBUG,
                    stream=sys.stdout)
import yaml
import subprocess
import time
from collections import defaultdict
# mine
import utils
import csv
import itertools

from collections import OrderedDict, Callable

class DefaultOrderedDict(OrderedDict):
    # Source: http://stackoverflow.com/a/6190500/562769
    def __init__(self, default_factory=None, *a, **kw):
        if (default_factory is not None and
           not isinstance(default_factory, Callable)):
            raise TypeError('first argument must be callable')
        OrderedDict.__init__(self, *a, **kw)
        self.default_factory = default_factory

    def __getitem__(self, key):
        try:
            return OrderedDict.__getitem__(self, key)
        except KeyError:
            return self.__missing__(key)

    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        self[key] = value = self.default_factory()
        return value

    def __reduce__(self):
        if self.default_factory is None:
            args = tuple()
        else:
            args = self.default_factory,
        return type(self), args, None, None, self.items()

    def copy(self):
        return self.__copy__()

    def __copy__(self):
        return type(self)(self.default_factory, self)

    def __deepcopy__(self, memo):
        import copy
        return type(self)(self.default_factory,
                          copy.deepcopy(self.items()))

    def __repr__(self):
        return 'OrderedDefaultDict(%s, %s)' % (self.default_factory,
                                               OrderedDict.__repr__(self))

def get_test_results(model_folder, basename, test_file):
    model_src = utils.get_latest_model(model_folder, basename)
    if model_src is None:
        logging.error("No model with basename '%s' found in '%s'.",
                      basename,
                      model_folder)
    else:
        PROJECT_ROOT = utils.get_project_root()
        time_prefix = time.strftime("%Y-%m-%d-%H-%M")
        logging.info("Evaluate '%s'...", model_src)
        logfile = os.path.join(PROJECT_ROOT,
                               "archive/logs/%s-error-evaluation.log" %
                               time_prefix)
        with open(logfile, "w") as log, open(model_src, "r") as model_src_p:
            p = subprocess.Popen(['nntoolkit', 'run',
                                  '--batch-size', '1', '-f%i', test_file],
                                 stdin=model_src_p,
                                 stdout=log)
            ret = p.wait()
            if ret != 0:
                logging.error("nntoolkit finished with ret code %s", str(ret))
                sys.exit()
        return logfile
        # Get the error
        #with open(logfile) as f:
        #    log_content = f.read()
        #pattern = re.compile("errors = (\d\.\d+)")
        #error = float(pattern.findall(log_content)[-1])
        #return error  # TODO: Adjust

def make_all(tuplelist):
    t = []
    for confusiongroup in tuplelist:
        for x, y in itertools.permutations(confusiongroup, 2):
            t.append((x, y))
    return t

def create_report(true_data, eval_data, index2latex):
    # Gather data
    correct = []
    wrong = []
    confusing = [('\sum', '\Sigma'),
                 ('\prod', '\Pi', '\sqcap'),
                 ('\coprod', '\\amalg', '\\sqcup'),
                 ('\perp', '\\bot'),
                 ('\models', '\\vDash'),
                 ('|', '\mid'),
                 ('\Delta', '\\triangle', '\\vartriangle'),
                 ('\|', '\parallel'),
                 ('\ohm', '\Omega'),
                 ('\setminus', '\\backslash'),
                 ('\checked', '\checkmark'),
                 ('\&', '\with'),
                 ('\#', '\sharp'),
                 ('\S', '\mathsection'),
                 ('\\nabla', '\\triangledown'),
                 ('\lhd', '\\triangleleft', '\\vartriangleleft'),
                 ('\\oint', '\\varoint'),
                 ('\\oiint', '\\varoiint'),
                 ('\mathbb{R}', '\mathds{R}'),
                 ('\mathcal{A}', '\mathscr{A}'),
                 ('\mathcal{D}', '\mathscr{D}'),
                 ('\mathcal{N}', '\mathscr{N}'),
                 ('\mathcal{R}', '\mathscr{R}'),
                 ('\propto', '\\varpropto')]
    understandable = [('\\alpha', '\propto', '\\varpropto', '\ltimes'),
                      ('0', 'O', 'o', '\circ', '\degree', '\\fullmoon', '\mathcal{O}'),
                      ('\epsilon', '\\varepsilon', '\in', '\mathcal{E}'),
                      ('\Lambda', '\wedge'),
                      ('\emptyset', '\O', '\o', '\diameter', '\\varnothing'),
                      ('\\rightarrow', '\longrightarrow', '\shortrightarrow'),
                      ('\Rightarrow', '\Longrightarrow'),
                      ('\Leftrightarrow', '\Longleftrightarrow'),
                      ('\mapsto', '\longmapsto'),
                      ('\mathbb{1}', '\mathds{1}'),
                      ('\mathscr{L}', '\mathcal{L}'),
                      ('\\mathbb{Q}', '\\mathds{Q}'),
                      ('\\mathbb{Z}', '\\mathds{Z}', '\\mathcal{Z}'),
                      ('\geq', '\geqslant', '\succeq'),
                      ('\leq', '\leqslant'),
                      ('\Pi', '\pi', '\prod'),
                      ('\psi', '\Psi'),
                      ('\phi', '\Phi', '\emptyset'),
                      ('\\rho', '\\varrho'),
                      ('\\theta', '\Theta'),
                      ('\odot', '\\astrosun'),
                      ('\cdot', '\\bullet'),
                      ('\\beta', '\ss'),
                      ('\male', '\mars'),
                      ('\\female', '\\venus'),
                      ('\Bowtie', '\\bowtie'),
                      ('\diamond', '\diamondsuit', '\lozenge'),
                      ('\dots', '\dotsc'),
                      ('\mathcal{T}', '\\tau'),
                      ('\mathcal{C}', 'C'),
                      ('x', '\\times', 'X', '\\chi', '\\mathcal{X}')]
    confusing = make_all(confusing) + make_all(understandable)
    # confusing = []
    for known, evaluated in zip(true_data, eval_data):
        if known['index'] == evaluated:
            correct.append(known)
        elif (index2latex[known['index']],
              index2latex[evaluated]) in confusing:  # Some confusions are ok!
            correct.append(known)
        else:
            formula_id = index2latex[evaluated]
            known['confused'] = formula_id  # That's an index!
            wrong.append(known)
    classification_error = (len(wrong) / float(len(wrong) + len(correct)))
    logging.info("Classification error: %0.4f (%i wrong)" %
                 (classification_error, len(wrong)))

    # Get the data
    errors_by_correct_classification = DefaultOrderedDict(list)
    errors_by_wrong_classification = DefaultOrderedDict(list)
    for el in wrong:
        errors_by_correct_classification[el['latex']].append(el)
        errors_by_wrong_classification[el['confused']].append(el)

    # Sort errors_by_correct_classification
    errors_by_correct_classification = OrderedDict(sorted(errors_by_correct_classification.iteritems(),
                                                   key=lambda n: len(n[1]),
                                                   reverse=True))
    for key in errors_by_correct_classification:
        errors_by_correct_classification[key] = sorted(errors_by_correct_classification[key],
                                                       key=lambda n: n['confused'])
    # Sort errors_by_wrong_classification
    errors_by_wrong_classification = OrderedDict(sorted(errors_by_wrong_classification.iteritems(),
                                                 key=lambda n: len(n[1]),
                                                 reverse=True))
    for key in errors_by_wrong_classification:
        errors_by_wrong_classification[key] = sorted(errors_by_wrong_classification[key],
                                                     key=lambda n: n['latex'])

    # Get the tempalte
    PROJECT_ROOT = utils.get_project_root()
    template = os.path.join(PROJECT_ROOT,
                            "tools/templates/classification-error-report.html")
    with open(template) as f:
        template = f.read()

    # Find right place for report file
    time_prefix = time.strftime("%Y-%m-%d-%H-%M")
    target = os.path.join(PROJECT_ROOT,
                          ("archive/reports/"
                           "%s-classification-error-report.html") %
                          time_prefix)
    # Fill the template
    from jinja2 import FileSystemLoader
    from jinja2.environment import Environment
    env = Environment()
    env.loader = FileSystemLoader('templates/')
    t = env.get_template('classification-error-report.html')
    rendered = t.render(wrong=wrong, correct=correct,
                        classification_error=classification_error,
                        errors_by_correct_classification=
                        errors_by_correct_classification,
                        errors_by_wrong_classification=
                        errors_by_wrong_classification)
    with open(target, "w") as f:
        f.write(rendered)

def analyze_results(translation_csv, what_evaluated_file, evaluation_file):
    index2latex = {}
    with open(translation_csv) as csvfile:
        spamreader = csv.DictReader(csvfile, delimiter=',', quotechar='"')
        for row in spamreader:
            index2latex[int(row['index'])] = row['latex']
    with open(evaluation_file) as f:
        eval_data = map(int, f.readlines())  # Has no heading
    true_data = []
    with open(what_evaluated_file) as csvfile:
        spamreader = csv.DictReader(csvfile, delimiter=',', quotechar='"')
        for row in spamreader:
            row['index'] = int(row['index'])
            true_data.append(row)

    create_report(true_data, eval_data, index2latex)

def main(model_description_file, translation_file, aset='test'):
    PROJECT_ROOT = utils.get_project_root()
    # Read the model description file
    with open(model_description_file, 'r') as ymlfile:
        model_description = yaml.load(ymlfile)
    model_folder = os.path.join(PROJECT_ROOT,
                                model_description['model']['folder'])
    if aset == 'test':
        key_model, key_file = 'testing', 'testdata'
    elif aset == 'valid':
        key_model, key_file = 'validating', 'validdata'
    else:
        key_model, key_file = 'training', 'traindata'
    test_data_path = os.path.join(PROJECT_ROOT,
                                  model_description['data'][key_model])
    evaluation_file = get_test_results(model_folder,
                                       model_description['model']['basename'],
                                       test_data_path)
    translation_csv = os.path.join(PROJECT_ROOT,
                                   "archive/logs/index2formula_id.csv")
    a = os.path.join(PROJECT_ROOT, "archive/logs")
    what_evaluated_file = os.path.join(PROJECT_ROOT,
                                       "%s/translation-%s.csv" % (a, key_file))
    analyze_results(translation_csv, what_evaluated_file, evaluation_file)

def is_valid_file(parser, arg):
    """Check if arg is a valid file that already exists on the file
       system.
    """
    arg = os.path.abspath(arg)
    if not os.path.exists(arg):
        parser.error("The file %s does not exist!" % arg)
    else:
        return arg

if __name__ == "__main__":
    PROJECT_ROOT = utils.get_project_root()

    # Get latest model description file
    models_folder = os.path.join(PROJECT_ROOT, "archive/models")
    latest_model = utils.get_latest_in_folder(models_folder, ".yml")

    from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
    parser = ArgumentParser(description=__doc__,
                            formatter_class=ArgumentDefaultsHelpFormatter)
    parser.add_argument("-m", "--model_description_file",
                        dest="model_description_file",
                        help="where is the model description YAML file?",
                        metavar="FILE",
                        type=lambda x: utils.is_valid_file(parser, x),
                        default=latest_model)
    parser.add_argument("-t", "--translation_file",
                        dest="translation_file",
                        help="index2formula_id.csv - where is it?",
                        metavar="FILE",
                        type=lambda x: utils.is_valid_file(parser, x),
                        default=latest_model)
    parser.add_argument("-s", "--set",
                        dest="aset",
                        choices=['test', 'train', 'valid'],
                        help="which set should get analyzed?",
                        default='test')
    args = parser.parse_args()
    main(args.model_description_file, args.translation_file, args.aset)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
'''
Service support for Debian systems (uses update-rc.d and /sbin/service)
'''

# Import python libs
import glob
import re

# Import salt libs
from .systemd import _sd_booted

__func_alias__ = {
    'reload_': 'reload'
}

def __virtual__():
    '''
    Only work on Debian and when systemd isn't running
    '''
    if __grains__['os'] == 'Debian' and not _sd_booted():
        return 'service'
    return False

def _get_runlevel():
    '''
    returns the current runlevel
    '''
    out = __salt__['cmd.run']('runlevel')
    # unknown can be returned while inside a container environment, since
    # this is due to a lack of init, it should be safe to assume runlevel
    # 2, which is Debian's default. If not, all service related states
    # will throw an out of range exception here which will cause
    # other functions to fail.
    if 'unknown' in out:
        return '2'
    else:
        return out.split()[1]

def get_enabled():
    '''
    Return a list of service that are enabled on boot

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_enabled
    '''
    prefix = '/etc/rc[S{0}].d/S'.format(_get_runlevel())
    ret = set()
    lines = glob.glob('{0}*'.format(prefix))
    for line in lines:
        ret.add(re.split(prefix + r'\d+', line)[1])
    return sorted(ret)

def get_disabled():
    '''
    Return a set of services that are installed but disabled

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_disabled
    '''
    return sorted(set(get_all()) - set(get_enabled()))

def get_all():
    '''
    Return all available boot services

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_all
    '''
    ret = set()
    lines = glob.glob('/etc/init.d/*')
    for line in lines:
        service = line.split('/etc/init.d/')[1]
        # Remove README.  If it's an enabled service, it will be added back in.
        if service != 'README':
            ret.add(service)
    return sorted(ret | set(get_enabled()))

def start(name):
    '''
    Start the specified service

    CLI Example:

    .. code-block:: bash

        salt '*' service.start <service name>
    '''
    cmd = 'service {0} start'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def stop(name):
    '''
    Stop the specified service

    CLI Example:

    .. code-block:: bash

        salt '*' service.stop <service name>
    '''
    cmd = 'service {0} stop'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def restart(name):
    '''
    Restart the named service

    CLI Example:

    .. code-block:: bash

        salt '*' service.restart <service name>
    '''
    cmd = 'service {0} restart'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def reload_(name):
    '''
    Reload the named service

    CLI Example:

    .. code-block:: bash

        salt '*' service.reload <service name>
    '''
    cmd = 'service {0} reload'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def force_reload(name):
    '''
    Force-reload the named service

    CLI Example:

    .. code-block:: bash

        salt '*' service.force_reload <service name>
    '''
    cmd = 'service {0} force-reload'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def status(name, sig=None):
    '''
    Return the status for a service, pass a signature to use to find
    the service via ps

    CLI Example:

    .. code-block:: bash

        salt '*' service.status <service name>
    '''
    if sig:
        return bool(__salt__['status.pid'](sig))
    cmd = 'service {0} status'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def enable(name, **kwargs):
    '''
    Enable the named service to start at boot

    CLI Example:

    .. code-block:: bash

        salt '*' service.enable <service name>
    '''
    cmd = 'update-rc.d {0} enable'.format(name)
    osmajor = __grains__['osrelease'].split('.')[0]
    if int(osmajor) >= 6:
        cmd = 'insserv {0} && '.format(name) + cmd
    return not __salt__['cmd.retcode'](cmd)

def disable(name, **kwargs):
    '''
    Disable the named service to start at boot

    CLI Example:

    .. code-block:: bash

        salt '*' service.disable <service name>
    '''
    cmd = 'update-rc.d {0} disable'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def enabled(name):
    '''
    Return True if the named service is enabled, false otherwise

    CLI Example:

    .. code-block:: bash

        salt '*' service.enabled <service name>
    '''
    return name in get_enabled()

def disabled(name):
    '''
    Return True if the named service is enabled, false otherwise

    CLI Example:

    .. code-block:: bash

        salt '*' service.disabled <service name>
    '''
    return name in get_disabled()
<commit_msg>Use debian_service as service provider for Raspbian<commit_after># -*- coding: utf-8 -*-
'''
Service support for Debian systems (uses update-rc.d and /sbin/service)
'''

# Import python libs
import glob
import re

# Import salt libs
from .systemd import _sd_booted

__func_alias__ = {
    'reload_': 'reload'
}

def __virtual__():
    '''
    Only work on Debian and when systemd isn't running
    '''
    if __grains__['os'] in ('Debian', 'Raspbian') and not _sd_booted():
        return 'service'
    return False

def _get_runlevel():
    '''
    returns the current runlevel
    '''
    out = __salt__['cmd.run']('runlevel')
    # unknown can be returned while inside a container environment, since
    # this is due to a lack of init, it should be safe to assume runlevel
    # 2, which is Debian's default. If not, all service related states
    # will throw an out of range exception here which will cause
    # other functions to fail.
    if 'unknown' in out:
        return '2'
    else:
        return out.split()[1]

def get_enabled():
    '''
    Return a list of service that are enabled on boot

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_enabled
    '''
    prefix = '/etc/rc[S{0}].d/S'.format(_get_runlevel())
    ret = set()
    lines = glob.glob('{0}*'.format(prefix))
    for line in lines:
        ret.add(re.split(prefix + r'\d+', line)[1])
    return sorted(ret)

def get_disabled():
    '''
    Return a set of services that are installed but disabled

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_disabled
    '''
    return sorted(set(get_all()) - set(get_enabled()))

def get_all():
    '''
    Return all available boot services

    CLI Example:

    .. code-block:: bash

        salt '*' service.get_all
    '''
    ret = set()
    lines = glob.glob('/etc/init.d/*')
    for line in lines:
        service = line.split('/etc/init.d/')[1]
        # Remove README.  If it's an enabled service, it will be added back in.
        if service != 'README':
            ret.add(service)
    return sorted(ret | set(get_enabled()))

def start(name):
    '''
    Start the specified service

    CLI Example:

    .. code-block:: bash

        salt '*' service.start <service name>
    '''
    cmd = 'service {0} start'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def stop(name):
    '''
    Stop the specified service

    CLI Example:

    .. code-block:: bash

        salt '*' service.stop <service name>
    '''
    cmd = 'service {0} stop'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def restart(name):
    '''
    Restart the named service

    CLI Example:

    .. code-block:: bash

        salt '*' service.restart <service name>
    '''
    cmd = 'service {0} restart'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def reload_(name):
    '''
    Reload the named service

    CLI Example:

    .. code-block:: bash

        salt '*' service.reload <service name>
    '''
    cmd = 'service {0} reload'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def force_reload(name):
    '''
    Force-reload the named service

    CLI Example:

    .. code-block:: bash

        salt '*' service.force_reload <service name>
    '''
    cmd = 'service {0} force-reload'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def status(name, sig=None):
    '''
    Return the status for a service, pass a signature to use to find
    the service via ps

    CLI Example:

    .. code-block:: bash

        salt '*' service.status <service name>
    '''
    if sig:
        return bool(__salt__['status.pid'](sig))
    cmd = 'service {0} status'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def enable(name, **kwargs):
    '''
    Enable the named service to start at boot

    CLI Example:

    .. code-block:: bash

        salt '*' service.enable <service name>
    '''
    cmd = 'update-rc.d {0} enable'.format(name)
    osmajor = __grains__['osrelease'].split('.')[0]
    if int(osmajor) >= 6:
        cmd = 'insserv {0} && '.format(name) + cmd
    return not __salt__['cmd.retcode'](cmd)

def disable(name, **kwargs):
    '''
    Disable the named service to start at boot

    CLI Example:

    .. code-block:: bash

        salt '*' service.disable <service name>
    '''
    cmd = 'update-rc.d {0} disable'.format(name)
    return not __salt__['cmd.retcode'](cmd)

def enabled(name):
    '''
    Return True if the named service is enabled, false otherwise

    CLI Example:

    .. code-block:: bash

        salt '*' service.enabled <service name>
    '''
    return name in get_enabled()

def disabled(name):
    '''
    Return True if the named service is enabled, false otherwise

    CLI Example:

    .. code-block:: bash

        salt '*' service.disabled <service name>
    '''
    return name in get_disabled()
<|endoftext|>


<commit_before>from django.conf.urls import url, patterns, include
from django.contrib import admin
from django.conf import settings
from django.views.generic.base import TemplateView

from tastypie.api import Api

from readthedocs.api.base import (ProjectResource, UserResource, BuildResource,
                      VersionResource, FileResource)
from readthedocs.builds.filters import VersionFilter
from readthedocs.builds.version_slug import VERSION_SLUG_REGEX
from readthedocs.core.views import HomepageView, SearchView
from readthedocs.projects.feeds import LatestProjectsFeed, NewProjectsFeed
from readthedocs.projects.filters import ProjectFilter
from readthedocs.projects.constants import LANGUAGES_REGEX
from readthedocs.projects.constants import PROJECT_SLUG_REGEX

v1_api = Api(api_name='v1')
v1_api.register(BuildResource())
v1_api.register(UserResource())
v1_api.register(ProjectResource())
v1_api.register(VersionResource())
v1_api.register(FileResource())

admin.autodiscover()

handler500 = 'readthedocs.core.views.server_error'
handler404 = 'readthedocs.core.views.server_error_404'

pattern_opts = {
    'project_slug': PROJECT_SLUG_REGEX,
    'lang_slug': LANGUAGES_REGEX,
    'version_slug': VERSION_SLUG_REGEX,
    'filename_slug': '(?:.*)',
}

urlpatterns = patterns(
    '',  # base view, flake8 complains if it is on the previous line.
    url(r'^$', HomepageView.as_view(), name='homepage'),
    url(r'^security/', TemplateView.as_view(template_name='security.html')),

    # For serving docs locally and when nginx isn't
    url((r'^docs/(?P<project_slug>{project_slug})/(?P<lang_slug>{lang_slug})/'
         r'(?P<version_slug>{version_slug})/'
         r'(?P<filename>{filename_slug})$'.format(**pattern_opts)),
        'readthedocs.core.views.serve_docs',
        name='docs_detail'),

    # Redirect to default version, if only lang_slug is set.
    url((r'^docs/(?P<project_slug>{project_slug})/'
         r'(?P<lang_slug>{lang_slug})/$'.format(**pattern_opts)),
        'readthedocs.core.views.redirect_lang_slug',
        name='docs_detail'),

    # Redirect to default version, if only version_slug is set.
    url((r'^docs/(?P<project_slug>{project_slug})/'
         r'(?P<version_slug>{version_slug})/$'.format(**pattern_opts)),
        'readthedocs.core.views.redirect_version_slug',
        name='docs_detail'),

    # Redirect to default version.
    url(r'^docs/(?P<project_slug>{project_slug})/$'.format(**pattern_opts),
        'readthedocs.core.views.redirect_project_slug',
        name='docs_detail'),

    # Handle /page/<path> redirects for explicit "latest" version goodness.
    url((r'^docs/(?P<project_slug>{project_slug})/page/'
         r'(?P<filename>{filename_slug})$'.format(**pattern_opts)),
        'readthedocs.core.views.redirect_page_with_filename',
        name='docs_detail'),

    # Handle single version URLs
    url((r'^docs/(?P<project_slug>{project_slug})/'
         r'(?P<filename>{filename_slug})$'.format(**pattern_opts)),
        'readthedocs.core.views.serve_single_version_docs',
        name='docs_detail'),

    # Handle fallbacks
    url((r'^user_builds/(?P<project_slug>{project_slug})/rtd-builds/'
         r'(?P<version_slug>{version_slug})/'
         r'(?P<filename>{filename_slug})$'.format(**pattern_opts)),
        'readthedocs.core.views.server_helpful_404',
        name='user_buils_fallback'),
    url((r'^user_builds/(?P<project_slug>{project_slug})/translations/'
         r'(?P<lang_slug>{lang_slug})/(?P<version_slug>{version_slug})/'
         r'(?P<filename>{filename_slug})$'.format(**pattern_opts)),
        'readthedocs.core.views.server_helpful_404',
        name='user_builds_fallback_translations'),

    url(r'^i18n/', include('django.conf.urls.i18n')),
    url(r'^projects/', include('readthedocs.projects.urls.public')),
    url(r'^builds/', include('readthedocs.builds.urls')),
    url(r'^bookmarks/', include('readthedocs.bookmarks.urls')),
    # Ship elastic search
    url(r'^search/$', 'readthedocs.search.views.elastic_search', name='search'),
    url(r'^elasticsearch/$', 'readthedocs.search.views.elastic_search', name='elastic_search'),
    url(r'^search/project/', SearchView.as_view(), name='haystack_project'),
    #url(r'^search/', include('haystack.urls')),
    url(r'^admin/', include(admin.site.urls)),
    url(r'^dashboard/', include('readthedocs.projects.urls.private')),
    url(r'^github', 'readthedocs.core.views.github_build', name='github_build'),
    url(r'^gitlab', 'readthedocs.core.views.gitlab_build', name='gitlab_build'),
    url(r'^bitbucket', 'readthedocs.core.views.bitbucket_build', name='bitbucket_build'),
    url((r'^build/'
         r'(?P<project_id_or_slug>{project_slug})'.format(**pattern_opts)),
        'readthedocs.core.views.generic_build',
        name='generic_build'),
    url(r'^random/(?P<project_slug>{project_slug})'.format(**pattern_opts),
        'readthedocs.core.views.random_page',
        name='random_page'),
    url(r'^random/$', 'readthedocs.core.views.random_page', name='random_page'),
    url(r'^sustainability/', include('readthedocs.donate.urls')),
    url(r'^live/$', 'readthedocs.core.views.live_builds', name='live_builds'),
    url(r'^500/$', 'readthedocs.core.views.divide_by_zero', name='divide_by_zero'),
    url(r'^filter/version/$',
        'django_filters.views.object_filter',
        {'filter_class': VersionFilter, 'template_name': 'filter.html'},
        name='filter_version'),
    url(r'^filter/project/$',
        'django_filters.views.object_filter',
        {'filter_class': ProjectFilter, 'template_name': 'filter.html'},
        name='filter_project'),
    url((r'^wipe/(?P<project_slug>{project_slug})/'
         r'(?P<version_slug>{version_slug})/$'.format(**pattern_opts)),
        'readthedocs.core.views.wipe_version',
        name='wipe_version'),

    url(r'^websupport/', include('readthedocs.comments.urls')),
    url(r'^profiles/', include('readthedocs.profiles.urls.public')),
    url(r'^accounts/', include('readthedocs.profiles.urls.private')),
    url(r'^accounts/', include('allauth.urls')),
    url(r'^accounts/gold/', include('readthedocs.gold.urls')),
    url(r'^api/', include(v1_api.urls)),
    url(r'^api/v2/', include('readthedocs.restapi.urls')),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^feeds/new/$',
        NewProjectsFeed(),
        name="new_feed"),
    url(r'^feeds/latest/$',
        LatestProjectsFeed(),
        name="latest_feed"),
    url((r'^mlt/(?P<project_slug>{project_slug})/'
         r'(?P<filename>{filename_slug})$'.format(**pattern_opts)),
        'readthedocs.core.views.morelikethis',
        name='morelikethis'),

)

if settings.DEBUG:
    urlpatterns += patterns(
        '',  # base view, flake8 complains if it is on the previous line.
        url('style-catalog/$',
            TemplateView.as_view(template_name='style_catalog.html')),
        url(regex='^%s/(?P<path>.*)$' % settings.MEDIA_URL.strip('/'),
            view='django.views.static.serve',
            kwargs={'document_root': settings.MEDIA_ROOT}),
    )
<commit_msg>Big honking urls.py refactor<commit_after>from django.conf.urls import url, patterns, include
from django.contrib import admin
from django.conf import settings
from django.views.generic.base import TemplateView

from tastypie.api import Api

from readthedocs.api.base import (ProjectResource, UserResource, BuildResource,
                                  VersionResource, FileResource)
from readthedocs.core.views import HomepageView

from readthedocs.core.urls import docs_urls, core_urls, deprecated_urls

v1_api = Api(api_name='v1')
v1_api.register(BuildResource())
v1_api.register(UserResource())
v1_api.register(ProjectResource())
v1_api.register(VersionResource())
v1_api.register(FileResource())

admin.autodiscover()

handler500 = 'readthedocs.core.views.server_error'
handler404 = 'readthedocs.core.views.server_error_404'

urlpatterns = patterns(
    '',  # base view, flake8 complains if it is on the previous line.
    url(r'^$', HomepageView.as_view(), name='homepage'),
    url(r'^security/', TemplateView.as_view(template_name='security.html')),
)

rtd_urls = patterns(
    '',
    url(r'^projects/', include('readthedocs.projects.urls.public')),
    url(r'^bookmarks/', include('readthedocs.bookmarks.urls')),
    url(r'^search/$', 'readthedocs.search.views.elastic_search', name='search'),
    url(r'^dashboard/', include('readthedocs.projects.urls.private')),
    url(r'^profiles/', include('readthedocs.profiles.urls.public')),
    url(r'^accounts/', include('readthedocs.profiles.urls.private')),
    url(r'^accounts/', include('allauth.urls')),
    # For redirects
    url(r'^builds/', include('readthedocs.builds.urls')),
)

api_urls = patterns(
    '',
    url(r'^api/', include(v1_api.urls)),
    url(r'^api/v2/', include('readthedocs.restapi.urls')),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^websupport/', include('readthedocs.comments.urls')),
)

django_urls = patterns(
    '',
    url(r'^i18n/', include('django.conf.urls.i18n')),
    url(r'^admin/', include(admin.site.urls)),
)

money_urls = patterns(
    '',
    url(r'^sustainability/', include('readthedocs.donate.urls')),
    url(r'^accounts/gold/', include('readthedocs.gold.urls')),
)

urlpatterns += docs_urls
urlpatterns += rtd_urls
urlpatterns += api_urls
urlpatterns += core_urls
urlpatterns += django_urls
urlpatterns += money_urls
urlpatterns += deprecated_urls

if settings.DEBUG:
    urlpatterns += patterns(
        '',  # base view, flake8 complains if it is on the previous line.
        url('style-catalog/$',
            TemplateView.as_view(template_name='style_catalog.html')),
        url(regex='^%s/(?P<path>.*)$' % settings.MEDIA_URL.strip('/'),
            view='django.views.static.serve',
            kwargs={'document_root': settings.MEDIA_ROOT}),
    )
<|endoftext|>


<commit_before>"""Handler for a Denon AVR-X1200W Audio/Video Receiver.

It reacts to various events triggered by my Chromecast, such as "App
(dis-)connected" or the type of media playing. Commands are sent via Telnet and
follow this documentation:
http://assets.denon.com/documentmaster/de/avr3313ci_protocol_v02.pdf
"""

###############################################################################
#
# TODO: [ ] get IP from config/router
# TODO: [X] build commands from base class:
#   DO, WHILE_DO, IF_DO
#   com.execute(), depending on ^^^
# TODO: [ ] Test old commands:
# TODO: [ ]     turn_off_with_delay
# TODO: [ ]     chromecast_connection_change
# TODO: [ ]     chromecast_playstate_change
# TODO: [ ] Docstrings
# TODO: [ ] Mute
# TODO: [ ] profile.night command
#
###############################################################################

# standard library imports
from collections import Iterable
from enum import Enum
import logging
import queue
import re
import socket
import telnetlib
import threading
import time
import traceback

# related third party imports

# application specific imports
from samantha.core import subscribe_to
from samantha.plugins.plugin import Device

__version__ = "1.7a1"

# Initialize the logger
LOGGER = logging.getLogger(__name__)

COMM_QUEUE = queue.Queue()

DEVICE_IP = "192.168.178.48"

try:
    telnet = telnetlib.Telnet(DEVICE_IP)
    telnet.close()
    ACTIVE = True
except socket.error:
    ACTIVE = False

class CommandType(Enum):
    DO = 1
    WHILE_DO = 2
    IF_DO = 3

class TelnetConnection:
    def __init__(self, retries=3):
        self.connection_retries = retries
        self._telnet = None

    def _connect(self):
        self._telnet = None
        while self.connection_retries > 0:
            # Try establishing a telnet connection
            try:
                self._telnet = telnetlib.Telnet(DEVICE_IP)
                return True  # Connection successful
            except socket.error:
                LOGGER.warning("AVR refused the connection. Retrying...")
                self.connection_retries -= 1
                time.sleep(1)
        if self._telnet is None:
            LOGGER.error("AVR refused the connection. Is another "
                         "device using the Telnet connection already?"
                         "\n%s", traceback.format_exc())
            return False

    def _disconnect(self):
        if self._telnet:
            self._telnet.close()

    def _execute_command(self, command):
        """Send a command to the connected AVR via Telnet."""
        output = ""
        if self._connect():
            LOGGER.debug("Sending command '%s'", command)
            self._telnet.write("{}\r".format(command).encode("utf-8"))
            LOGGER.debug("Successfully sent the command '%s'.", command)
            output = self._telnet.read_some()  # Read the output
            output = output.decode("utf-8").replace("\r", " ")
        else:
            LOGGER.error("Couldn't send the command %s since the SVR refused "
                         "to connect.", command)
        self._disconnect()  # Disconnect either way
        return output.strip()

class Condition(TelnetConnection):
    def __init__(self, command=None, expectation=None,
                 must_match=True, retries=3):
        super().__init__(retries)
        self.command = command
        self.expectation = expectation
        self.must_match = must_match

    def check(self):
        if not self.command:
            return self.expectation
        else:
            result = self._execute_command(self.command)
            matches = result == self.expectation
            LOGGER.debug("Condition was: '%s'. Comparing to: '%s'. "
                         "Matches: %s. Should match: %s.",
                         self.expectation, result,
                         matches, self.must_match)
            return matches == self.must_match

class Command(TelnetConnection):
    def __init__(self, command, type=CommandType.DO,
                 condition=Condition(expectation=True), retries=3):
        super().__init__(retries)
        self.command = command
        self.type = type
        self.condition = condition

    def execute(self):
        while self.condition.check():
            self._execute_command(self.command)
            if self.type is not CommandType.WHILE_DO:
                break

class SmoothVolumeCommand(Command):
    def __init__(self, target_volume, retries=3):
        target_volume = int(target_volume)
        self.target_volume = (0 if target_volume < 0 else
                              98 if target_volume > 98 else
                              target_volume)
        condition = Condition("MV?", "MV{}".format(self.target_volume), False)
        super().__init__(None, CommandType.WHILE_DO, condition, retries)

    def _parse(self, result):
        match = re.match(r"MV(?P<vol>\d{2})(?P<dec>\d?)", result)
        if int(match.group("vol")) < self.target_volume:
            self.command = "MVUP"
        else:
            self.command = "MVDOWN"

    def execute(self):
        self._parse(self._execute_command("MV?"))
        super().execute()

def _check_condition(condition, _telnet, logger):
    """Check if the given condition is met.

    Is must be in the format ["COMMAND=RESULT", Boolean]. Depending on the
    boolean, the condition is met when the result is part of the command's
    result (Bool == True) or not (Bool == False).
    """
    if condition is None:
        return True
    try:
        condition_must_match = condition[1]
        cond_comm, cond_val = condition[0].split("=")

        _telnet.read_very_eager()  # Clear the device's output

        # Send the command specified in the condition
        _telnet.write("{}\r".format(cond_comm).encode("utf-8"))

        output = _telnet.read_some()  # Read the output
        output = output.decode("utf-8").replace("\r", " ")
        logger.debug("Condition was: '%s'. Output was: '%s'",
                     condition, output)

        if condition_must_match == (cond_val in output):
            return True

    except ValueError:
        logger.exception("Error while processing the condition '%s'.",
                         condition)

    return False

def _send(command, logger, condition=None, retries=3):
    """Send a command to the connected AVR via Telnet."""
    if retries > 0:
        _telnet = None
        while retries > 0:
            try:
                _telnet = telnetlib.Telnet(DEVICE_IP)
                break
            except socket.error:
                logger.warning("AVR refused the connection. Retrying...")
                retries -= 1
                time.sleep(1)
        if _telnet is None:
            logger.error("AVR refused the connection. Is another "
                         "device using the Telnet connection already?"
                         "\n%s", traceback.format_exc())
        else:
            if not _check_condition(condition, _telnet, logger):
                logger.debug("Not sending the command because the condition "
                             "'%s' is not fulfilled.", condition)
            else:
                logger.debug("Sending command '%s'", command)
                _telnet.write("{}\r".format(command).encode("utf-8"))
                logger.debug("Successfully sent the command '%s'.", command)

            # Close the telnet connection in any case
            _telnet.close()

    else:
        logger.error("Maximium count of retries reached. The command '%s' "
                     "couldn't be sent.", command)

def worker():
    """Read and process commands from the COMM_QUEUE queue.

    Put results back into OUTPUT. This helps if 2 commands should be sent at
    the same time. Since the worker reads all items from a threadsafe Queue, no
    parallel processing (and thus no attempt to create a 2 telnet connections
    at the same time) is possible.
    """
    # Get a new logger for each thread.
    # Used instead of the global LOGGER on purpose inside this function.
    logger = logging.getLogger(
        __name__ + "." + threading.current_thread().name)

    while True:
        # Wait until an item becomes available in INPUT
        logger.debug("Waiting for a command.")
        command = COMM_QUEUE.get()
        logger.warning("Processing command %s", command)
        command.execute()
        COMM_QUEUE.task_done()

def turn_off_with_delay(name="sleeper"):
    """Turn the AVR off."""
    logger = logging.getLogger(name)
    logger.debug("Sending the command to shut down the AVR.")
    condition = Condition("SI?", "SIMPLAY", True)
    command = Command(command="ZMOFF", condition=condition)
    COMM_QUEUE.put(command)

WORKER = threading.Thread(target=worker, name="worker")
WORKER.daemon = True
SLEEPER = None

PLUGIN = Device("AVR", ACTIVE, LOGGER, __file__)

@subscribe_to("system.onstart")
def onstart(key, data):
    """Set up the plugin by starting the worker-thread."""
    LOGGER.debug("Starting the worker")
    WORKER.start()
    return "Worker started successfully."

@subscribe_to("set.avr.volume")
def set_vol(key, data):
    if "volume" in data:
        command = SmoothVolumeCommand(data["volume"])
        COMM_QUEUE.put(command)

@subscribe_to("chromecast.connection_change")
def chromecast_connection_change(key, data):
    """React to a change of the Chromecast's connection."""
    global SLEEPER

    if SLEEPER is not None:
        # Stop the sleeper if it's already running
        LOGGER.debug("Stopping the sleeper-thread.")
        SLEEPER.cancel()
        SLEEPER.join()
        SLEEPER = None
        LOGGER.debug("Stopped the sleeper-thread.")

    # Check if the Chromecast is connected to an app
    if data["display_name"] in [None, "Backdrop"]:  # not connected
        LOGGER.debug("No app connected to the Chromecast. (%s)",
                     data["display_name"])
        # Run the sleeper that turns off the AVR after 2 minutes
        SLEEPER = threading.Timer(120.0,
                                  turn_off_with_delay,
                                  [__name__ + ".sleeper"])
        SLEEPER.start()
        LOGGER.debug("Started the Sleeper with a delay of 120 seconds.")
        return "No app connected, Started the Sleeper."
    else:  # An app is connected to the Chromecast
        LOGGER.debug("'%s' connected to the Chromecast.",
                     data["display_name"])
        condition_is_off = Condition("ZM?", "ZMON", False)
        command_on = Command("ZMON", condition=condition_is_off)
        COMM_QUEUE.put(command_on)
        condition_input_not_play = Condition("SI?", "SIMPLAY", False)
        command_switch_input_play = Command("SIMPLAY",
                                            condition=condition_input_not_play)
        COMM_QUEUE.put(command_switch_input_play)
        return "Handled connecting of {} to the Chromecast.".format(
            data["display_name"])

@subscribe_to("chromecast.contenttype_change")
def chromecast_playstate_change(key, data):
    """React to a change of the Chromecast's playstate."""

    # Set the audio mode depending on what kind of content is playing
    if data["content_type"] is not None:
        comm_text = ("MSSTEREO" if "audio" in data["content_type"] else
                   "MSDOLBY DIGITAL")
        # Prefer stereo audio for music, surround for everything else
        condition = Condition("MS?", comm_text.split(" ")[0])
        command = Command(comm_text, condition=condition)
        COMM_QUEUE.put(command)
        return "Set he audio mode to {}.".format(comm_text)
    else:
        return "Invalid content-type. Looks like no app is currently connected."

@subscribe_to("system.onexit")
def stop(key, data):
    """Exit the device's hadler."""
    LOGGER.info("Exiting...")
    if SLEEPER:
        SLEEPER.cancel()
        SLEEPER.join()
    LOGGER.warning("Waiting for COMM_QUEUE to be emptied. It currently holds "
                   "%d items.", COMM_QUEUE.qsize())
    COMM_QUEUE.join()
    LOGGER.info("Exited.")
    return "Exited."
<commit_msg>:new: added profile.active.night command, muting and auto-standby<commit_after>"""Handler for a Denon AVR-X1200W Audio/Video Receiver.

It reacts to various events triggered by my Chromecast, such as "App
(dis-)connected" or the type of media playing. Commands are sent via Telnet and
follow this documentation:
http://assets.denon.com/documentmaster/de/avr3313ci_protocol_v02.pdf
"""

###############################################################################
#
# TODO: [ ] get IP from config/router
# TODO: [X] build commands from base class:
#   DO, WHILE_DO, IF_DO
#   com.execute(), depending on ^^^
# TODO: [ ] Test old commands:
# TODO: [ ]     turn_off_with_delay
# TODO: [ ]     chromecast_connection_change
# TODO: [ ]     chromecast_playstate_change
# TODO: [X] Docstrings
# TODO: [X] Mute ("SIMUON/OFF/?")
# TODO: [ ] profile.night command
# TODO: [WIP] Improve performance while sending commands
#
###############################################################################

# standard library imports
# from collections import Iterable
from enum import Enum
import logging
import queue
import re
import socket
import telnetlib
import threading
import time
import traceback

# related third party imports

# application specific imports
from samantha.core import subscribe_to
from samantha.plugins.plugin import Device

__version__ = "1.7a2"

# Initialize the logger
LOGGER = logging.getLogger(__name__)

COMM_QUEUE = queue.Queue()

DEVICE_IP = "192.168.178.48"

try:
    telnet = telnetlib.Telnet(DEVICE_IP)
    telnet.close()
    ACTIVE = True
except socket.error:
    ACTIVE = False

class CommandType(Enum):
    DO = 1
    WHILE_DO = 2
    IF_DO = 3

class TelnetConnection:
    def __init__(self, retries=3):
        """Connection to the AVR. Used as parent for Command and Condition.

        :param retries:  Number of max. retries. (default: 3)
        """
        self.connection_retries = retries
        self._telnet = None

    def _connect(self):
        """Connect to the AVR.

        :return: True, if the connection succeeded, False otherwise.
        """
        self._telnet = None
        while self.connection_retries > 0:
            # Try establishing a telnet connection
            try:
                self._telnet = telnetlib.Telnet(DEVICE_IP)
                return True  # Connection successful
            except socket.error:
                LOGGER.warning("AVR refused the connection. Retrying...")
                self.connection_retries -= 1
                time.sleep(1)
        if self._telnet is None:
            LOGGER.error("AVR refused the connection. Is another "
                         "device using the Telnet connection already?"
                         "\n%s", traceback.format_exc())
            return False

    def _disconnect(self):
        """Disconnect the Telnet connection to the AVR."""
        if self._telnet:
            self._telnet.close()

    def _execute_command(self, command):
        """Send a command to the connected AVR via Telnet.
        :param command: The command to be executed
        :return: The command's output
        """
        LOGGER.debug("Sending command '%s'", command)
        self._telnet.write("{}\r".format(command).encode("utf-8"))
        LOGGER.debug("Successfully sent the command '%s'.", command)
        output = self._telnet.read_some()  # Read the output
        output = output.decode("utf-8").replace("\r", " ")
        return output.strip()

class Condition(TelnetConnection):
    def __init__(self, command=None, expectation=None,
                 must_match=True, retries=3):
        """Initialize the condition.

        If command is None, this will return ecpectation.

        :param command: Command to produce the output that is to be checked.
        :param expectation: The expected output.
        :param must_match: Whether the output must match the expectation. This
        parameter allows for "EQUAL" and "NOT EQUAL" comparison
        :param retries:  Number of max. retries. (default: 3)
        :return: Boolean, whether the condition checks out or not.
        """
        super().__init__(retries)
        self.command = command
        self.expectation = expectation
        self.must_match = must_match

    def check(self):
        if not self.command:
            return self.expectation
        else:
            result = self._execute_command(self.command)
            matches = result == self.expectation
            LOGGER.debug("Condition was: '%s'. Comparing to: '%s'. "
                         "Matches: %s. Should match: %s.",
                         self.expectation, result,
                         matches, self.must_match)
            return matches == self.must_match

class Command(TelnetConnection):
    def __init__(self, command, command_type=CommandType.DO,
                 condition=Condition(expectation=True), retries=3):
        """Initialize the command.

        :param command: Command to be sent to the AVR
        :param command_type: Type of the command (default: CommandType.DO)
        :param condition: Condition to execute the command (default: None)
        :param retries: Number of max. retries. (default: 3)
        """
        super().__init__(retries)
        self.command = command
        self.type = command_type
        self.condition = condition

    def execute(self):
        """Execute self.command."""
        if self._connect():
            while self.condition.check():
                self._execute_command(self.command)
                if self.type is not CommandType.WHILE_DO:
                    break
        else:
            LOGGER.error("Couldn't send the command %s since the SVR refused "
                         "to connect.", self.command)
        self._disconnect()  # Disconnect either way

class SmoothVolumeCommand(Command):
    def __init__(self, target_volume,
                 condition=Condition(expectation=True), retries=3):
        """Initialize the command.

        :param target_volume: The value to adjust the volume to
        :param retries: Number of max. retries. (defaul: 3)
        """
        target_volume = int(target_volume)
        self.target_volume = (0 if target_volume < 0 else
                              98 if target_volume > 98 else
                              target_volume)
        self.vol_condition = condition
        while_condition = Condition("MV?",
                                    "MV{}".format(self.target_volume),
                                    False)
        super().__init__(None, CommandType.WHILE_DO, while_condition, retries)

    def _parse(self, result):
        """ Parse the current volume and adjust self.command.

        :param result: output of MV?, eg. MV125 for 12.5
        """
        match = re.match(r"MV(?P<vol>\d{2})(?P<dec>\d?)", result)
        if int(match.group("vol")) < self.target_volume:
            self.command = "MVUP"
        else:
            self.command = "MVDOWN"

    def execute(self):
        """Parse current volume, then execute via parent function."""
        self._parse(self._execute_command("MV?"))
        if self.vol_condition.check():
            super().execute()

# def _check_condition(condition, _telnet, logger):
#     """Check if the given condition is met.
#
#     Is must be in the format ["COMMAND=RESULT", Boolean]. Depending on the
#     boolean, the condition is met when the result is part of the command's
#     result (Bool == True) or not (Bool == False).
#     """
#     if condition is None:
#         return True
#     try:
#         condition_must_match = condition[1]
#         cond_comm, cond_val = condition[0].split("=")
#
#         _telnet.read_very_eager()  # Clear the device's output
#
#         # Send the command specified in the condition
#         _telnet.write("{}\r".format(cond_comm).encode("utf-8"))
#
#         output = _telnet.read_some()  # Read the output
#         output = output.decode("utf-8").replace("\r", " ")
#         logger.debug("Condition was: '%s'. Output was: '%s'",
#                      condition, output)
#
#         if condition_must_match == (cond_val in output):
#             return True
#
#     except ValueError:
#         logger.exception("Error while processing the condition '%s'.",
#                          condition)
#
#     return False
#
#
# def _send(command, logger, condition=None, retries=3):
#     """Send a command to the connected AVR via Telnet."""
#     if retries > 0:
#         _telnet = None
#         while retries > 0:
#             try:
#                 _telnet = telnetlib.Telnet(DEVICE_IP)
#                 break
#             except socket.error:
#                 logger.warning("AVR refused the connection. Retrying...")
#                 retries -= 1
#                 time.sleep(1)
#         if _telnet is None:
#             logger.error("AVR refused the connection. Is another "
#                          "device using the Telnet connection already?"
#                          "\n%s", traceback.format_exc())
#         else:
#             if not _check_condition(condition, _telnet, logger):
#                 logger.debug("Not sending the command because the condition "
#                              "'%s' is not fulfilled.", condition)
#             else:
#                 logger.debug("Sending command '%s'", command)
#                 _telnet.write("{}\r".format(command).encode("utf-8"))
#                 logger.debug("Successfully sent the command '%s'.", command)
#
#             # Close the telnet connection in any case
#             _telnet.close()
#
#     else:
#         logger.error("Maximium count of retries reached. The command '%s' "
#                      "couldn't be sent.", command)

def worker():
    """Read and process commands from the COMM_QUEUE queue.

    Put results back into OUTPUT. This helps if 2 commands should be sent at
    the same time. Since the worker reads all items from a threadsafe Queue, no
    parallel processing (and thus no attempt to create a 2 telnet connections
    at the same time) is possible.
    """
    # Get a new logger for each thread.
    # Used instead of the global LOGGER on purpose inside this function.
    logger = logging.getLogger(
        __name__ + "." + threading.current_thread().name)

    while True:
        # Wait until an item becomes available in INPUT
        logger.debug("Waiting for a command.")
        command = COMM_QUEUE.get()
        logger.warning("Processing command %s", command)
        command.execute()
        COMM_QUEUE.task_done()

def turn_off_with_delay(name="sleeper"):
    """Turn the AVR off."""
    logger = logging.getLogger(name)
    logger.debug("Sending the command to shut down the AVR.")
    condition = Condition("SI?", "SIMPLAY", True)
    command = Command(command="ZMOFF", condition=condition)
    COMM_QUEUE.put(command)

WORKER = threading.Thread(target=worker, name="worker")
WORKER.daemon = True
SLEEPER = None

PLUGIN = Device("AVR", ACTIVE, LOGGER, __file__)

@subscribe_to("system.onstart")
def onstart(key, data):
    """Set up the plugin by starting the worker-thread."""
    LOGGER.debug("Starting the worker")
    WORKER.start()
    return "Worker started successfully."

@subscribe_to("set.avr.volume")
def set_vol(key, data):
    if "volume" in data:
        command = SmoothVolumeCommand(data["volume"])
        COMM_QUEUE.put(command)
        return "Volume adjusted successfully to {}.".format(data["volume"])
    return "Parameter 'volume' is missing."

@subscribe_to("set.avr.mute")
def set_vol(key, data):
    if "mute" in data:
        if bool(data["mute"]):
            command = Command("SIMUON")
            status = "on"
        else:
            command = Command("SIMUOFF")
            status = "off"
        COMM_QUEUE.put(command)
        return "Mute turned {} successfully.".format(status)
    return "Parameter 'mute' is missing."

@subscribe_to("set.avr.sleep")
def set_sleep(key, data):
    if "sleep" in data:
        duration = int(data["sleep"])
        if duration < 1:
            command = Command("SLPOFF")
            result = "Sleep timer turned off successfully."
        else:
            if duration > 120:
                duration = 120
            command = Command("SLP{0:03d}".format(duration))
            result = "Sleep timer adjusted successfully to {} minutes.".format(
                duration)
        COMM_QUEUE.put(command)
    else:
        result = "Parameter 'sleep' is missing."
    return result

@subscribe_to("profile.active.night")
def activate_night_profile(key, data):
    """Reduce volume and turn on 20 minute timer if device is on."""
    condition = Condition("PW?", "PWON")
    command_vol = SmoothVolumeCommand(13, condition=condition)
    command_mute = Command("SIMUON", condition=condition)
    command_timer = Command("SLP20", condition=condition)
    COMM_QUEUE.put(command_vol)
    COMM_QUEUE.put(command_mute)
    COMM_QUEUE.put(command_timer)
    return "Adjusted volume, mute and timer"

@subscribe_to("chromecast.connection_change")
def chromecast_connection_change(key, data):
    """React to a change of the Chromecast's connection."""
    global SLEEPER

    if SLEEPER is not None:
        # Stop the sleeper if it's already running
        LOGGER.debug("Stopping the sleeper-thread.")
        SLEEPER.cancel()
        SLEEPER.join()
        SLEEPER = None
        LOGGER.debug("Stopped the sleeper-thread.")

    # Check if the Chromecast is connected to an app
    if data["display_name"] in [None, "Backdrop"]:  # not connected
        LOGGER.debug("No app connected to the Chromecast. (%s)",
                     data["display_name"])
        # Run the sleeper that turns off the AVR after 2 minutes
        SLEEPER = threading.Timer(120.0,
                                  turn_off_with_delay,
                                  [__name__ + ".sleeper"])
        SLEEPER.start()
        LOGGER.debug("Started the Sleeper with a delay of 120 seconds.")
        return "No app connected, Started the Sleeper."
    else:  # An app is connected to the Chromecast
        LOGGER.debug("'%s' connected to the Chromecast.",
                     data["display_name"])
        condition_is_off = Condition("ZM?", "ZMON", False)
        command_on = Command("ZMON", condition=condition_is_off)
        COMM_QUEUE.put(command_on)
        condition_input_not_play = Condition("SI?", "SIMPLAY", False)
        command_switch_input_play = Command("SIMPLAY",
                                            condition=condition_input_not_play)
        COMM_QUEUE.put(command_switch_input_play)
        return "Handled connecting of {} to the Chromecast.".format(
            data["display_name"])

@subscribe_to("chromecast.contenttype_change")
def chromecast_playstate_change(key, data):
    """React to a change of the Chromecast's playstate."""

    # Set the audio mode depending on what kind of content is playing
    if data["content_type"] is not None:
        comm_text = ("MSSTEREO" if "audio" in data["content_type"] else
                     "MSDOLBY DIGITAL")
        # Prefer stereo audio for music, surround for everything else
        condition = Condition("MS?", comm_text.split(" ")[0])
        command = Command(comm_text, condition=condition)
        COMM_QUEUE.put(command)
        return "Set he audio mode to {}.".format(comm_text)
    else:
        return "Invalid content-type. Looks like no app is currently connected."

@subscribe_to("system.onexit")
def stop(key, data):
    """Exit the device's handler."""
    LOGGER.info("Exiting...")
    if SLEEPER:
        SLEEPER.cancel()
        SLEEPER.join()
    LOGGER.warning("Waiting for COMM_QUEUE to be emptied. It currently holds "
                   "%d items.", COMM_QUEUE.qsize())
    COMM_QUEUE.join()
    LOGGER.info("Exited.")
    return "Exited."
<|endoftext|>


<commit_before>"""A plugin to control LEDs connected to another RasPi."""

###############################################################################
#
# TODO: [ ]
#
###############################################################################

# standard library imports
import logging
import math
import time

# related third party imports
try:
    import pigpio
except ImportError:
    pigpio = None

# application specific imports
# pylint: disable=import-error
from core import subscribe_to
from plugins.plugin import Device
# pylint: enable=import-error

__version__ = "1.2.2"

# Initialize the logger
LOGGER = logging.getLogger(__name__)

if pigpio:
    PI = pigpio.pi("192.168.178.56")
else:
    PI = None
    LOGGER.exception(
        "Could not import pigpio. Please follow the instructions on %s to "
        "install it manually.",
        "https://github.com/joan2937/pigpio/blob/master/README#L103")

if PI is not None and PI.connected:
    RED_PINS = [12, 25]
    GREEN_PINS = [20, 23]
    BLUE_PINS = [21, 18]
else:
    RED_PINS = []
    GREEN_PINS = []
    BLUE_PINS = []
    LOGGER.error("Could not connect to the RasPi at 192.168.178.56.")

PLUGIN = Device("LED", PI.connected, LOGGER, __file__)

def _set_pins(red=-1, green=-1, blue=-1):
    if 0 <= red <= 255:
        for pin in RED_PINS:
            PI.set_PWM_dutycycle(pin, red)
    if 0 <= green <= 255:
        for pin in GREEN_PINS:
            PI.set_PWM_dutycycle(pin, green)
    if 0 <= blue <= 255:
        for pin in BLUE_PINS:
            PI.set_PWM_dutycycle(pin, blue)

def _spread(steps, length=256, interpolator=None):
    """Spread an amount of 'steps' evenly in a list with 'length' items."""

    if length == 0:
        return []
    if steps == 0:
        return [0] * length

    # Convert the args to floats
    length = float(length)
    steps = float(steps)

    def linear(x):
        """Linear function. linear(0) = 0, linear(length) = steps."""
        return round(x*(steps/length))

    def squared(x):
        """Squared function. squared(0) = 0, squared(length) = steps."""
        return round(x*x*(steps/(length*length)))

    def root(x):
        """Square root function. root(0) = 0, root(length) = steps."""
        return round((steps*math.sqrt(length)*math.sqrt(x))/length)

    if interpolator == "linear" or (interpolator is None and steps == 0):
        func = linear
    elif interpolator == "squared" or (interpolator is None and steps > 0):
        func = squared
    elif interpolator == "sqrt" or (interpolator is None and steps < 0):
        func = root

    # Create a 'length' long list with evenly distributed items [0 ... 'steps']
    result = [func(x) for x in range(1, int(length)+1)]
    # For each item, set the item to it's rounded difference to the item before
    val = 0.0
    for i, value in enumerate(result):
        result[i], val = int(value - val), value
    return result

def _crossfade(red=-1, green=-1, blue=-1, speed=1.0, interpolator=None):
    """Fade from the current color to another one."""
    steps = int(256 * speed)
    if 0 <= red <= 255:
        red_is = PI.get_PWM_dutycycle(RED_PINS[0])
        red_list = _spread((red - red_is), steps, interpolator)
    if 0 <= green <= 255:
        green_is = PI.get_PWM_dutycycle(GREEN_PINS[0])
        green_list = _spread((green - green_is), steps, interpolator)
    if 0 <= blue <= 255:
        blue_is = PI.get_PWM_dutycycle(BLUE_PINS[0])
        blue_list = _spread((blue - blue_is), steps, interpolator)
    for i in range(steps):
        if red_list[i] is not 0:
            red_is += red_list[i]
        if green_list[i] is not 0:
            green_is += green_list[i]
        if blue_list[i] is not 0:
            blue_is += blue_list[i]
        _set_pins(red_is, green_is, blue_is)

@subscribe_to("system.onstart")
def start_func(key, data):
    """Test the LEDs during the 'onstart' event."""
    _set_pins(0, 0, 0)
    _crossfade(red=255, green=0, blue=0, speed=0.2)
    _crossfade(red=0, green=255, blue=0, speed=0.2)
    _crossfade(red=0, green=0, blue=255, speed=0.2)
    _crossfade(red=0, green=0, blue=0, speed=0.2)
    # _crossfade(255, 85, 17)
    return True

@subscribe_to("test.led")
def test_interpolators(key, data):
    """Test the different interpolators."""
    _crossfade(0, 0, 0, 0.2)
    _crossfade(255, 0, 0, interpolator="sqrt")
    _set_pins(0, 0, 0)
    time.sleep(0.5)
    _set_pins(255, 0, 0)
    _crossfade(0, 0, 0, interpolator="sqrt")
    time.sleep(0.5)
    _crossfade(0, 255, 0, interpolator="linear")
    _set_pins(0, 0, 0)
    time.sleep(0.5)
    _set_pins(0, 255, 0)
    _crossfade(0, 0, 0, interpolator="linear")
    time.sleep(0.5)
    _crossfade(0, 0, 255, interpolator="squared")
    _set_pins(0, 0, 0)
    time.sleep(0.5)
    _set_pins(0, 0, 255)
    _crossfade(0, 0, 0, interpolator="squared")
    # _crossfade(255, 85, 17)
    return True

@PLUGIN.turn_on
@subscribe_to("turn_on.led")
def turn_on(key, data):
    """Turn on all lights."""
    _crossfade(255, 85, 17, 0.2)
    return True

@subscribe_to(["system.onexit", "turn_off.led"])
def turn_off(key, data):
    """Turn off all lights."""
    _crossfade(0, 0, 0, 0.2)
    return True
<commit_msg>:heavy_plus_sign: assign to "light" group<commit_after>"""A plugin to control LEDs connected to another RasPi."""

###############################################################################
#
# TODO: [ ]
#
###############################################################################

# standard library imports
import logging
import math
import time

# related third party imports
try:
    import pigpio
except ImportError:
    pigpio = None

# application specific imports
# pylint: disable=import-error
from core import subscribe_to
from plugins.plugin import Device
# pylint: enable=import-error

__version__ = "1.2.3"

# Initialize the logger
LOGGER = logging.getLogger(__name__)

if pigpio:
    PI = pigpio.pi("192.168.178.56")
else:
    PI = None
    LOGGER.exception(
        "Could not import pigpio. Please follow the instructions on %s to "
        "install it manually.",
        "https://github.com/joan2937/pigpio/blob/master/README#L103")

if PI is not None and PI.connected:
    RED_PINS = [12, 25]
    GREEN_PINS = [20, 23]
    BLUE_PINS = [21, 18]
else:
    RED_PINS = []
    GREEN_PINS = []
    BLUE_PINS = []
    LOGGER.error("Could not connect to the RasPi at 192.168.178.56.")

PLUGIN = Device("LED", PI.connected, LOGGER, __file__, "light")

def _set_pins(red=-1, green=-1, blue=-1):
    if 0 <= red <= 255:
        for pin in RED_PINS:
            PI.set_PWM_dutycycle(pin, red)
    if 0 <= green <= 255:
        for pin in GREEN_PINS:
            PI.set_PWM_dutycycle(pin, green)
    if 0 <= blue <= 255:
        for pin in BLUE_PINS:
            PI.set_PWM_dutycycle(pin, blue)

def _spread(steps, length=256, interpolator=None):
    """Spread an amount of 'steps' evenly in a list with 'length' items."""

    if length == 0:
        return []
    if steps == 0:
        return [0] * length

    # Convert the args to floats
    length = float(length)
    steps = float(steps)

    def linear(x):
        """Linear function. linear(0) = 0, linear(length) = steps."""
        return round(x*(steps/length))

    def squared(x):
        """Squared function. squared(0) = 0, squared(length) = steps."""
        return round(x*x*(steps/(length*length)))

    def root(x):
        """Square root function. root(0) = 0, root(length) = steps."""
        return round((steps*math.sqrt(length)*math.sqrt(x))/length)

    if interpolator == "linear" or (interpolator is None and steps == 0):
        func = linear
    elif interpolator == "squared" or (interpolator is None and steps > 0):
        func = squared
    elif interpolator == "sqrt" or (interpolator is None and steps < 0):
        func = root

    # Create a 'length' long list with evenly distributed items [0 ... 'steps']
    result = [func(x) for x in range(1, int(length)+1)]
    # For each item, set the item to it's rounded difference to the item before
    val = 0.0
    for i, value in enumerate(result):
        result[i], val = int(value - val), value
    return result

def _crossfade(red=-1, green=-1, blue=-1, speed=1.0, interpolator=None):
    """Fade from the current color to another one."""
    steps = int(256 * speed)
    if 0 <= red <= 255:
        red_is = PI.get_PWM_dutycycle(RED_PINS[0])
        red_list = _spread((red - red_is), steps, interpolator)
    if 0 <= green <= 255:
        green_is = PI.get_PWM_dutycycle(GREEN_PINS[0])
        green_list = _spread((green - green_is), steps, interpolator)
    if 0 <= blue <= 255:
        blue_is = PI.get_PWM_dutycycle(BLUE_PINS[0])
        blue_list = _spread((blue - blue_is), steps, interpolator)
    for i in range(steps):
        if red_list[i] is not 0:
            red_is += red_list[i]
        if green_list[i] is not 0:
            green_is += green_list[i]
        if blue_list[i] is not 0:
            blue_is += blue_list[i]
        _set_pins(red_is, green_is, blue_is)

@subscribe_to("system.onstart")
def start_func(key, data):
    """Test the LEDs during the 'onstart' event."""
    _set_pins(0, 0, 0)
    _crossfade(red=255, green=0, blue=0, speed=0.2)
    _crossfade(red=0, green=255, blue=0, speed=0.2)
    _crossfade(red=0, green=0, blue=255, speed=0.2)
    _crossfade(red=0, green=0, blue=0, speed=0.2)
    # _crossfade(255, 85, 17)
    return True

@subscribe_to("test.led")
def test_interpolators(key, data):
    """Test the different interpolators."""
    _crossfade(0, 0, 0, 0.2)
    _crossfade(255, 0, 0, interpolator="sqrt")
    _set_pins(0, 0, 0)
    time.sleep(0.5)
    _set_pins(255, 0, 0)
    _crossfade(0, 0, 0, interpolator="sqrt")
    time.sleep(0.5)
    _crossfade(0, 255, 0, interpolator="linear")
    _set_pins(0, 0, 0)
    time.sleep(0.5)
    _set_pins(0, 255, 0)
    _crossfade(0, 0, 0, interpolator="linear")
    time.sleep(0.5)
    _crossfade(0, 0, 255, interpolator="squared")
    _set_pins(0, 0, 0)
    time.sleep(0.5)
    _set_pins(0, 0, 255)
    _crossfade(0, 0, 0, interpolator="squared")
    # _crossfade(255, 85, 17)
    return True

@PLUGIN.turn_on
@subscribe_to("turn_on.led")
def turn_on(key, data):
    """Turn on all lights."""
    _crossfade(255, 85, 17, 0.2)
    return True

@subscribe_to(["system.onexit", "turn_off.led"])
def turn_off(key, data):
    """Turn off all lights."""
    _crossfade(0, 0, 0, 0.2)
    return True
<|endoftext|>


<commit_before>import os
import mimetypes
import jwt
import requests

from django.conf import settings
from django.utils.log import getLogger
from django.utils.six.moves.urllib.parse import urlparse

logger = getLogger(__name__)

def guess_output_format(url):
    output_format = 'png'
    url_path = urlparse(url).path
    file_extension = os.path.splitext(url_path)[1].lower()
    file_mimetype, file_encoding = mimetypes.guess_type(url_path)

    if not file_mimetype:
        return output_format

    file_type = file_mimetype.split('/')[0]

    raw_image_extensions = [
        '.arw', '.crw', '.cr2', '.nef'
        '.3fr',
        '.ari', '.arw',
        '.bay',
        '.crw', '.cr2', '.cap',
        '.dcs', '.dcr', '.dng', '.drf',
        '.eip', '.erf',
        '.fff',
        '.iiq',
        '.k25', '.kdc',
        '.mdc', '.mef', '.mos', '.mrw',
        '.nef', '.nrw',
        '.obm', '.orf',
        '.pef', '.ptx', '.pxn',
        '.r3d', '.raf', '.raw', '.rwl', '.rw2', '.rwz',
        '.sr2', '.srf', '.srw',
        '.x3f'
    ]

    jpg_conditions = [
        file_type == 'video',
        file_extension in ['.jpg', '.jpeg', '.tiff', '.tif'],
        file_extension in raw_image_extensions
    ]

    if any(jpg_conditions):
        output_format = 'jpg'

    return output_format

def queue_previews(url, sizes, metadata):
    payload = {
        'account_key': settings.BLIMP_PREVIEWS_ACCOUNT_ID,
        'url': url,
        'sizes': sizes,
        'metadata': metadata,
        'extra_data': ['all'],
        'format': guess_output_format(url)
    }

    token = jwt.encode(payload, settings.BLIMP_PREVIEWS_SECRET_KEY)

    headers = {
        'content-type': 'text/plain'
    }

    try:
        logger.info('Requesting previews for {}'.format(url))

        request = requests.post(
            settings.BLIMP_PREVIEWS_URL, data=token, headers=headers)

        logger.info(request.text)
    except Exception as e:
        logger.exception(e)

def decode_previews_payload(token):
    try:
        payload = jwt.decode(token, settings.BLIMP_PREVIEWS_SECRET_KEY)
    except:
        payload = None

    return payload
<commit_msg>Add filepreviews uploader headers option<commit_after>import os
import mimetypes
import jwt
import requests

from django.conf import settings
from django.utils.log import getLogger
from django.utils.six.moves.urllib.parse import urlparse

logger = getLogger(__name__)

def guess_output_format(url):
    output_format = 'png'
    url_path = urlparse(url).path
    file_extension = os.path.splitext(url_path)[1].lower()
    file_mimetype, file_encoding = mimetypes.guess_type(url_path)

    if not file_mimetype:
        return output_format

    file_type = file_mimetype.split('/')[0]

    raw_image_extensions = [
        '.arw', '.crw', '.cr2', '.nef'
        '.3fr',
        '.ari', '.arw',
        '.bay',
        '.crw', '.cr2', '.cap',
        '.dcs', '.dcr', '.dng', '.drf',
        '.eip', '.erf',
        '.fff',
        '.iiq',
        '.k25', '.kdc',
        '.mdc', '.mef', '.mos', '.mrw',
        '.nef', '.nrw',
        '.obm', '.orf',
        '.pef', '.ptx', '.pxn',
        '.r3d', '.raf', '.raw', '.rwl', '.rw2', '.rwz',
        '.sr2', '.srf', '.srw',
        '.x3f'
    ]

    jpg_conditions = [
        file_type == 'video',
        file_extension in ['.jpg', '.jpeg', '.tiff', '.tif'],
        file_extension in raw_image_extensions
    ]

    if any(jpg_conditions):
        output_format = 'jpg'

    return output_format

def queue_previews(url, sizes, metadata):
    payload = {
        'account_key': settings.BLIMP_PREVIEWS_ACCOUNT_ID,
        'url': url,
        'sizes': sizes,
        'metadata': metadata,
        'extra_data': ['all'],
        'format': guess_output_format(url),
        'uploader': {
            'headers': {
                'Cache-Control': 'max-age=315360000, no-transform, private',
                'x-amz-acl': 'private'
            }
        }
    }

    token = jwt.encode(payload, settings.BLIMP_PREVIEWS_SECRET_KEY)

    headers = {
        'content-type': 'text/plain'
    }

    try:
        logger.info('Requesting previews for {}'.format(url))

        request = requests.post(
            settings.BLIMP_PREVIEWS_URL, data=token, headers=headers)

        logger.info(request.text)
    except Exception as e:
        logger.exception(e)

def decode_previews_payload(token):
    try:
        payload = jwt.decode(token, settings.BLIMP_PREVIEWS_SECRET_KEY)
    except:
        payload = None

    return payload
<|endoftext|>


<commit_before>"""

resourceservice.py

This module contains functionality for interacting with resource models in
the database.

"""

from rad.models import Resource
from datetime import datetime
import rad.geoutils

def search(database, search_params=None, limit=0, order_by='last_updated desc'):
    """
    Searches for one or more resources in the database using the specified parameters.

    Args:
        database: The current database context.
        limit: The maximum number of results to return.
        search_params: The dictionary of searching parameters to use.
        order_by: The ordering to use.

    Returns:
        A list of all resources matching the specified filtering criteria.
    """

    # Make sure we have at least some searching parameters, or failing that,
    # at least some sort of limit
    if (search_params is None or len(search_params) == 0) and limit <= 0:
        return None

    # Set up our
    query = database.session.query(Resource)

    # TODO: Add in category/tag stuff once that's all properly sorted

    # Make sure we have some searching parameters!
    if search_params is not None and len(search_params) > 0:

        # "id" parameter - search against specific resource ID
        if 'id' in search_params:
            query = query.filter(Resource.id == search_params['id'])

        # "search" parameter - text search against name/description
        if 'search' in search_params and not search_params['search'].isspace():
            search_like_str = '%' + search_params['search'] + '%'
            query = query.filter(or_(Resource.name.like(search_like_str), Resource.description.like(search_like_str)))

        # Location parameters ("lat", "long", "dist") - proximity filtering
        if 'dist' in search_params and \
            search_params['dist'] > 0 and \
            'lat' in search_params and \
            'long' in search_params:

            # Convert our overall distance value to kilometers
            dist_km = geoutils.miles2km(search_params['dist'])

            # Calculate our bounding box
            minLat, minLong, maxLat, maxLong = geoutils.boundingBox(search_params['lat'],
                search_params['long'], dist_km)

            # Now apply filtering against that bounding box
            query = query.filter(Resource.latitude >= minLat, Resource.latitude <= maxLat)
            query = query.filter(Resource.longitude >= minLong, Resource.longitude <= maxLong)

    # Apply ordering
    if order_by is not None and not order_by.isspace():
        query = query.order_by(order_by)

    # Apply limiting
    if limit > 0:
        query = query.limit(limit)

    return query.all()

def save(database, resource):
    """
    Creates or modifies a resource.

    Args:
        database: The current database context.
        resource: The resource to save.

    Returns:
        A Boolean indicating if the operation succeeded.  If a pre-existing
        resource is being updated (as indicated by its ID) but it no longer
        exists in the database, the save will fail.
    """
    # TODO
    pass

def delete(database, resource_id):
    """
    Deletes a resource.

    Args:
        database: The current database context.
        resource_id: The ID of the resource to delete.

    Returns:
        A Boolean indicating if the operation succeeded.
    """
    # TODO
    pass
<commit_msg>adding support for a "visible" searching parameter<commit_after>"""

resourceservice.py

This module contains functionality for interacting with resource models in
the database.

"""

from rad.models import Resource
from datetime import datetime
import rad.geoutils

def search(database, search_params=None, limit=0, order_by='last_updated desc'):
    """
    Searches for one or more resources in the database using the specified parameters.

    Args:
        database: The current database context.
        limit: The maximum number of results to return.
        search_params: The dictionary of searching parameters to use.
        order_by: The ordering to use.

    Returns:
        A list of all resources matching the specified filtering criteria.
    """

    # Make sure we have at least some searching parameters, or failing that,
    # at least some sort of limit
    if (search_params is None or len(search_params) == 0) and limit <= 0:
        return None

    # Set up our
    query = database.session.query(Resource)

    # TODO: Add in category/tag stuff once that's all properly sorted

    # Make sure we have some searching parameters!
    if search_params is not None and len(search_params) > 0:

        # "id" parameter - search against specific resource ID
        if 'id' in search_params:
            query = query.filter(Resource.id == search_params['id'])

        if 'visible' in search_params:
            # TODO: update once spelling is fixed
            query = query.filter(Resource.visable == search_params['visible'])

        # "search" parameter - text search against name/description
        if 'search' in search_params and not search_params['search'].isspace():
            search_like_str = '%' + search_params['search'] + '%'
            query = query.filter(or_(Resource.name.like(search_like_str), Resource.description.like(search_like_str)))

        # Location parameters ("lat", "long", "dist") - proximity filtering
        if 'dist' in search_params and \
            search_params['dist'] > 0 and \
            'lat' in search_params and \
            'long' in search_params:

            # Convert our overall distance value to kilometers
            dist_km = geoutils.miles2km(search_params['dist'])

            # Calculate our bounding box
            minLat, minLong, maxLat, maxLong = geoutils.boundingBox(search_params['lat'],
                search_params['long'], dist_km)

            # Now apply filtering against that bounding box
            query = query.filter(Resource.latitude >= minLat, Resource.latitude <= maxLat)
            query = query.filter(Resource.longitude >= minLong, Resource.longitude <= maxLong)

    # Apply ordering
    if order_by is not None and not order_by.isspace():
        query = query.order_by(order_by)

    # Apply limiting
    if limit > 0:
        query = query.limit(limit)

    return query.all()

def save(database, resource):
    """
    Creates or modifies a resource.

    Args:
        database: The current database context.
        resource: The resource to save.

    Returns:
        A Boolean indicating if the operation succeeded.  If a pre-existing
        resource is being updated (as indicated by its ID) but it no longer
        exists in the database, the save will fail.
    """
    # TODO
    pass

def delete(database, resource_id):
    """
    Deletes a resource.

    Args:
        database: The current database context.
        resource_id: The ID of the resource to delete.

    Returns:
        A Boolean indicating if the operation succeeded.
    """
    # TODO
    pass
<|endoftext|>