__init__.py
186 lines
| 6.2 KiB
| text/x-python
|
PythonLexer
|
r7071 | # zeroconf.py - zeroconf support for Mercurial | ||
# | ||||
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r7071 | |||
|
r8894 | '''discover and advertise repositories on the local network | ||
|
r7606 | |||
|
r11504 | Zeroconf-enabled repositories will be announced in a network without | ||
|
r8003 | the need to configure a server or a service. They can be discovered | ||
without knowing their actual IP address. | ||||
|
r7606 | |||
|
r11504 | To allow other people to discover your repository using run | ||
:hg:`serve` in your repository:: | ||||
|
r7606 | |||
|
r9218 | $ cd test | ||
$ hg serve | ||||
|
r7606 | |||
|
r11504 | You can discover Zeroconf-enabled repositories by running | ||
:hg:`paths`:: | ||||
|
r7606 | |||
|
r9218 | $ hg paths | ||
zc-test = http://example.com:8000/test | ||||
|
r7606 | ''' | ||
|
r11340 | import socket, time, os | ||
import Zeroconf | ||||
|
r14104 | from mercurial import ui, hg, encoding, util, dispatch | ||
|
r7216 | from mercurial import extensions | ||
|
r7071 | from mercurial.hgweb import hgweb_mod | ||
from mercurial.hgweb import hgwebdir_mod | ||||
# publish | ||||
server = None | ||||
localip = None | ||||
def getip(): | ||||
# finds external-facing interface without sending any packets (Linux) | ||||
try: | ||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||||
s.connect(('1.0.0.1', 0)) | ||||
ip = s.getsockname()[0] | ||||
return ip | ||||
|
r16688 | except socket.error: | ||
|
r7071 | pass | ||
# Generic method, sometimes gives useless results | ||||
|
r8264 | try: | ||
dumbip = socket.gethostbyaddr(socket.gethostname())[2][0] | ||||
if not dumbip.startswith('127.') and ':' not in dumbip: | ||||
return dumbip | ||||
|
r10317 | except (socket.gaierror, socket.herror): | ||
|
r8264 | dumbip = '127.0.0.1' | ||
|
r7071 | |||
# works elsewhere, but actually sends a packet | ||||
try: | ||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||||
s.connect(('1.0.0.1', 1)) | ||||
ip = s.getsockname()[0] | ||||
return ip | ||||
|
r16688 | except socket.error: | ||
|
r7071 | pass | ||
return dumbip | ||||
def publish(name, desc, path, port): | ||||
global server, localip | ||||
if not server: | ||||
|
r8264 | ip = getip() | ||
if ip.startswith('127.'): | ||||
|
r7295 | # if we have no internet connection, this can happen. | ||
return | ||||
|
r7071 | localip = socket.inet_aton(ip) | ||
|
r8264 | server = Zeroconf.Zeroconf(ip) | ||
|
r7071 | |||
|
r7845 | hostname = socket.gethostname().split('.')[0] | ||
host = hostname + ".local" | ||||
name = "%s-%s" % (hostname, name) | ||||
|
r7071 | |||
# advertise to browsers | ||||
svc = Zeroconf.ServiceInfo('_http._tcp.local.', | ||||
name + '._http._tcp.local.', | ||||
server = host, | ||||
port = port, | ||||
properties = {'description': desc, | ||||
'path': "/" + path}, | ||||
address = localip, weight = 0, priority = 0) | ||||
server.registerService(svc) | ||||
# advertise to Mercurial clients | ||||
svc = Zeroconf.ServiceInfo('_hg._tcp.local.', | ||||
name + '._hg._tcp.local.', | ||||
|
r7088 | server = host, | ||
|
r7071 | port = port, | ||
properties = {'description': desc, | ||||
'path': "/" + path}, | ||||
address = localip, weight = 0, priority = 0) | ||||
server.registerService(svc) | ||||
class hgwebzc(hgweb_mod.hgweb): | ||||
|
r11005 | def __init__(self, repo, name=None, baseui=None): | ||
super(hgwebzc, self).__init__(repo, name=name, baseui=baseui) | ||||
name = self.reponame or os.path.basename(self.repo.root) | ||||
|
r9442 | path = self.repo.ui.config("web", "prefix", "").strip('/') | ||
|
r7071 | desc = self.repo.ui.config("web", "description", name) | ||
|
r11005 | publish(name, desc, path, | ||
|
r12076 | util.getport(self.repo.ui.config("web", "port", 8000))) | ||
|
r7071 | |||
class hgwebdirzc(hgwebdir_mod.hgwebdir): | ||||
|
r9442 | def __init__(self, conf, baseui=None): | ||
|
r10993 | super(hgwebdirzc, self).__init__(conf, baseui=baseui) | ||
|
r9442 | prefix = self.ui.config("web", "prefix", "").strip('/') + '/' | ||
|
r9488 | for repo, path in self.repos: | ||
|
r8191 | u = self.ui.copy() | ||
|
r9488 | u.readconfig(os.path.join(path, '.hg', 'hgrc')) | ||
name = os.path.basename(repo) | ||||
path = (prefix + repo).strip('/') | ||||
|
r9489 | desc = u.config('web', 'description', name) | ||
|
r16683 | publish(name, desc, path, | ||
util.getport(u.config("web", "port", 8000))) | ||||
|
r7071 | |||
# listen | ||||
class listener(object): | ||||
def __init__(self): | ||||
self.found = {} | ||||
def removeService(self, server, type, name): | ||||
if repr(name) in self.found: | ||||
del self.found[repr(name)] | ||||
def addService(self, server, type, name): | ||||
self.found[repr(name)] = server.getServiceInfo(type, name) | ||||
def getzcpaths(): | ||||
|
r8264 | ip = getip() | ||
if ip.startswith('127.'): | ||||
return | ||||
server = Zeroconf.Zeroconf(ip) | ||||
|
r7071 | l = listener() | ||
|
r7874 | Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l) | ||
|
r7071 | time.sleep(1) | ||
server.close() | ||||
|
r9488 | for value in l.found.values(): | ||
name = value.name[:value.name.index('.')] | ||||
url = "http://%s:%s%s" % (socket.inet_ntoa(value.address), value.port, | ||||
value.properties.get("path", "/")) | ||||
yield "zc-" + name, url | ||||
|
r7071 | |||
|
r7216 | def config(orig, self, section, key, default=None, untrusted=False): | ||
|
r7071 | if section == "paths" and key.startswith("zc-"): | ||
|
r9488 | for name, path in getzcpaths(): | ||
if name == key: | ||||
return path | ||||
|
r7216 | return orig(self, section, key, default, untrusted) | ||
|
r7071 | |||
|
r7238 | def configitems(orig, self, section, untrusted=False): | ||
|
r9488 | repos = orig(self, section, untrusted) | ||
|
r7071 | if section == "paths": | ||
|
r9488 | repos += getzcpaths() | ||
return repos | ||||
|
r7071 | |||
|
r10342 | def defaultdest(orig, source): | ||
for name, path in getzcpaths(): | ||||
if path == source: | ||||
return name.encode(encoding.encoding) | ||||
return orig(source) | ||||
|
r14104 | def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc): | ||
try: | ||||
return orig(ui, options, cmd, cmdfunc) | ||||
finally: | ||||
# we need to call close() on the server to notify() the various | ||||
# threading Conditions and allow the background threads to exit | ||||
global server | ||||
if server: | ||||
server.close() | ||||
extensions.wrapfunction(dispatch, '_runcommand', cleanupafterdispatch) | ||||
|
r7216 | extensions.wrapfunction(ui.ui, 'config', config) | ||
extensions.wrapfunction(ui.ui, 'configitems', configitems) | ||||
|
r10342 | extensions.wrapfunction(hg, 'defaultdest', defaultdest) | ||
|
r7071 | hgweb_mod.hgweb = hgwebzc | ||
hgwebdir_mod.hgwebdir = hgwebdirzc | ||||