<commit_before>"""Classes for monitoring the monitoring server stack.

    Covering mon-persister, mon-api and mon-thresh.
    Kafka, mysql, vertica and influxdb are covered by other detection plugins. Mon-notification uses statsd.
"""

import logging
import yaml

import monasca_setup.agent_config
import monasca_setup.detection
from monasca_setup.detection import find_process_cmdline
from monasca_setup.detection import find_process_name
from monasca_setup.detection import watch_process

log = logging.getLogger(__name__)

class MonAPI(monasca_setup.detection.Plugin):
    """Detect mon_api and setup monitoring."""
    def _detect(self):
        """Run detection, set self.available True if the service is detected."""
        monasca_api = monasca_setup.detection.find_process_cmdline('monasca-api')
        if monasca_api is not None:
            # monasca-api can show up in urls and be an arg to this setup program, check port also
            # Find the right port from the config, this is specific to the Java version
            try:
                with open('/etc/monasca/api-config.yml', 'r') as config:
                    self.api_config = yaml.load(config.read())
                api_port = self.api_config['server']['applicationConnectors'][0]['port']
            except Exception:
                api_port = 8070
                log.warn('Failed parsing /etc/monasca/api-config.yml, defaulting api port to {0}'.format(api_port))
            for conn in monasca_api.connections('inet'):
                if conn.laddr[1] == api_port:
                    self.available = True
                    return

    def build_config(self):
        """Build the config as a Plugins object and return."""
        log.info("\tEnabling the Monasca api healthcheck")
        config = monasca_setup.agent_config.Plugins()
        config.merge(dropwizard_health_check('monitoring', 'monasca-api', 'http://localhost:8081/healthcheck'))

        log.info("\tEnabling the Monasca api metrics")
        whitelist = [
            {
                "name": "jvm.memory.total.max",
                "path": "gauges/jvm.memory.total.max/value",
                "type": "gauge"},
            {
                "name": "jvm.memory.total.used",
                "path": "gauges/jvm.memory.total.used/value",
                "type": "gauge"},
            {
                "name": "metrics.published",
                "path": "meters/monasca.api.app.MetricService.metrics.published/count",
                "type": "rate"},
            {
                "name": "raw-sql.time.avg",
                "path": "timers/org.skife.jdbi.v2.DBI.raw-sql/mean",
                "type": "gauge"},
            {
                "name": "raw-sql.time.max",
                "path": "timers/org.skife.jdbi.v2.DBI.raw-sql/max",
                "type": "gauge"},
        ]
        config.merge(dropwizard_metrics('monitoring', 'monasca-api', 'http://localhost:8081/metrics', whitelist))
        return config

    def dependencies_installed(self):
        return True

class MonNotification(monasca_setup.detection.Plugin):
    """Detect the Monsaca notification engine and setup some simple checks."""
    def _detect(self):
        """Run detection, set self.available True if the service is detected."""
        if find_process_cmdline('monasca-notification') is not None:
            self.available = True

    def build_config(self):
        """Build the config as a Plugins object and return."""
        log.info("\tEnabling the Monasca Notification healthcheck")
        return watch_process(['monasca-notification'], 'monitoring', 'monasca-notification', exact_match=False)

    def dependencies_installed(self):
        return True

class MonPersister(monasca_setup.detection.Plugin):
    """Detect mon_persister and setup monitoring."""
    def _detect(self):
        """Run detection, set self.available True if the service is detected."""
        if find_process_cmdline('monasca-persister') is not None:
            self.available = True

    def build_config(self):
        """Build the config as a Plugins object and return."""
        """Read persister-config.yml file to find the exact numThreads."""
        try:
            with open('/etc/monasca/persister-config.yml', 'r') as config:
                self.persister_config = yaml.load(config.read())
        except Exception:
            log.exception('Failed parsing /etc/monasca/persister-config.yml')
            self.available = False
            return

        alarm_num_threads = self.persister_config['alarmHistoryConfiguration']['numThreads']
        metric_num_threads = self.persister_config['metricConfiguration']['numThreads']

        database_type = self.persister_config['databaseConfiguration']['databaseType']

        log.info("\tEnabling the Monasca persister healthcheck")
        config = monasca_setup.agent_config.Plugins()
        config.merge(dropwizard_health_check('monitoring', 'monasca-persister', 'http://localhost:8091/healthcheck'))

        log.info("\tEnabling the Monasca persister metrics")
        whitelist = [
            {
                "name": "jvm.memory.total.max",
                "path": "gauges/jvm.memory.total.max/value",
                "type": "gauge"},
            {
                "name": "jvm.memory.total.used",
                "path": "gauges/jvm.memory.total.used/value",
                "type": "gauge"}
        ]

        # Generate initial whitelist based on the database type
        if database_type == 'influxdb':
            pass
        elif database_type == 'vertica':
            whitelist.extend([
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.definition-cache-hit-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.definition-cache-hit-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.definition-cache-miss-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.definition-cache-miss-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.definition-dimension-cache-hit-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.definition-dimension-cache-hit-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.definition-dimension-cache-miss-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.definition-dimension-cache-miss-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.dimension-cache-hit-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.dimension-cache-hit-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.dimension-cache-miss-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.dimension-cache-miss-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.measurement-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.measurement-meter/count",
                    "type": "rate"}
            ])
        else:
            log.warn('Failed finding database type in /etc/monasca/persister-config.yml')

        # Dynamic Whitelist
        for idx in range(alarm_num_threads):
            new_thread = {"name": "alarm-state-transitions-added-to-batch-counter[{0}]".format(idx),
                          "path": "counters/monasca.persister.pipeline.event.AlarmStateTransitionHandler[alarm-state-transition-{0}].alarm-state-transitions-added-to-batch-counter/count".format(idx),
                          "type": "rate"
                          }
            whitelist.append(new_thread)

        for idx in range(metric_num_threads):
            new_thread = {"name": "metrics-added-to-batch-counter[{0}]".format(idx),
                          "path": "counters/monasca.persister.pipeline.event.MetricHandler[metric-{0}].metrics-added-to-batch-counter/count".format(idx),
                          "type": "rate"
                          }
            whitelist.append(new_thread)

        config.merge(dropwizard_metrics('monitoring', 'monasca-persister', 'http://localhost:8091/metrics', whitelist))
        return config

    def dependencies_installed(self):
        return True

class MonThresh(monasca_setup.detection.Plugin):
    """Detect the running mon-thresh and monitor."""
    def _detect(self):
        """Run detection, set self.available True if the service is detected."""
        if find_process_cmdline('backtype.storm.daemon') is not None:
            self.available = True

    def build_config(self):
        """Build the config as a Plugins object and return."""
        log.info("\tWatching the mon-thresh process.")
        config = monasca_setup.agent_config.Plugins()
        for process in ['backtype.storm.daemon.nimbus', 'backtype.storm.daemon.supervisor', 'backtype.storm.daemon.worker']:
            if find_process_cmdline(process) is not None:
                config.merge(watch_process([process], 'monitoring', 'apache-storm', exact_match=False))
        return config

    def dependencies_installed(self):
        return True

def dropwizard_health_check(service, component, url):
    """Setup a dropwizard heathcheck to be watched by the http_check plugin."""
    config = monasca_setup.agent_config.Plugins()
    config['http_check'] = {'init_config': None,
                            'instances': [{'name': "{0}-{1} healthcheck".format(service, component),
                                           'url': url,
                                           'timeout': 1,
                                           'include_content': False,
                                           'dimensions': {'service': service, 'component': component}}]}
    return config

def dropwizard_metrics(service, component, url, whitelist):
    """Setup a dropwizard metrics check"""
    config = monasca_setup.agent_config.Plugins()
    config['http_metrics'] = {'init_config': None,
                              'instances': [{'name': "{0}-{1} metrics".format(service, component),
                                             'url': url,
                                             'timeout': 1,
                                             'dimensions': {'service': service, 'component': component},
                                             'whitelist': whitelist}]}
    return config

class MonVertica(monasca_setup.detection.Plugin):
    """Detect Vertica and setup some simple checks."""

    def _detect(self):
        """Run detection, set self.available True if the service is detected.
        """
        if (find_process_name('vertica') is not None and find_process_name(
                'spread') is not None):
            self.available = True

    def build_config(self):
        """Build the config as a Plugins object and return."""
        log.info("\tEnabling the Monasca Vertica check")
        config = monasca_setup.agent_config.Plugins()
        for process in ['vertica', 'spread']:
            config.merge(watch_process([process], 'monitoring', process,
                                       exact_match=False))
        return config

    def dependencies_installed(self):
        return True

class MonInfluxDB(monasca_setup.detection.Plugin):
    """Detect InfluxDB and setup some simple checks."""

    def _detect(self):
        """Run detection, set self.available True if the service is detected.
        """

        if find_process_name('influxd') is not None:
            self.available = True

    def build_config(self):
        """Build the config as a Plugins object and return."""
        log.info("\tEnabling the Monasca InfluxDB check")
        return watch_process(['influxd'], component='influxdb',
                             exact_match=False)

    def dependencies_installed(self):
        return True
<commit_msg>consolidate InfluxDB detection into a single class<commit_after>"""Classes for monitoring the monitoring server stack.

    Covering mon-persister, mon-api and mon-thresh.
    Kafka, mysql, vertica and influxdb are covered by other detection plugins. Mon-notification uses statsd.
"""

import logging
import yaml

import monasca_setup.agent_config
import monasca_setup.detection
from monasca_setup.detection import find_process_cmdline
from monasca_setup.detection import find_process_name
from monasca_setup.detection import watch_process

log = logging.getLogger(__name__)

