<commit_before># coding=utf-8
from __future__ import absolute_import, division, print_function

__author__ = "<NAME> <<EMAIL>>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"

"""
The SSDP/UPNP implementations has been largely inspired by https://gist.github.com/schlamar/2428250
"""

import logging
import flask
from flask_babel import gettext

# noinspection PyCompatibility
from builtins import range

import octoprint.plugin
import octoprint.util

try:
	import pybonjour
except:
	pybonjour = False


def __plugin_load__():
	if not pybonjour:
		# no pybonjour available, we can't use that
		logging.getLogger("octoprint.plugins." + __name__).info("pybonjour is not installed, Zeroconf Discovery won't be available")

	plugin = DiscoveryPlugin()

	global __plugin_implementation__
	__plugin_implementation__ = plugin

	global __plugin_helpers__
	__plugin_helpers__ = dict(
		ssdp_browse=plugin.ssdp_browse
	)
	if pybonjour:
		__plugin_helpers__.update(dict(
			zeroconf_browse=plugin.zeroconf_browse,
			zeroconf_register=plugin.zeroconf_register,
			zeroconf_unregister=plugin.zeroconf_unregister
		))

class DiscoveryPlugin(octoprint.plugin.StartupPlugin,
                      octoprint.plugin.ShutdownPlugin,
                      octoprint.plugin.BlueprintPlugin,
                      octoprint.plugin.SettingsPlugin):

	ssdp_multicast_addr = "172.16.17.32"

	ssdp_multicast_port = 1900

	# noinspection PyMissingConstructor
	def __init__(self):
		self.host = None
		self.port = None

		# zeroconf
		self._sd_refs = dict()
		self._cnames = dict()

		# upnp/ssdp
		self._ssdp_monitor_active = False
		self._ssdp_monitor_thread = None
		self._ssdp_notify_timeout = 10
		self._ssdp_last_notify = 0

	##~~ SettingsPlugin API

	def get_settings_defaults(self):
		return {
			"publicHost": None,
			"publicPort": None,
			"pathPrefix": None,
			"httpUsername": None,
			"httpPassword": None,
			"upnpUuid": None,
			"zeroConf": [],
			"model": {
				"name": None,
				"description": None,
				"number": None,
				"url": None,
				"serial": None,
				"vendor": None,
				"vendorUrl": None
			}
		}

	##~~ BlueprintPlugin API -- used for providing the SSDP device descriptor XML

	@octoprint.plugin.BlueprintPlugin.route("/discovery.xml", methods=["GET"])
	def discovery(self):
		self._logger.debug("Rendering discovery.xml")

		modelName = self._settings.get(["model", "name"])
		if not modelName:
			import octoprint.server
			modelName = octoprint.server.DISPLAY_VERSION

		vendor = self._settings.get(["model", "vendor"])
		vendorUrl = self._settings.get(["model", "vendorUrl"])
		if not vendor:
			vendor = "The OctoPrint Project"
			vendorUrl = "http://www.octoprint.org/"

		response = flask.make_response(flask.render_template("discovery.xml.jinja2",
		                                                     friendlyName=self.get_instance_name(),
		                                                     manufacturer=vendor,
		                                                     manufacturerUrl=vendorUrl,
		                                                     modelName=modelName,
		                                                     modelDescription=self._settings.get(["model", "description"]),
		                                                     modelNumber=self._settings.get(["model", "number"]),
		                                                     modelUrl=self._settings.get(["model", "url"]),
		                                                     serialNumber=self._settings.get(["model", "serial"]),
		                                                     uuid=self.get_uuid(),
		                                                     presentationUrl=flask.url_for("index", _external=True)))
		response.headers['Content-Type'] = 'application/xml'
		return response

	def is_blueprint_protected(self):
		return False

	##~~ StartupPlugin API -- used for registering OctoPrint's Zeroconf and SSDP services upon application startup

	def on_startup(self, host, port):
		public_host = self._settings.get(["publicHost"])
		if public_host:
			host = public_host
		public_port = self._settings.get(["publicPort"])
		if public_port:
			port = public_port

		self.host = host
		self.port = port

		# Zeroconf
		self.zeroconf_register("_http._tcp", self.get_instance_name(), txt_record=self._create_http_txt_record_dict())
		self.zeroconf_register("_octoprint._tcp", self.get_instance_name(), txt_record=self._create_octoprint_txt_record_dict())
		for zeroconf in self._settings.get(["zeroConf"]):
			if "service" in zeroconf:
				self.zeroconf_register(
					zeroconf["service"],
					zeroconf["name"] if "name" in zeroconf else self.get_instance_name(),
					port=zeroconf["port"] if "port" in zeroconf else None,
					txt_record=zeroconf["txtRecord"] if "txtRecord" in zeroconf else None
				)

		# SSDP
		self._ssdp_register()

	##~~ ShutdownPlugin API -- used for unregistering OctoPrint's Zeroconf and SSDP service upon application shutdown

	def on_shutdown(self):
		for key in self._sd_refs:
			reg_type, port = key
			self.zeroconf_unregister(reg_type, port)

		self._ssdp_unregister()

	##~~ helpers

	# ZeroConf

	def zeroconf_register(self, reg_type, name=None, port=None, txt_record=None):
		"""
		Registers a new service with Zeroconf/Bonjour/Avahi.

		:param reg_type: type of service to register, e.g. "_gntp._tcp"
		:param name: displayable name of the service, if not given defaults to the OctoPrint instance name
		:param port: port to register for the service, if not given defaults to OctoPrint's (public) port
		:param txt_record: optional txt record to attach to the service, dictionary of key-value-pairs
		"""

		if not pybonjour:
			return

		if not name:
			name = self.get_instance_name()
		if not port:
			port = self.port

		params = dict(
			name=name,
			regtype=reg_type,
			port=port
		)
		if txt_record:
			params["txtRecord"] = pybonjour.TXTRecord(txt_record)

		key = (reg_type, port)
		self._sd_refs[key] = pybonjour.DNSServiceRegister(**params)
		self._logger.info(u"Registered {name} for {reg_type}".format(**locals()))

	def zeroconf_unregister(self, reg_type, port=None):
		"""
		Unregisteres a previously registered Zeroconf/Bonjour/Avahi service identified by service and port.
<commit_msg>discovery: enumerate zeroconf service names on collision<commit_after># coding=utf-8
from __future__ import absolute_import, division, print_function

__author__ = "<NAME> <<EMAIL>>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"

"""
The SSDP/UPNP implementations has been largely inspired by https://gist.github.com/schlamar/2428250
"""

import logging
import flask
from flask_babel import gettext

# noinspection PyCompatibility
from builtins import range

import octoprint.plugin
import octoprint.util

try:
	import pybonjour
except:
	pybonjour = False


def __plugin_load__():
	if not pybonjour:
		# no pybonjour available, we can't use that
		logging.getLogger("octoprint.plugins." + __name__).info("pybonjour is not installed, Zeroconf Discovery won't be available")

	plugin = DiscoveryPlugin()

	global __plugin_implementation__
	__plugin_implementation__ = plugin

	global __plugin_helpers__
	__plugin_helpers__ = dict(
		ssdp_browse=plugin.ssdp_browse
	)
	if pybonjour:
		__plugin_helpers__.update(dict(
			zeroconf_browse=plugin.zeroconf_browse,
			zeroconf_register=plugin.zeroconf_register,
			zeroconf_unregister=plugin.zeroconf_unregister
		))

class DiscoveryPlugin(octoprint.plugin.StartupPlugin,
                      octoprint.plugin.ShutdownPlugin,
                      octoprint.plugin.BlueprintPlugin,
                      octoprint.plugin.SettingsPlugin):

	ssdp_multicast_addr = "172.16.17.32"

	ssdp_multicast_port = 1900

	# noinspection PyMissingConstructor
	def __init__(self):
		self.host = None
		self.port = None

		# zeroconf
		self._sd_refs = dict()
		self._cnames = dict()

		# upnp/ssdp
		self._ssdp_monitor_active = False
		self._ssdp_monitor_thread = None
		self._ssdp_notify_timeout = 10
		self._ssdp_last_notify = 0

	##~~ SettingsPlugin API

	def get_settings_defaults(self):
		return {
			"publicHost": None,
			"publicPort": None,
			"pathPrefix": None,
			"httpUsername": None,
			"httpPassword": None,
			"upnpUuid": None,
			"zeroConf": [],
			"model": {
				"name": None,
				"description": None,
				"number": None,
				"url": None,
				"serial": None,
				"vendor": None,
				"vendorUrl": None
			}
		}

	##~~ BlueprintPlugin API -- used for providing the SSDP device descriptor XML

	@octoprint.plugin.BlueprintPlugin.route("/discovery.xml", methods=["GET"])
	def discovery(self):
		self._logger.debug("Rendering discovery.xml")

		modelName = self._settings.get(["model", "name"])
		if not modelName:
			import octoprint.server
			modelName = octoprint.server.DISPLAY_VERSION

		vendor = self._settings.get(["model", "vendor"])
		vendorUrl = self._settings.get(["model", "vendorUrl"])
		if not vendor:
			vendor = "The OctoPrint Project"
			vendorUrl = "http://www.octoprint.org/"

		response = flask.make_response(flask.render_template("discovery.xml.jinja2",
		                                                     friendlyName=self.get_instance_name(),
		                                                     manufacturer=vendor,
		                                                     manufacturerUrl=vendorUrl,
		                                                     modelName=modelName,
		                                                     modelDescription=self._settings.get(["model", "description"]),
		                                                     modelNumber=self._settings.get(["model", "number"]),
		                                                     modelUrl=self._settings.get(["model", "url"]),
		                                                     serialNumber=self._settings.get(["model", "serial"]),
		                                                     uuid=self.get_uuid(),
		                                                     presentationUrl=flask.url_for("index", _external=True)))
		response.headers['Content-Type'] = 'application/xml'
		return response

	def is_blueprint_protected(self):
		return False

	##~~ StartupPlugin API -- used for registering OctoPrint's Zeroconf and SSDP services upon application startup

	def on_startup(self, host, port):
		public_host = self._settings.get(["publicHost"])
		if public_host:
			host = public_host
		public_port = self._settings.get(["publicPort"])
		if public_port:
			port = public_port

		self.host = host
		self.port = port

		# Zeroconf
		self.zeroconf_register("_http._tcp", self.get_instance_name(), txt_record=self._create_http_txt_record_dict())
		self.zeroconf_register("_octoprint._tcp", self.get_instance_name(), txt_record=self._create_octoprint_txt_record_dict())
		for zeroconf in self._settings.get(["zeroConf"]):
			if "service" in zeroconf:
				self.zeroconf_register(
					zeroconf["service"],
					zeroconf["name"] if "name" in zeroconf else self.get_instance_name(),
					port=zeroconf["port"] if "port" in zeroconf else None,
					txt_record=zeroconf["txtRecord"] if "txtRecord" in zeroconf else None
				)

		# SSDP
		self._ssdp_register()

	##~~ ShutdownPlugin API -- used for unregistering OctoPrint's Zeroconf and SSDP service upon application shutdown

	def on_shutdown(self):
		for key in self._sd_refs:
			reg_type, port = key
			self.zeroconf_unregister(reg_type, port)

		self._ssdp_unregister()

	##~~ helpers

	# ZeroConf

	def zeroconf_register(self, reg_type, name=None, port=None, txt_record=None):
		"""
		Registers a new service with Zeroconf/Bonjour/Avahi.

		:param reg_type: type of service to register, e.g. "_gntp._tcp"
		:param name: displayable name of the service, if not given defaults to the OctoPrint instance name
		:param port: port to register for the service, if not given defaults to OctoPrint's (public) port
		:param txt_record: optional txt record to attach to the service, dictionary of key-value-pairs
		"""

		if not pybonjour:
			return

		if not name:
			name = self.get_instance_name()
		if not port:
			port = self.port

		params = dict(
			name=name,
			regtype=reg_type,
			port=port
		)
		if txt_record:
			params["txtRecord"] = pybonjour.TXTRecord(txt_record)

		key = (reg_type, port)

		counter = 0
		while True:
			try:
				self._sd_refs[key] = pybonjour.DNSServiceRegister(**params)
				self._logger.info(u"Registered '{name}' for {regtype}".format(**params))
				return True
			except pybonjour.BonjourError as be:
				if be.errorCode == pybonjour.kDNSServiceErr_NameConflict:
					# Name already registered by different service, let's try a counter postfix. See #2852
					counter += 1
					params["name"] = u"{} ({})".format(name, counter)
				else:
					raise

	def zeroconf_unregister(self, reg_type, port=None):
		"""
		Unregisteres a previously registered Zeroconf/Bonjour/Avahi service identified by service and port.

{'fraction_non_alphanumeric': 0.10268094478620794, 'fraction_numerical': 0.005012531328320802, 'mean_word_length': 3.0036485253876557, 'pattern_counts': {'":': 30, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 4, 'xml': 8}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 40, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4430569', 'n_tokens_mistral': 4520, 'n_tokens_neox': 3953, 'n_words': 935}