class MonAPI(monasca_setup.detection.Plugin):
    """Detect mon_api and setup monitoring."""
    def _detect(self):
        """Run detection, set self.available True if the service is detected."""
        monasca_api = monasca_setup.detection.find_process_cmdline('monasca-api')
        if monasca_api is not None:
            # monasca-api can show up in urls and be an arg to this setup program, check port also
            # Find the right port from the config, this is specific to the Java version
            try:
                with open('/etc/monasca/api-config.yml', 'r') as config:
                    self.api_config = yaml.load(config.read())
                api_port = self.api_config['server']['applicationConnectors'][0]['port']
            except Exception:
                api_port = 8070
                log.warn('Failed parsing /etc/monasca/api-config.yml, defaulting api port to {0}'.format(api_port))
            for conn in monasca_api.connections('inet'):
                if conn.laddr[1] == api_port:
                    self.available = True
                    return

    def build_config(self):
        """Build the config as a Plugins object and return."""
        log.info("\tEnabling the Monasca api healthcheck")
        config = monasca_setup.agent_config.Plugins()
        config.merge(dropwizard_health_check('monitoring', 'monasca-api', 'http://localhost:8081/healthcheck'))

        log.info("\tEnabling the Monasca api metrics")
        whitelist = [
            {
                "name": "jvm.memory.total.max",
                "path": "gauges/jvm.memory.total.max/value",
                "type": "gauge"},
            {
                "name": "jvm.memory.total.used",
                "path": "gauges/jvm.memory.total.used/value",
                "type": "gauge"},
            {
                "name": "metrics.published",
                "path": "meters/monasca.api.app.MetricService.metrics.published/count",
                "type": "rate"},
            {
                "name": "raw-sql.time.avg",
                "path": "timers/org.skife.jdbi.v2.DBI.raw-sql/mean",
                "type": "gauge"},
            {
                "name": "raw-sql.time.max",
                "path": "timers/org.skife.jdbi.v2.DBI.raw-sql/max",
                "type": "gauge"},
        ]
        config.merge(dropwizard_metrics('monitoring', 'monasca-api', 'http://localhost:8081/metrics', whitelist))
        return config

    def dependencies_installed(self):
        return True

class MonNotification(monasca_setup.detection.Plugin):
    """Detect the Monsaca notification engine and setup some simple checks."""
    def _detect(self):
        """Run detection, set self.available True if the service is detected."""
        if find_process_cmdline('monasca-notification') is not None:
            self.available = True

    def build_config(self):
        """Build the config as a Plugins object and return."""
        log.info("\tEnabling the Monasca Notification healthcheck")
        return watch_process(['monasca-notification'], 'monitoring', 'monasca-notification', exact_match=False)

    def dependencies_installed(self):
        return True

class MonPersister(monasca_setup.detection.Plugin):
    """Detect mon_persister and setup monitoring."""
    def _detect(self):
        """Run detection, set self.available True if the service is detected."""
        if find_process_cmdline('monasca-persister') is not None:
            self.available = True

    def build_config(self):
        """Build the config as a Plugins object and return."""
        """Read persister-config.yml file to find the exact numThreads."""
        try:
            with open('/etc/monasca/persister-config.yml', 'r') as config:
                self.persister_config = yaml.load(config.read())
        except Exception:
            log.exception('Failed parsing /etc/monasca/persister-config.yml')
            self.available = False
            return

        alarm_num_threads = self.persister_config['alarmHistoryConfiguration']['numThreads']
        metric_num_threads = self.persister_config['metricConfiguration']['numThreads']

        database_type = self.persister_config['databaseConfiguration']['databaseType']

        log.info("\tEnabling the Monasca persister healthcheck")
        config = monasca_setup.agent_config.Plugins()
        config.merge(dropwizard_health_check('monitoring', 'monasca-persister', 'http://localhost:8091/healthcheck'))

        log.info("\tEnabling the Monasca persister metrics")
        whitelist = [
            {
                "name": "jvm.memory.total.max",
                "path": "gauges/jvm.memory.total.max/value",
                "type": "gauge"},
            {
                "name": "jvm.memory.total.used",
                "path": "gauges/jvm.memory.total.used/value",
                "type": "gauge"}
        ]

        # Generate initial whitelist based on the database type
        if database_type == 'influxdb':
            pass
        elif database_type == 'vertica':
            whitelist.extend([
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.definition-cache-hit-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.definition-cache-hit-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.definition-cache-miss-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.definition-cache-miss-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.definition-dimension-cache-hit-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.definition-dimension-cache-hit-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.definition-dimension-cache-miss-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.definition-dimension-cache-miss-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.dimension-cache-hit-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.dimension-cache-hit-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.dimension-cache-miss-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.dimension-cache-miss-meter/count",
                    "type": "rate"},
                {
                    "name": "monasca.persister.repository.vertica.VerticaMetricRepo.measurement-meter",
                    "path": "meters/monasca.persister.repository.vertica.VerticaMetricRepo.measurement-meter/count",
                    "type": "rate"}
            ])
        else:
            log.warn('Failed finding database type in /etc/monasca/persister-config.yml')

        # Dynamic Whitelist
        for idx in range(alarm_num_threads):
            new_thread = {"name": "alarm-state-transitions-added-to-batch-counter[{0}]".format(idx),
                          "path": "counters/monasca.persister.pipeline.event.AlarmStateTransitionHandler[alarm-state-transition-{0}].alarm-state-transitions-added-to-batch-counter/count".format(idx),
                          "type": "rate"
                          }
            whitelist.append(new_thread)

        for idx in range(metric_num_threads):
            new_thread = {"name": "metrics-added-to-batch-counter[{0}]".format(idx),
                          "path": "counters/monasca.persister.pipeline.event.MetricHandler[metric-{0}].metrics-added-to-batch-counter/count".format(idx),
                          "type": "rate"
                          }
            whitelist.append(new_thread)

        config.merge(dropwizard_metrics('monitoring', 'monasca-persister', 'http://localhost:8091/metrics', whitelist))
        return config

    def dependencies_installed(self):
        return True

class MonThresh(monasca_setup.detection.Plugin):
    """Detect the running mon-thresh and monitor."""
    def _detect(self):
        """Run detection, set self.available True if the service is detected."""
        if find_process_cmdline('backtype.storm.daemon') is not None:
            self.available = True

    def build_config(self):
        """Build the config as a Plugins object and return."""
        log.info("\tWatching the mon-thresh process.")
        config = monasca_setup.agent_config.Plugins()
        for process in ['backtype.storm.daemon.nimbus', 'backtype.storm.daemon.supervisor', 'backtype.storm.daemon.worker']:
            if find_process_cmdline(process) is not None:
                config.merge(watch_process([process], 'monitoring', 'apache-storm', exact_match=False))
        return config

    def dependencies_installed(self):
        return True

def dropwizard_health_check(service, component, url):
    """Setup a dropwizard heathcheck to be watched by the http_check plugin."""
    config = monasca_setup.agent_config.Plugins()
    config['http_check'] = {'init_config': None,
                            'instances': [{'name': "{0}-{1} healthcheck".format(service, component),
                                           'url': url,
                                           'timeout': 1,
                                           'include_content': False,
                                           'dimensions': {'service': service, 'component': component}}]}
    return config

def dropwizard_metrics(service, component, url, whitelist):
    """Setup a dropwizard metrics check"""
    config = monasca_setup.agent_config.Plugins()
    config['http_metrics'] = {'init_config': None,
                              'instances': [{'name': "{0}-{1} metrics".format(service, component),
                                             'url': url,
                                             'timeout': 1,
                                             'dimensions': {'service': service, 'component': component},
                                             'whitelist': whitelist}]}
    return config

class MonVertica(monasca_setup.detection.Plugin):
    """Detect Vertica and setup some simple checks."""

    def _detect(self):
        """Run detection, set self.available True if the service is detected.
        """
        if (find_process_name('vertica') is not None and find_process_name(
                'spread') is not None):
            self.available = True

    def build_config(self):
        """Build the config as a Plugins object and return."""
        log.info("\tEnabling the Monasca Vertica check")
        config = monasca_setup.agent_config.Plugins()
        for process in ['vertica', 'spread']:
            config.merge(watch_process([process], 'monitoring', process,
                                       exact_match=False))
        return config

    def dependencies_installed(self):
        return True<|endoftext|>


<commit_before>import sublime
from sublime_plugin import WindowCommand, TextCommand
import re
from ..git_command import GitCommand
from .log import GsLogActionCommand, GsLogCommand
from .navigate import GsNavigate
from ...common import util
from ..ui_mixins.quick_panel import show_branch_panel

COMMIT_NODE_CHAR = "●"
COMMIT_NODE_CHAR_OPTIONS = "●*"
GRAPH_CHAR_OPTIONS = r" /_\|\-."
COMMIT_LINE = re.compile(
    "^[{graph_chars}]*[{node_chars}][{graph_chars}]* (?P<commit_hash>[a-f0-9]{{5,40}})".format(
        graph_chars=GRAPH_CHAR_OPTIONS, node_chars=COMMIT_NODE_CHAR_OPTIONS))

class LogGraphMixin(object):

    """
    Open a new window displaying an ASCII-graphic representation
    of the repo's branch relationships.
    """

    def run(self, file_path=None, title=None):
        self._file_path = file_path
        self.title = title or "GRAPH"
        sublime.set_timeout_async(self.run_async)

    def run_async(self):
        # need to get repo_path before the new view is created.
        repo_path = self.repo_path
        view = util.view.get_scratch_view(self, "log_graph", read_only=True)
        settings = view.settings()
        settings.set("git_savvy.repo_path", repo_path)
        settings.set("git_savvy.file_path", self._file_path)
        settings.set("git_savvy.git_graph_args", self.get_graph_args())
        view.set_syntax_file("Packages/GitSavvy/syntax/graph.sublime-syntax")
        view.set_name(self.title)
        view.sel().clear()
        view.run_command("gs_log_graph_refresh")
        view.run_command("gs_log_graph_navigate")

    def get_graph_args(self):
        args = self.savvy_settings.get("git_graph_args")
        follow = self.savvy_settings.get("log_follow_rename")
        if self._file_path and follow:
            args = args + ["--follow"]
        if self._file_path:
            file_path = self.get_rel_path(self._file_path)
            args = args + ["--", file_path]
        return args

class GsLogGraphRefreshCommand(TextCommand, GitCommand):

    """
    Refresh the current graph view with the latest commits.
    """

    def run(self, edit):
        sublime.set_timeout_async(self.run_async)

    def run_async(self):
        file_path = self.file_path
        if file_path:
            graph_content = "File: {}\n\n".format(file_path)
        else:
            graph_content = ""

        args = self.view.settings().get("git_savvy.git_graph_args")
        graph_content += self.git(*args)
        graph_content = re.sub(
            r'(^[{}]*)\*'.format(GRAPH_CHAR_OPTIONS),
            r'\1' + COMMIT_NODE_CHAR, graph_content,
            flags=re.MULTILINE)

        self.view.run_command("gs_replace_view_text", {"text": graph_content})
        self.view.run_command("gs_log_graph_more_info")

        self.view.run_command("gs_handle_vintageous")
        self.view.run_command("gs_handle_arrow_keys")

class GsLogGraphCurrentBranch(LogGraphMixin, WindowCommand, GitCommand):
    pass

class GsLogGraphAllBranches(LogGraphMixin, WindowCommand, GitCommand):

    def get_graph_args(self):
        args = super().get_graph_args()
        args.append("--all")
        return args

class GsLogGraphByAuthorCommand(LogGraphMixin, WindowCommand, GitCommand):

    """
    Open a quick panel containing all committers for the active
    repository, ordered by most commits, Git name, and email.
    Once selected, display a quick panel with all commits made
    by the specified author.
    """

    def run_async(self):
        email = self.git("config", "user.email").strip()
        self._entries = []

        commiter_str = self.git("shortlog", "-sne", "HEAD")
        for line in commiter_str.split('\n'):
            m = re.search(r'\s*(\d*)\s*(.*)\s<(.*)>', line)
            if m is None:
                continue
            commit_count, author_name, author_email = m.groups()
            author_text = "{} <{}>".format(author_name, author_email)
            self._entries.append((commit_count, author_name, author_email, author_text))

        self.window.show_quick_panel(
            [entry[3] for entry in self._entries],
            self.on_author_selection,
            flags=sublime.MONOSPACE_FONT,
            selected_index=(list(line[2] for line in self._entries)).index(email)
        )

    def on_author_selection(self, index):
        if index == -1:
            return
        self._selected_author = self._entries[index][3]
        super().run_async()

    def get_graph_args(self):
        args = super().get_graph_args()
        args.insert(1, "--author={}".format(self._selected_author))
        return args

class GsLogGraphByBranchCommand(LogGraphMixin, WindowCommand, GitCommand):

    def run_async(self):
        show_branch_panel(self.on_branch_selection)

    def on_branch_selection(self, branch):
        if branch:
            self._selected_branch = branch
            super().run_async()

    def get_graph_args(self):
        args = super().get_graph_args()
        args.append(self._selected_branch)
        return args

class GsLogGraphCommand(GsLogCommand):
    default_actions = [
        ["gs_log_graph_current_branch", "For current branch"],
        ["gs_log_graph_all_branches", "For all branches"],
        ["gs_log_graph_by_author", "Filtered by author"],
        ["gs_log_graph_by_branch", "Filtered by branch"],
    ]

class GsLogGraphActionCommand(GsLogActionCommand):

    """
    Checkout the commit at the selected line. It is also used by compare_commit_view.
    """
    default_actions = [
        ["show_commit", "Show commit"],
        ["checkout_commit", "Checkout commit"],
        ["cherry_pick", "Cherry-pick commit"],
        ["compare_against", "Compare commit against ..."],
        ["copy_sha", "Copy the full SHA"]
    ]

    def update_actions(self):
        super().update_actions()
        view = self.window.active_view()
        if view.settings().get("git_savvy.log_graph_view"):
            if self._file_path:
                # for `git: graph current file`, two more options would be inserted
                # at index 1
                self.actions.insert(5, ["revert_commit", "Revert commit"])
            else:
                self.actions.insert(3, ["revert_commit", "Revert commit"])

            self.actions.extend([
                ["diff_commit", "Diff commit"],
                ["diff_commit_cache", "Diff commit (cached)"],
            ])
        elif view.settings().get("git_savvy.compare_commit_view"):
            pass

    def run(self):
        view = self.window.active_view()

        self.selections = view.sel()

        lines = util.view.get_lines_from_regions(view, self.selections)
        line = lines[0]

        m = COMMIT_LINE.search(line)
        self._commit_hash = m.groupdict()['commit_hash'] if m else ""
        self._file_path = self.file_path

        if not len(self.selections) == 1:
            self.window.status_message("You can only do actions on one commit at a time.")
            return

        super().run(commit_hash=self._commit_hash, file_path=self._file_path)

class GsLogGraphNavigateCommand(GsNavigate):

    """
    Travel between commits. It is also used by compare_commit_view.
    """
    offset = 0

    def run(self, edit, **kwargs):
        super().run(edit, **kwargs)
        self.view.window().run_command("gs_log_graph_more_info")

    def get_available_regions(self):
        return self.view.find_by_selector("constant.numeric.graph.commit-hash.git-savvy")

class GsLogGraphMoreInfoCommand(TextCommand, GitCommand):

    """
    Show all info about a commit in a quick panel. It is also used by compare_commit_view.
    """

    def run(self, edit):
        show_more = self.savvy_settings.get("graph_show_more_commit_info")
        if not show_more:
            return

        selections = self.view.sel()
        if len(selections) != 1:
            return

        lines = util.view.get_lines_from_regions(self.view, selections)
        if not lines:
            return
        line = lines[0]

        m = COMMIT_LINE.search(line)
        commit_hash = m.groupdict()['commit_hash'] if m else ""

        if len(commit_hash) <= 3:
            return

        self.view.window().run_command("gs_show_commit_info", {"commit_hash": commit_hash})

class GsLogGraphToggleMoreInfoCommand(TextCommand, WindowCommand, GitCommand):

    """
    Toggle `graph_show_more_commit_info` setting. It is also used by compare_commit_view.
    """

    def run(self, edit):
        show_more = not self.savvy_settings.get("graph_show_more_commit_info")
        self.savvy_settings.set("graph_show_more_commit_info", show_more)
        if not show_more:
            self.view.window().run_command("hide_panel")

        self.view.run_command("gs_log_graph_more_info")
<commit_msg>Handle one-time side-effects after view creation<commit_after>import sublime
from sublime_plugin import WindowCommand, TextCommand
import re
from ..git_command import GitCommand
from .log import GsLogActionCommand, GsLogCommand
from .navigate import GsNavigate
from ...common import util
from ..ui_mixins.quick_panel import show_branch_panel

COMMIT_NODE_CHAR = "●"
COMMIT_NODE_CHAR_OPTIONS = "●*"
GRAPH_CHAR_OPTIONS = r" /_\|\-."
COMMIT_LINE = re.compile(
    "^[{graph_chars}]*[{node_chars}][{graph_chars}]* (?P<commit_hash>[a-f0-9]{{5,40}})".format(
        graph_chars=GRAPH_CHAR_OPTIONS, node_chars=COMMIT_NODE_CHAR_OPTIONS))

class LogGraphMixin(object):

    """
    Open a new window displaying an ASCII-graphic representation
    of the repo's branch relationships.
    """

    def run(self, file_path=None, title=None):
        self._file_path = file_path
        self.title = title or "GRAPH"
        sublime.set_timeout_async(self.run_async)

    def run_async(self):
        # need to get repo_path before the new view is created.
        repo_path = self.repo_path

        view = util.view.get_scratch_view(self, "log_graph", read_only=True)
        view.set_syntax_file("Packages/GitSavvy/syntax/graph.sublime-syntax")
        view.run_command("gs_handle_vintageous")
        view.run_command("gs_handle_arrow_keys")
        view.sel().clear()

        settings = view.settings()
        settings.set("git_savvy.repo_path", repo_path)
        settings.set("git_savvy.file_path", self._file_path)
        settings.set("git_savvy.git_graph_args", self.get_graph_args())
        view.set_name(self.title)

        view.run_command("gs_log_graph_refresh")
        view.run_command("gs_log_graph_navigate")

    def get_graph_args(self):
        args = self.savvy_settings.get("git_graph_args")
        follow = self.savvy_settings.get("log_follow_rename")
        if self._file_path and follow:
            args = args + ["--follow"]
        if self._file_path:
            file_path = self.get_rel_path(self._file_path)
            args = args + ["--", file_path]
        return args

class GsLogGraphRefreshCommand(TextCommand, GitCommand):

    """
    Refresh the current graph view with the latest commits.
    """

    def run(self, edit):
        sublime.set_timeout_async(self.run_async)

    def run_async(self):
        file_path = self.file_path
        if file_path:
            graph_content = "File: {}\n\n".format(file_path)
        else:
            graph_content = ""

        args = self.view.settings().get("git_savvy.git_graph_args")
        graph_content += self.git(*args)
        graph_content = re.sub(
            r'(^[{}]*)\*'.format(GRAPH_CHAR_OPTIONS),
            r'\1' + COMMIT_NODE_CHAR, graph_content,
            flags=re.MULTILINE)

        self.view.run_command("gs_replace_view_text", {"text": graph_content})
        self.view.run_command("gs_log_graph_more_info")

class GsLogGraphCurrentBranch(LogGraphMixin, WindowCommand, GitCommand):
    pass

class GsLogGraphAllBranches(LogGraphMixin, WindowCommand, GitCommand):

    def get_graph_args(self):
        args = super().get_graph_args()
        args.append("--all")
        return args

class GsLogGraphByAuthorCommand(LogGraphMixin, WindowCommand, GitCommand):

    """
    Open a quick panel containing all committers for the active
    repository, ordered by most commits, Git name, and email.
    Once selected, display a quick panel with all commits made
    by the specified author.
    """

    def run_async(self):
        email = self.git("config", "user.email").strip()
        self._entries = []

        commiter_str = self.git("shortlog", "-sne", "HEAD")
        for line in commiter_str.split('\n'):
            m = re.search(r'\s*(\d*)\s*(.*)\s<(.*)>', line)
            if m is None:
                continue
            commit_count, author_name, author_email = m.groups()
            author_text = "{} <{}>".format(author_name, author_email)
            self._entries.append((commit_count, author_name, author_email, author_text))

        self.window.show_quick_panel(
            [entry[3] for entry in self._entries],
            self.on_author_selection,
            flags=sublime.MONOSPACE_FONT,
            selected_index=(list(line[2] for line in self._entries)).index(email)
        )

    def on_author_selection(self, index):
        if index == -1:
            return
        self._selected_author = self._entries[index][3]
        super().run_async()

    def get_graph_args(self):
        args = super().get_graph_args()
        args.insert(1, "--author={}".format(self._selected_author))
        return args

class GsLogGraphByBranchCommand(LogGraphMixin, WindowCommand, GitCommand):

    def run_async(self):
        show_branch_panel(self.on_branch_selection)

    def on_branch_selection(self, branch):
        if branch:
            self._selected_branch = branch
            super().run_async()

    def get_graph_args(self):
        args = super().get_graph_args()
        args.append(self._selected_branch)
        return args

class GsLogGraphCommand(GsLogCommand):
    default_actions = [
        ["gs_log_graph_current_branch", "For current branch"],
        ["gs_log_graph_all_branches", "For all branches"],
        ["gs_log_graph_by_author", "Filtered by author"],
        ["gs_log_graph_by_branch", "Filtered by branch"],
    ]

class GsLogGraphActionCommand(GsLogActionCommand):

    """
    Checkout the commit at the selected line. It is also used by compare_commit_view.
    """
    default_actions = [
        ["show_commit", "Show commit"],
        ["checkout_commit", "Checkout commit"],
        ["cherry_pick", "Cherry-pick commit"],
        ["compare_against", "Compare commit against ..."],
        ["copy_sha", "Copy the full SHA"]
    ]

    def update_actions(self):
        super().update_actions()
        view = self.window.active_view()
        if view.settings().get("git_savvy.log_graph_view"):
            if self._file_path:
                # for `git: graph current file`, two more options would be inserted
                # at index 1
                self.actions.insert(5, ["revert_commit", "Revert commit"])
            else:
                self.actions.insert(3, ["revert_commit", "Revert commit"])

            self.actions.extend([
                ["diff_commit", "Diff commit"],
                ["diff_commit_cache", "Diff commit (cached)"],
            ])
        elif view.settings().get("git_savvy.compare_commit_view"):
            pass

    def run(self):
        view = self.window.active_view()

        self.selections = view.sel()

        lines = util.view.get_lines_from_regions(view, self.selections)
        line = lines[0]

        m = COMMIT_LINE.search(line)
        self._commit_hash = m.groupdict()['commit_hash'] if m else ""
        self._file_path = self.file_path

        if not len(self.selections) == 1:
            self.window.status_message("You can only do actions on one commit at a time.")
            return

        super().run(commit_hash=self._commit_hash, file_path=self._file_path)

class GsLogGraphNavigateCommand(GsNavigate):

    """
    Travel between commits. It is also used by compare_commit_view.
    """
    offset = 0

    def run(self, edit, **kwargs):
        super().run(edit, **kwargs)
        self.view.window().run_command("gs_log_graph_more_info")

    def get_available_regions(self):
        return self.view.find_by_selector("constant.numeric.graph.commit-hash.git-savvy")

class GsLogGraphMoreInfoCommand(TextCommand, GitCommand):

    """
    Show all info about a commit in a quick panel. It is also used by compare_commit_view.
    """

    def run(self, edit):
        show_more = self.savvy_settings.get("graph_show_more_commit_info")
        if not show_more:
            return

        selections = self.view.sel()
        if len(selections) != 1:
            return

        lines = util.view.get_lines_from_regions(self.view, selections)
        if not lines:
            return
        line = lines[0]

        m = COMMIT_LINE.search(line)
        commit_hash = m.groupdict()['commit_hash'] if m else ""

        if len(commit_hash) <= 3:
            return

        self.view.window().run_command("gs_show_commit_info", {"commit_hash": commit_hash})

class GsLogGraphToggleMoreInfoCommand(TextCommand, WindowCommand, GitCommand):

    """
    Toggle `graph_show_more_commit_info` setting. It is also used by compare_commit_view.
    """

    def run(self, edit):
        show_more = not self.savvy_settings.get("graph_show_more_commit_info")
        self.savvy_settings.set("graph_show_more_commit_info", show_more)
        if not show_more:
            self.view.window().run_command("hide_panel")

        self.view.run_command("gs_log_graph_more_info")
<|endoftext|>


<commit_before>"""
__/\\\\\\\\\\\\______________________/\\\\\\\\\\\____/\\\________/\\\_
 _\/\\\////////\\\__________________/\\\/////////\\\_\/\\\_______\/\\\_
  _\/\\\______\//\\\________________\//\\\______\///__\/\\\_______\/\\\_
   _\/\\\_______\/\\\_____/\\\\\______\////\\\_________\/\\\_______\/\\\_
    _\/\\\_______\/\\\___/\\\///\\\_______\////\\\______\/\\\_______\/\\\_
     _\/\\\_______\/\\\__/\\\__\//\\\_________\////\\\___\/\\\_______\/\\\_
      _\/\\\_______/\\\__\//\\\__/\\\___/\\\______\//\\\__\//\\\______/\\\__
       _\/\\\\\\\\\\\\/____\///\\\\\/___\///\\\\\\\\\\\/____\///\\\\\\\\\/___
        _\////////////________\/////_______\///////////________\/////////_____

Created by Tomáš Sandrini
"""

import os
import sys
import shutil
import errno
import time
import fnmatch
import subprocess
import shlex
from datetime import datetime
from distutils.dir_util import copy_tree

from . import config
from .utils import load_file
from .decorators import trackcalls

@trackcalls
def make(subjects):
    subject_dir = config.get('templates.subject_dir')
    root_dir = config.get('general.root_dir')

    if not root_dir:
        print ("Root dir is not defined in config file")
        return False

    if not subject_dir:
        print ("Subject template dir is not defined in config file")
        return False

    for subject in subjects:

        if subject in config.subjects:
            print ("Subject %s already exists" % subject)
            continue

        copy_tree(subject_dir, root_dir + '/' + subject)

    else:
        return True

    return False

@trackcalls
def delete(subjects):
    root_dir = config.get('general.root_dir')

    if not root_dir:
        print ("Root dir is not defined in config file")
        return False

    for subject in subjects:

        if subject not in config.subjects:
            print ("Subject %s does not exist" % subject)
            continue

        shutil.rmtree(root_dir + '/' + subject)

    else:
        return True

    return False

@trackcalls
def write(subject):
    if subject not in config.subjects:
        print ("Subject %s does not exist" % subject)
        return False

    root_dir = config.get('general.root_dir')

    if not root_dir:
        print ("Root dir is not defined in config file")
        return False

    note_prefix = config.get('templates.note_prefix')
    today = datetime.today()
    subject_dir = root_dir + '/' + subject
    path = '%s/%s/%s%d/%s%02d' % (
        subject_dir,
        config.get('templates.notes_dir'),
        config.get('templates.year_dir_prefix'),
        today.year % 1000,
        config.get('templates.month_dir_prefix'),
        today.month
    )

    try:
        os.makedirs(path)
        note_number = 0
    except OSError as e:
        if e.errno != errno.EEXIST:
            raise
        else:
            note_number = len(fnmatch.filter(os.listdir(path), note_prefix + '*.md'))

    note_number += 1

    meta = load_file(subject_dir + '/' + config.get('templates.meta_file')).format(
        config=config.get('templates.variables'),
        date=time.strftime('%d.%m.%Y'),
        subject=subject,
        note_number=note_number
    )

    path += '/note_%d.md' % note_number

    with open(path, 'w') as f:
        f.write(meta)

    os.chdir(subject_dir)
    if config.get('writing.open_reader'):

        tmp_pdf_path = subject_dir + '/' + '.tmp.pdf'
        subprocess.Popen(['pandoc', path, '-o', tmp_pdf_path]).wait()

        cmd = shlex.split(config.get('writing.reader'))
        cmd.append(tmp_pdf_path)
        subprocess.Popen(cmd)

    if config.get('writing.open_editor'):
        cmd = shlex.split(config.get('writing.editor'))
        cmd.append(path)
        subprocess.Popen(cmd)

    sys.exit(0)

    return True

@trackcalls
def compile(subjects, years, months):

    root_dir = config.get('general.root_dir')

    if not root_dir:
        print ("Root dir is not defined in config file")
        return False

    for subject in subjects:

        if subject not in config.subjects:
            print ("Subject %s does not exist" % subject)
            continue

        today = datetime.today()
        subject_dir = root_dir + '/' + subject
        compiled_path = '%s/%s/%s_%02d_%02d_%d.pdf' % (
            subject_dir,
            config.get('templates.compiled_dir'),
            subject.lower(),
            today.day,
            today.month,
            today.year % 1000
        )

        os.chdir(subject_dir)

        command = "pandoc -o %s " % compiled_path

        for year in years:

            year_dir = '%s/%s/%s%d' % (
                subject_dir,
                config.get('templates.notes_dir'),
                config.get('templates.year_dir_prefix'),
                year % 1000
            )

            if not os.path.isdir(year_dir):
                print ("Subject %s has no notes for year %d" % (subject, year))
                continue

            for month in months:

                month_dir = '%s/%s%02d' % (
                    year_dir,
                    config.get('templates.month_dir_prefix'),
                    month
                )

                if not os.path.isdir(month_dir):
                    print ("Subject %s has no notes for month %d in year %d" % (
                        subject,
                        month,
                        year
                    ))
                    continue

                command += '%s ' % month_dir

        cmd = shlex.split(command)
        subprocess.call(cmd)

        return True
<commit_msg>update<commit_after>"""
__/\\\\\\\\\\\\______________________/\\\\\\\\\\\____/\\\________/\\\_
 _\/\\\////////\\\__________________/\\\/////////\\\_\/\\\_______\/\\\_
  _\/\\\______\//\\\________________\//\\\______\///__\/\\\_______\/\\\_
   _\/\\\_______\/\\\_____/\\\\\______\////\\\_________\/\\\_______\/\\\_
    _\/\\\_______\/\\\___/\\\///\\\_______\////\\\______\/\\\_______\/\\\_
     _\/\\\_______\/\\\__/\\\__\//\\\_________\////\\\___\/\\\_______\/\\\_
      _\/\\\_______/\\\__\//\\\__/\\\___/\\\______\//\\\__\//\\\______/\\\__
       _\/\\\\\\\\\\\\/____\///\\\\\/___\///\\\\\\\\\\\/____\///\\\\\\\\\/___
        _\////////////________\/////_______\///////////________\/////////_____

Created by Tomáš Sandrini
"""

import os
import sys
import shutil
import errno
import time
import fnmatch
import subprocess
import shlex
from datetime import datetime
from distutils.dir_util import copy_tree

from . import config
from .utils import load_file
from .decorators import trackcalls

@trackcalls
def make(subjects):
    subject_dir = config.get('templates.subject_dir')
    root_dir = config.get('general.root_dir')

    if not root_dir:
        print ("Root dir is not defined in config file")
        return False

    if not subject_dir:
        print ("Subject template dir is not defined in config file")
        return False

    for subject in subjects:

        if subject in config.subjects:
            print ("Subject %s already exists" % subject)
            continue

        copy_tree(subject_dir, root_dir + '/' + subject)

    else:
        return True

    return False

@trackcalls
def delete(subjects):
    root_dir = config.get('general.root_dir')

    if not root_dir:
        print ("Root dir is not defined in config file")
        return False

    for subject in subjects:

        if subject not in config.subjects:
            print ("Subject %s does not exist" % subject)
            continue

        shutil.rmtree(root_dir + '/' + subject)

    else:
        return True

    return False

@trackcalls
def write(subject):
    if subject not in config.subjects:
        print ("Subject %s does not exist" % subject)
        return False

    root_dir = config.get('general.root_dir')

    if not root_dir:
        print ("Root dir is not defined in config file")
        return False

    note_prefix = config.get('templates.note_prefix')
    today = datetime.today()
    subject_dir = root_dir + '/' + subject
    path = '%s/%s/%s%d/%s%02d' % (
        subject_dir,
        config.get('templates.notes_dir'),
        config.get('templates.year_dir_prefix'),
        today.year % 1000,
        config.get('templates.month_dir_prefix'),
        today.month
    )

    try:
        os.makedirs(path)
        note_number = 0
    except OSError as e:
        if e.errno != errno.EEXIST:
            raise
        else:
            note_number = len(fnmatch.filter(os.listdir(path), note_prefix + '*.md'))

    note_number += 1

    meta = load_file(subject_dir + '/' + config.get('templates.meta_file')).format(
        config=config.get('templates.variables'),
        date=time.strftime('%d.%m.%Y'),
        subject=subject,
        note_number=note_number
    )

    path += '/note_%d.md' % note_number

    with open(path, 'w') as f:
        f.write(meta)

    os.chdir(subject_dir)
    if config.get('writing.open_reader'):

        tmp_pdf_path = subject_dir + '/' + '.tmp.pdf'
        subprocess.Popen(['pandoc', path, '-o', tmp_pdf_path]).wait()

        cmd = shlex.split(config.get('writing.reader'))
        cmd.append(tmp_pdf_path)
        subprocess.Popen(cmd)

    if config.get('writing.open_editor'):
        cmd = shlex.split(config.get('writing.editor'))
        cmd.append(path)
        subprocess.Popen(cmd)

    sys.exit(0)

    return True

@trackcalls
def compile(subjects, years, months):

    root_dir = config.get('general.root_dir')

    if not root_dir:
        print ("Root dir is not defined in config file")
        return False

    for subject in subjects:

        if subject not in config.subjects:
            print ("Subject %s does not exist" % subject)
            continue

        today = datetime.today()
        subject_dir = root_dir + '/' + subject
        compiled_path = '%s/%s/%s_%02d_%02d_%d.pdf' % (
            subject_dir,
            config.get('templates.compiled_dir'),
            subject.lower(),
            today.day,
            today.month,
            today.year % 1000
        )

        os.chdir(subject_dir)

        command = "pandoc -o %s " % compiled_path

        for year in years:

            year_dir = '%s/%s/%s%d' % (
                subject_dir,
                config.get('templates.notes_dir'),
                config.get('templates.year_dir_prefix'),
                year % 1000
            )

            if not os.path.isdir(year_dir):
                print ("Subject %s has no notes for year %d" % (subject, year))
                continue

            for month in months:

                month_dir = '%s/%s%02d' % (
                    year_dir,
                    config.get('templates.month_dir_prefix'),
                    month
                )

                if not os.path.isdir(month_dir):
                    print ("Subject %s has no notes for month %d in year %d" % (
                        subject,
                        month,
                        year
                    ))
                    continue

                command += '%s/%s*.md ' % (
                    month_dir,
                    config.get('templates.note_prefix')
                )

        cmd = shlex.split(command)
        subprocess.call(cmd)

        return True
<|endoftext|>


<commit_before># -*- encoding: utf-8 -*-
##############################################################################
#
#    open2bizz
#    Copyright (C) 2016 open2bizz (open2bizz.nl).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from odoo import models, fields, api
from odoo.exceptions import ValidationError

from .. import services

import threading
import logging
_logger = logging.getLogger(__name__)

ORBEON_PERSISTENCE_SERVER_PREFIX = 'orbeon.persistence.server'
ORBEON_PERSISTENCE_SERVER_INTERFACE = '127.0.0.1'

PERSISTENCE_SERVER_SINGLE_THREADED = 'SINGLE_THREADED'
PERSISTENCE_SERVER_MULTI_THREADED = 'MULTI_THREADED'
PERSISTENCE_SERVER_FORKING = 'FORKING'

class OrbeonThreadedWSGIServer(threading.Thread):

    def __init__(self, name, server, stopper):
        super(OrbeonThreadedWSGIServer, self).__init__(name=name)

        self.local = threading.local()
        self.local.server = server
        self.local.stopper = stopper
        self.server = self.local.server
        self.stopper = self.local.stopper

    def run(self):
        while not self.stopper.is_set():
            try:
                self.server.serve_forever()
            except:
                pass

class OrbeonServer(models.Model):
    _name = "orbeon.server"
    _rec_name = "url"

    name = fields.Char(
        "Name",
        required=True,
    )
    url = fields.Char(
        "URL",
        required=True,
    )
    summary_url = fields.Char(
        "Summary URL",
        compute="_set_summary_url",
        store=True,
    )
    description = fields.Text(
        "Description"
    )

    persistence_server_port = fields.Char(
        "Port"
    )

    persistence_server_processtype = fields.Selection(
        [
            (PERSISTENCE_SERVER_SINGLE_THREADED, "Single threaded"),
            (PERSISTENCE_SERVER_MULTI_THREADED, "Multi threaded"),
            (PERSISTENCE_SERVER_FORKING, "Forking (process)"),
        ],
        "Process-type",
        default=PERSISTENCE_SERVER_SINGLE_THREADED,
        required=True
    )

    persistence_server_configfilename = fields.Char(
        "Config-filename"
    )

    persistence_server_active = fields.Boolean(
        "Active",
        default=False,
        help="Provisioning the persistence-server is enabled if active is checked (True).",
    )

    persistence_server_autostart = fields.Boolean(
        "Autostart",
        default=False,
        help="Ensures the persistence-server will be started right after Odoo starts"
    )

    persistence_server_uuid = fields.Char(
        "UUID (thread)",
        compute='_persistence_server_uuid',
        store=True
    )

    default_builder_xml = fields.Text(
        "Default Builder XML",
        help="Boilerplate XML (copied from the Orbeon<VERSION> server)",
        required=True
    )

    def __init__(self, pool, cr):
        res = super(OrbeonServer, self).__init__(pool, cr)
        self._autostart_persistence_servers(pool, cr)
        return res

    @api.one
    @api.depends("url")
    def _set_summary_url(self):
        if self.url:
            url = "%s/fr/orbeon/builder/summary" % self.url
        else:
            url = "Enter URL"
        self.summary_url = url

    @api.constrains("name")
    def constraint_unique_name(self):
        cur_record = self.search([("name","=",self.name)])
        if len(cur_record) > 1:
            raise ValidationError("Server with name '%s' already exists!" % self.name)

    @api.constrains("url")
    def constraint_unique_url(self):
        cur_record = self.search([("url","=",self.url)])
        if len(cur_record) > 1:
            raise ValidationError("Server with URL '%s' already exists!" % self.url)

    @api.multi
    def action_start_persistence_server(self, context=None, *args, **kwargs):
        try:
            uuid = self._persistence_server_uuid()

            self._start_persistence_server(
                uuid,
                self.persistence_server_port,
                self.persistence_server_processtype,
                self.persistence_server_configfilename
            )
            self.persistence_server_uuid = uuid
            return True
        except Exception, e:
            _logger.error('Exception: %s' % e)

    @api.multi
    def action_stop_persistence_server(self, context=None, *args, **kwargs):
        self._stop_persistence_server(self.persistence_server_uuid, self.persistence_server_port)
        self.persistence_server_uuid = None
        return True

    def _persistence_server_uuid(self):
        from uuid import uuid4
        return uuid4()

    def _persistence_wsgi_server(self, processtype):
        from werkzeug.serving import BaseWSGIServer, ThreadedWSGIServer, ForkingWSGIServer

        if processtype == PERSISTENCE_SERVER_SINGLE_THREADED:
            return BaseWSGIServer
        elif processtype == PERSISTENCE_SERVER_MULTI_THREADED:
            return ThreadedWSGIServer
        elif processtype == PERSISTENCE_SERVER_FORKING:
            return ForkingWSGIServer

    def _autostart_persistence_servers(self, pool, cr):
        # Force clear (uuid) which ensures a clean start
        cr.execute("UPDATE orbeon_server SET persistence_server_uuid = NULL ")

        try:
            cr.execute(
                "SELECT "
                "    id, "
                "    persistence_server_active AS active,"
                "    persistence_server_autostart AS autostart,"
                "    persistence_server_uuid AS uuid,"
                "    persistence_server_port AS port,"
                "    persistence_server_processtype AS processtype,"
                "    persistence_server_configfilename AS configfilename"
                "  FROM"
                "    orbeon_server "
                "  WHERE "
                "    persistence_server_autostart = True "
                "    AND persistence_server_active = True "
            )

            for (id,active,autostart,uuid,port,processtype,configfilename) in cr.fetchall():
                self._stop_persistence_server(uuid, port)

                new_uuid = self._persistence_server_uuid()
                self._start_persistence_server(
                    new_uuid,
                    port,
                    processtype,
                    configfilename
                )

                cr.execute("UPDATE orbeon_server SET persistence_server_uuid = %s WHERE id = %s", (str(new_uuid), id))

        except Exception, e:
            _logger.error("Exception: %s" % e)

    def _start_persistence_server(self, uuid, port, processtype, configfilename=None):
        app = services.wsgi_server.create_app(configfilename)
        wsgi_server = self._persistence_wsgi_server(processtype)
        wsgi_app_server = wsgi_server(ORBEON_PERSISTENCE_SERVER_INTERFACE, port, app)

        stopper = threading.Event()

        t = OrbeonThreadedWSGIServer(
            name=uuid,
            server=wsgi_app_server,
            stopper=stopper
        )

        t.setDaemon(True)
        _logger.info('Starting HTTP (werkzeug) %s (thread: %s) on port %s', ORBEON_PERSISTENCE_SERVER_PREFIX, uuid, port)
        t.start()

    def _stop_persistence_server(self, uuid, port):
        for thread in threading.enumerate():
            if thread.getName() == uuid: # TODO uuid
                thread.stopper.set()
                _logger.info("Stopping HTTP (werkzeug) %s (thread: %s) on port %s", ORBEON_PERSISTENCE_SERVER_PREFIX, uuid, port)
                thread.server.server_close()
                thread.join()
<commit_msg>[t#2910] Some help(text) about the (orbeon.sever) persistence_server_configfilename field.<commit_after># -*- encoding: utf-8 -*-
##############################################################################
#
#    open2bizz
#    Copyright (C) 2016 open2bizz (open2bizz.nl).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from odoo import models, fields, api
from odoo.exceptions import ValidationError

from .. import services

import threading
import logging
_logger = logging.getLogger(__name__)

ORBEON_PERSISTENCE_SERVER_PREFIX = 'orbeon.persistence.server'
ORBEON_PERSISTENCE_SERVER_INTERFACE = '127.0.0.1'

PERSISTENCE_SERVER_SINGLE_THREADED = 'SINGLE_THREADED'
PERSISTENCE_SERVER_MULTI_THREADED = 'MULTI_THREADED'
PERSISTENCE_SERVER_FORKING = 'FORKING'

class OrbeonThreadedWSGIServer(threading.Thread):

    def __init__(self, name, server, stopper):
        super(OrbeonThreadedWSGIServer, self).__init__(name=name)

        self.local = threading.local()
        self.local.server = server
        self.local.stopper = stopper
        self.server = self.local.server
        self.stopper = self.local.stopper

    def run(self):
        while not self.stopper.is_set():
            try:
                self.server.serve_forever()
            except:
                pass

class OrbeonServer(models.Model):
    _name = "orbeon.server"
    _rec_name = "url"

    name = fields.Char(
        "Name",
        required=True,
    )
    url = fields.Char(
        "URL",
        required=True,
    )
    summary_url = fields.Char(
        "Summary URL",
        compute="_set_summary_url",
        store=True,
    )
    description = fields.Text(
        "Description"
    )

    persistence_server_port = fields.Char(
        "Port"
    )

    persistence_server_processtype = fields.Selection(
        [
            (PERSISTENCE_SERVER_SINGLE_THREADED, "Single threaded"),
            (PERSISTENCE_SERVER_MULTI_THREADED, "Multi threaded"),
            (PERSISTENCE_SERVER_FORKING, "Forking (process)"),
        ],
        "Process-type",
        default=PERSISTENCE_SERVER_SINGLE_THREADED,
        required=True
    )

    persistence_server_configfilename = fields.Char(
        "Config-filename",
        help="If specified, the Odoo connection is setup with its config. "
        "If blank, the Orbeon HTTP-Headers will be used"
    )

    persistence_server_active = fields.Boolean(
        "Active",
        default=False,
        help="Provisioning the persistence-server is enabled if active is checked (True).",
    )

    persistence_server_autostart = fields.Boolean(
        "Autostart",
        default=False,
        help="Ensures the persistence-server will be started right after Odoo starts"
    )

    persistence_server_uuid = fields.Char(
        "UUID (thread)",
        compute='_persistence_server_uuid',
        store=True
    )

    default_builder_xml = fields.Text(
        "Default Builder XML",
        help="Boilerplate XML (copied from the Orbeon<VERSION> server)",
        required=True
    )

    def __init__(self, pool, cr):
        res = super(OrbeonServer, self).__init__(pool, cr)
        self._autostart_persistence_servers(pool, cr)
        return res

    @api.one
    @api.depends("url")
    def _set_summary_url(self):
        if self.url:
            url = "%s/fr/orbeon/builder/summary" % self.url
        else:
            url = "Enter URL"
        self.summary_url = url

    @api.constrains("name")
    def constraint_unique_name(self):
        cur_record = self.search([("name","=",self.name)])
        if len(cur_record) > 1:
            raise ValidationError("Server with name '%s' already exists!" % self.name)

    @api.constrains("url")
    def constraint_unique_url(self):
        cur_record = self.search([("url","=",self.url)])
        if len(cur_record) > 1:
            raise ValidationError("Server with URL '%s' already exists!" % self.url)

    @api.multi
    def action_start_persistence_server(self, context=None, *args, **kwargs):
        try:
            uuid = self._persistence_server_uuid()

            self._start_persistence_server(
                uuid,
                self.persistence_server_port,
                self.persistence_server_processtype,
                self.persistence_server_configfilename
            )
            self.persistence_server_uuid = uuid
            return True
        except Exception, e:
            _logger.error('Exception: %s' % e)

    @api.multi
    def action_stop_persistence_server(self, context=None, *args, **kwargs):
        self._stop_persistence_server(self.persistence_server_uuid, self.persistence_server_port)
        self.persistence_server_uuid = None
        return True

    def _persistence_server_uuid(self):
        from uuid import uuid4
        return uuid4()

    def _persistence_wsgi_server(self, processtype):
        from werkzeug.serving import BaseWSGIServer, ThreadedWSGIServer, ForkingWSGIServer

        if processtype == PERSISTENCE_SERVER_SINGLE_THREADED:
            return BaseWSGIServer
        elif processtype == PERSISTENCE_SERVER_MULTI_THREADED:
            return ThreadedWSGIServer
        elif processtype == PERSISTENCE_SERVER_FORKING:
            return ForkingWSGIServer

    def _autostart_persistence_servers(self, pool, cr):
        # Force clear (uuid) which ensures a clean start
        cr.execute("UPDATE orbeon_server SET persistence_server_uuid = NULL ")

        try:
            cr.execute(
                "SELECT "
                "    id, "
                "    persistence_server_active AS active,"
                "    persistence_server_autostart AS autostart,"
                "    persistence_server_uuid AS uuid,"
                "    persistence_server_port AS port,"
                "    persistence_server_processtype AS processtype,"
                "    persistence_server_configfilename AS configfilename"
                "  FROM"
                "    orbeon_server "
                "  WHERE "
                "    persistence_server_autostart = True "
                "    AND persistence_server_active = True "
            )

            for (id,active,autostart,uuid,port,processtype,configfilename) in cr.fetchall():
                self._stop_persistence_server(uuid, port)

                new_uuid = self._persistence_server_uuid()
                self._start_persistence_server(
                    new_uuid,
                    port,
                    processtype,
                    configfilename
                )

                cr.execute("UPDATE orbeon_server SET persistence_server_uuid = %s WHERE id = %s", (str(new_uuid), id))

        except Exception, e:
            _logger.error("Exception: %s" % e)

    def _start_persistence_server(self, uuid, port, processtype, configfilename=None):
        app = services.wsgi_server.create_app(configfilename)
        wsgi_server = self._persistence_wsgi_server(processtype)
        wsgi_app_server = wsgi_server(ORBEON_PERSISTENCE_SERVER_INTERFACE, port, app)

        stopper = threading.Event()

        t = OrbeonThreadedWSGIServer(
            name=uuid,
            server=wsgi_app_server,
            stopper=stopper
        )

        t.setDaemon(True)
        _logger.info('Starting HTTP (werkzeug) %s (thread: %s) on port %s', ORBEON_PERSISTENCE_SERVER_PREFIX, uuid, port)
        t.start()

    def _stop_persistence_server(self, uuid, port):
        for thread in threading.enumerate():
            if thread.getName() == uuid: # TODO uuid
                thread.stopper.set()
                _logger.info("Stopping HTTP (werkzeug) %s (thread: %s) on port %s", ORBEON_PERSISTENCE_SERVER_PREFIX, uuid, port)
                thread.server.server_close()
                thread.join()
<|endoftext|>


<commit_before>from flask import Flask, render_template, url_for, jsonify, redirect, request
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from database_setup import Restaurant, Menu, connection_str

app = Flask(__name__)

engine = create_engine(connection_str)
DBSession = sessionmaker(bind=engine)
session = DBSession()

@app.route('/')
@app.route('/restaurants/')
def home():
    restaurants = session.query(Restaurant).all()
    return render_template('restaurants_list.html',
                           restaurants=restaurants,
                           edit_func=edit_restaurant)

def new_restaurant():
    return None

@app.route('/restaurants/<int:restaurant_id>/edit/')
def edit_restaurant(restaurant_id):
    return None

@app.route('/restaurants/<int:restaurant_id>/menu/<int:menu_id>/json/')
def restaurant_menu_item_JSON(restaurant_id, menu_id):
    item = session.query(Menu).filter_by(restaurant_id=restaurant_id, id=menu_id).one()
    return jsonify(MenuItem=item.serialize)

def delete_restaurant(restaurant_id):
    return None

@app.route('/restaurants/<int:restaurant_id>/menu/', methods=['POST', 'GET'])
def menu(restaurant_id):
    menu_items = session.query(Menu).filter_by(restaurant_id=restaurant_id).all()
    restaurant = session.query(Restaurant).filter_by(id=restaurant_id).one()
    return render_template('menu_list.html', menu=menu_items, restaurant=restaurant)

@app.route('/restaurants/<int:restaurant_id>/menu/add/')
def new_menu_item(restaurant_id):
    if request.method == 'POST':
        pass
    return redirect(url_for('menu', restaurant_id=restaurant_id))

@app.route('/restaurants/<int:restaurant_id>/menu/edit/', methods=['POST'])
def edit_menu_item(restaurant_id):
    if request.method == 'POST':
        menu_id = request.form['menu_id']
        name = request.form['name']
        item = session.query(Menu).filter_by(id=menu_id, restaurant_id=restaurant_id).one()
        item.name = name
        session.add(item)
        session.commit()
    return redirect(url_for('menu', restaurant_id=restaurant_id))

def delete_menu_item(restaurant_id, menu_id):
    return None

if __name__ == '__main__':
    app.debug = True
    app.run('0.0.0.0', port=5000)
<commit_msg>Feat: Added support for creating new menu items<commit_after>from flask import Flask, render_template, url_for, jsonify, redirect, request
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from database_setup import Restaurant, Menu, connection_str

app = Flask(__name__)

engine = create_engine(connection_str)
DBSession = sessionmaker(bind=engine)
session = DBSession()

@app.route('/')
@app.route('/restaurants/')
def home():
    restaurants = session.query(Restaurant).all()
    return render_template('restaurants_list.html',
                           restaurants=restaurants,
                           edit_func=edit_restaurant)

def new_restaurant():
    return None

@app.route('/restaurants/<int:restaurant_id>/edit/')
def edit_restaurant(restaurant_id):
    return None

@app.route('/restaurants/<int:restaurant_id>/menu/<int:menu_id>/json/')
def restaurant_menu_item_JSON(restaurant_id, menu_id):
    item = session.query(Menu).filter_by(restaurant_id=restaurant_id, id=menu_id).one()
    return jsonify(MenuItem=item.serialize)

def delete_restaurant(restaurant_id):
    return None

@app.route('/restaurants/<int:restaurant_id>/menu/', methods=['POST', 'GET'])
def menu(restaurant_id):
    menu_items = session.query(Menu).filter_by(restaurant_id=restaurant_id).all()
    restaurant = session.query(Restaurant).filter_by(id=restaurant_id).one()
    return render_template('menu_list.html', menu=menu_items, restaurant=restaurant)

@app.route('/restaurants/<int:restaurant_id>/menu/add/', methods=['POST'])
def new_menu_item(restaurant_id):
    if request.method == 'POST':
        m = Menu(name=request.form['name'],
                 description=request.form['desc'],
                 course=request.form['course'],
                 price=request.form['price'],
                 restaurant_id=restaurant_id)
        session.add(m)
        session.commit()
    return redirect(url_for('menu', restaurant_id=restaurant_id))

@app.route('/restaurants/<int:restaurant_id>/menu/edit/', methods=['POST'])
def edit_menu_item(restaurant_id):
    if request.method == 'POST':
        menu_id = request.form['menu_id']
        name = request.form['name']
        item = session.query(Menu).filter_by(id=menu_id, restaurant_id=restaurant_id).one()
        item.name = name
        session.add(item)
        session.commit()
    return redirect(url_for('menu', restaurant_id=restaurant_id))

def delete_menu_item(restaurant_id, menu_id):
    return None

if __name__ == '__main__':
    app.debug = True
    app.run('0.0.0.0', port=5000)
<|endoftext|>


<commit_before>r""" Master Server Query Protocol

This module implements the legacy Steam master server protocol.

https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol

Nowadays games query servers through Steam. See :any:`steam.client.builtins.gameservers`

Filters
-------

.. note::
    Multiple filters can be joined to together (Eg. ``\app\730\white\1\empty\1``)

=========================== =========================================================================================================================
Filter code                 What it does
=========================== =========================================================================================================================
\\nor\\[x]                  A special filter, specifies that servers matching any of the following [x] conditions should not be returned
\\nand\\[x]                 A special filter, specifies that servers matching all of the following [x] conditions should not be returned
\\dedicated\\1              Servers running dedicated
\\secure\\1                 Servers using anti-cheat technology (VAC, but potentially others as well)
\\gamedir\\[mod]            Servers running the specified modification (ex. cstrike)
\\map\\[map]                Servers running the specified map (ex. cs_italy)
\\linux\\1                  Servers running on a Linux platform
\\password\\0               Servers that are not password protected
\\empty\\1                  Servers that are not empty
\\full\\1                   Servers that are not full
\\proxy\\1                  Servers that are spectator proxies
\\appid\\[appid]            Servers that are running game [appid]
\\napp\\[appid]             Servers that are NOT running game [appid] (This was introduced to block Left 4 Dead games from the Steam Server Browser)
\\noplayers\\1              Servers that are empty
\\white\\1                  Servers that are whitelisted
\\gametype\\[tag,...]       Servers with all of the given tag(s) in sv_tags
\\gamedata\\[tag,...]       Servers with all of the given tag(s) in their 'hidden' tags (L4D2)
\\gamedataor\\[tag,...]     Servers with any of the given tag(s) in their 'hidden' tags (L4D2)
\\name_match\\[hostname]    Servers with their hostname matching [hostname] (can use * as a wildcard)
\\version_match\\[version]  Servers running version [version] (can use * as a wildcard)
\\collapse_addr_hash\\1     Return only one server for each unique IP address matched
\\gameaddr\\[ip]            Return only servers on the specified IP address (port supported and optional)
=========================== =========================================================================================================================
"""
import socket
import struct
from time import sleep
from enum import IntEnum, Enum
from steam.util.binary import StructReader

class MSRegion(IntEnum):
    US_East = 0x00
    US_West = 0x01
    South_America = 0x02
    Europe = 0x03
    Asia = 0x04
    Australia = 0x05
    Middle_East = 0x06
    Africa = 0x07
    World = 0xFF

class MSServer:
    GoldSrc = ('hl1master.steampowered.com', 27010)  #: These have been shutdown
    Source = ('hl2master.steampowered.com', 27011)
    Source_27015 = ('208.64.200.65', 27015)          #: ``hl2master`` but on different port

def query(filter_text=r'\napp\500', region=MSRegion.World, master=MSServer.Source):
    r"""Generator that returns (IP,port) pairs of serveras

    .. warning::
        Valve's master servers seem to be heavily rate limited.
        Queries that return a large numbers IPs will timeout before returning everything.
        There is no way to resume the query.
        Use :class:`SteamClient` to access game servers in a reliable way.

    .. note::
        When specifying ``filter_text`` use *raw strings* otherwise python won't treat backslashes
        as literal characters (e.g. ``query(r'\appid\730\white\1')``)

    :param filter_text: filter for servers
    :type  filter_text: str
    :param region: (optional) region code
    :type  region: :class:`.MSRegion`
    :param master: (optional) master server to query
    :type  master: (:class:`str`, :class:`int`)
    :raises: This function will raise in various situations
    :returns: a generator yielding (ip, port) pairs
    :rtype: :class:`generator`
    """

    if not isinstance(region, MSRegion):
        raise TypeError("region_code is not of type MSRegion")

    ms = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    ms.connect(master)
    ms.settimeout(8)

    next_ip = b'0.0.0.0:0'
    req_prefix = b'1' + struct.pack('>B', region)
    req_suffix = b'\x00' + filter_text.encode('utf-8') + b'\x00'

    while True:
        ms.send(req_prefix + next_ip + req_suffix)

        data = ms.recv(2048)
        data = StructReader(data)

        # verify response header
        if data.read(6) != b'\xFF\xFF\xFF\xFF\x66\x0A':
            raise RuntimeError("Invalid response from master server")

        # read list of servers
        while data.rlen():
            ip = '.'.join(map(str, data.unpack('>BBBB')))
            port, = data.unpack('>H')

            # check if we've reach the end of the list
            if ip == '0.0.0.0' and port == 0:
                return

            yield ip, port

        next_ip = '{}:{}'.format(ip, port).encode('utf-8')
<commit_msg>add a2s_info, a2s_player, a2s_ping<commit_after>r""" Master Server Query Protocol

This module implements the legacy Steam master server protocol.

https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol

Nowadays games query servers through Steam. See :any:`steam.client.builtins.gameservers`

Filters
-------

.. note::
    Multiple filters can be joined to together (Eg. ``\app\730\white\1\empty\1``)

=========================== =========================================================================================================================
Filter code                 What it does
=========================== =========================================================================================================================
\\nor\\[x]                  A special filter, specifies that servers matching any of the following [x] conditions should not be returned
\\nand\\[x]                 A special filter, specifies that servers matching all of the following [x] conditions should not be returned
\\dedicated\\1              Servers running dedicated
\\secure\\1                 Servers using anti-cheat technology (VAC, but potentially others as well)
\\gamedir\\[mod]            Servers running the specified modification (ex. cstrike)
\\map\\[map]                Servers running the specified map (ex. cs_italy)
\\linux\\1                  Servers running on a Linux platform
\\password\\0               Servers that are not password protected
\\empty\\1                  Servers that are not empty
\\full\\1                   Servers that are not full
\\proxy\\1                  Servers that are spectator proxies
\\appid\\[appid]            Servers that are running game [appid]
\\napp\\[appid]             Servers that are NOT running game [appid] (This was introduced to block Left 4 Dead games from the Steam Server Browser)
\\noplayers\\1              Servers that are empty
\\white\\1                  Servers that are whitelisted
\\gametype\\[tag,...]       Servers with all of the given tag(s) in sv_tags
\\gamedata\\[tag,...]       Servers with all of the given tag(s) in their 'hidden' tags (L4D2)
\\gamedataor\\[tag,...]     Servers with any of the given tag(s) in their 'hidden' tags (L4D2)
\\name_match\\[hostname]    Servers with their hostname matching [hostname] (can use * as a wildcard)
\\version_match\\[version]  Servers running version [version] (can use * as a wildcard)
\\collapse_addr_hash\\1     Return only one server for each unique IP address matched
\\gameaddr\\[ip]            Return only servers on the specified IP address (port supported and optional)
=========================== =========================================================================================================================
"""
import socket
from struct import pack as _pack, unpack_from as _unpack_from
from time import time as _time
from enum import IntEnum, Enum
from steam.util.binary import StructReader

class MSRegion(IntEnum):
    US_East = 0x00
    US_West = 0x01
    South_America = 0x02
    Europe = 0x03
    Asia = 0x04
    Australia = 0x05
    Middle_East = 0x06
    Africa = 0x07
    World = 0xFF

class MSServer:
    GoldSrc = ('hl1master.steampowered.com', 27010)  #: These have been shutdown
    Source = ('hl2master.steampowered.com', 27011)
    Source_27015 = ('208.64.200.65', 27015)          #: ``hl2master`` but on different port

def query(filter_text=r'\napp\500', region=MSRegion.World, master=MSServer.Source):
    r"""Generator that returns (IP,port) pairs of serveras

    .. warning::
        Valve's master servers seem to be heavily rate limited.
        Queries that return a large numbers IPs will timeout before returning everything.
        There is no way to resume the query.
        Use :class:`SteamClient` to access game servers in a reliable way.

    .. note::
        When specifying ``filter_text`` use *raw strings* otherwise python won't treat backslashes
        as literal characters (e.g. ``query(r'\appid\730\white\1')``)

    :param filter_text: filter for servers
    :type  filter_text: str
    :param region: (optional) region code
    :type  region: :class:`.MSRegion`
    :param master: (optional) master server to query
    :type  master: (:class:`str`, :class:`int`)
    :raises: This function will raise in various situations
    :returns: a generator yielding (ip, port) pairs
    :rtype: :class:`generator`
    """

    if not isinstance(region, MSRegion):
        raise TypeError("region_code is not of type MSRegion")

    ms = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    ms.connect(master)
    ms.settimeout(8)

    next_ip = b'0.0.0.0:0'
    req_prefix = b'1' + _pack('>B', region)
    req_suffix = b'\x00' + filter_text.encode('utf-8') + b'\x00'

    while True:
        ms.send(req_prefix + next_ip + req_suffix)

        data = ms.recv(2048)
        data = StructReader(data)

        # verify response header
        if data.read(6) != b'\xFF\xFF\xFF\xFF\x66\x0A':
            raise RuntimeError("Invalid response from master server")

        # read list of servers
        while data.rlen():
            ip = '.'.join(map(str, data.unpack('>BBBB')))
            port, = data.unpack('>H')

            # check if we've reach the end of the list
            if ip == '0.0.0.0' and port == 0:
                return

            yield ip, port

        next_ip = '{}:{}'.format(ip, port).encode('utf-8')

def _handle_a2s_response(sock):
    packet = sock.recv(2048)
    header, = _unpack_from('<l', packet)

    if header == -1:  # single packet response
        return packet[4:]
    elif header == -2:  # multi packet response
        raise RuntimeError("Multi packet response not implemented yet")
    else:
        raise RuntimeError("Invalid reponse header")

def a2s_info(server_addr, goldsrc=False, timeout=6):
    """Get information from a server

    :param server_addr: (ip, port) for the server
    :type  server_addr: tuple
    :param goldsrc: (optional) Weather to expect GoldSrc or Source response format
    :type  goldsrc: :class:`bool`
    :param timeout: (optional) timeout in seconds
    :type  timeout: int
    :returns: a dict with information or `None` on timeout
    :rtype: :class:`dict`, :class:`None`
    """
    ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    ss.connect(server_addr)
    ss.settimeout(timeout)

    # request server info
    ss.send(_pack('<lc', -1, b'T') + b'Source Engine Query\x00')
    resp_header = b'm' if goldsrc else b'I'

    while True:
        try:
            data = _handle_a2s_response(ss)
        except socket.timeout:
            return None
        else:
            if data[0:1] == resp_header:
                break

    data = StructReader(data)
    data.skip(1)  # header

    # GoldSrc response
    if goldsrc:
        info = {
            'address': data.read_cstring(),
            'name': data.read_cstring(),
            'map': data.read_cstring(),
            'folder': data.read_cstring(),
            'game': data.read_cstring(),
        }

        (info['players'],
         info['max_players'],
         info['protocol'],
         info['server_type'],
         info['environment'],
         info['visibility'],
         info['mod'],
         ) = data.unpack('<BBBccBB')

        if info['mod'] == 1:
            info['link'] = data.read_cstring(),
            info['download_link'] = data.read_cstring(),

            (info['version'],
             info['size'],
             info['type'],
             info['ddl'],
             ) = data.unpack('<xLLBB')

        info['vac'], info['bots'] = data.unpack('<BB')
    # Source response
    else:
        info = {
            'protocol': data.unpack('<b')[0],
            'name': data.read_cstring(),
            'map': data.read_cstring(),
            'folder': data.read_cstring(),
            'game': data.read_cstring(),
        }

        (info['app_id'],
         info['players'],
         info['max_players'],
         info['bots'],
         info['server_type'],
         info['environment'],
         info['visibility'],
         info['vac'],
         ) = data.unpack('<HBBBccBB')

    return info

def a2s_player(server_addr, challenge=0, timeout=8):
    """Get list of players and their info

    :param server_addr: (ip, port) for the server
    :type  server_addr: tuple
    :param challenge: (optional) challenge number
    :type  challenge: int
    :param timeout: (optional) timeout in seconds
    :type  timeout: int
    :returns: a list of players
    :rtype: :class:`list`, :class:`None`
    """
    ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    ss.connect(server_addr)
    ss.settimeout(timeout)

    # request challenge number
    if challenge in (-1, 0):
        ss.send(_pack('<lci', -1, b'U', challenge))
        _, header, challange = _unpack_from('<lcl', ss.recv(512))

        if header != b'A':
            raise RuntimeError("Unexpected challange response")

    # request player info
    ss.send(_pack('<lci', -1, b'U', challange))

    try:
        data = StructReader(_handle_a2s_response(ss))
    except socket.timeout:
        return None

    header, num_players = data.unpack('<BB')

    players = []

    while len(players) != num_players:
        player = dict()
        player['index'] = data.unpack('<B')[0]
        player['name'] = data.read_cstring()
        player['score'], player['duration'] = data.unpack('<lf')
        players.append(player)

    if data.rlen() / 8 == num_players:  # assume the ship server
        for player in players:
            player['deaths'], player['money'] = data.unpack('<ll')

    return players

def a2s_ping(server_addr, timeout=8):
    """Ping a server

    .. warning::
        This method for pinging is considered deprecated and will not work on newer sources games

    :param server_addr: (ip, port) for the server
    :type  server_addr: tuple
    :param timeout: (optional) timeout in seconds
    :type  timeout: int
    :returns: ping response in seconds or `None` for timeout
    :rtype: :class:`float`, :class:`None`
    """
    ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    ss.connect(server_addr)
    ss.settimeout(timeout)

    ss.send(_pack('<lc', -1, b'i'))
    start = _time()

    try:
        data = _handle_a2s_response(ss)
    except socket.timeout:
        return None

    diff = _time() - start

    if data[0:1] == b'j':
        return diff
<|endoftext|>