__init__.py
215 lines
| 6.9 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. | ||
|
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 | ''' | ||
|
r28296 | from __future__ import absolute_import | ||
|
r7606 | |||
|
r28296 | import os | ||
import socket | ||||
import time | ||||
|
r11340 | |||
|
r28296 | from . import Zeroconf | ||
from mercurial import ( | ||||
dispatch, | ||||
encoding, | ||||
extensions, | ||||
hg, | ||||
|
r28308 | ui as uimod, | ||
|
r28296 | ) | ||
from mercurial.hgweb import ( | ||||
server as servermod | ||||
) | ||||
|
r7071 | |||
|
r29841 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | ||
|
r25186 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | ||
# be specifying the version(s) of Mercurial they are tested with, or | ||||
# leave the attribute unspecified. | ||||
|
r29841 | testedwith = 'ships-with-hg-core' | ||
|
r16743 | |||
|
r7071 | # 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] | ||||
|
r30885 | if ':' in dumbip: | ||
dumbip = '127.0.0.1' | ||||
if not dumbip.startswith('127.'): | ||||
|
r8264 | 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) | ||||
|
r18190 | def zc_create_server(create_server, ui, app): | ||
httpd = create_server(ui, app) | ||||
port = httpd.port | ||||
|
r7071 | |||
|
r18190 | try: | ||
repos = app.repos | ||||
except AttributeError: | ||||
# single repo | ||||
|
r27910 | with app._obtainrepo() as repo: | ||
name = app.reponame or os.path.basename(repo.root) | ||||
path = repo.ui.config("web", "prefix", "").strip('/') | ||||
desc = repo.ui.config("web", "description", name) | ||||
|
r18190 | publish(name, desc, path, port) | ||
else: | ||||
# webdir | ||||
prefix = app.ui.config("web", "prefix", "").strip('/') + '/' | ||||
for repo, path in repos: | ||||
u = app.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) | ||
|
r18190 | publish(name, desc, path, port) | ||
return httpd | ||||
|
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 | |||
r33175 | def config(orig, self, section, key, *args, **kwargs): | |||
|
r7071 | if section == "paths" and key.startswith("zc-"): | ||
|
r9488 | for name, path in getzcpaths(): | ||
if name == key: | ||||
return path | ||||
r33175 | return orig(self, section, key, *args, **kwargs) | |||
|
r7071 | |||
|
r28038 | def configitems(orig, self, section, *args, **kwargs): | ||
repos = orig(self, section, *args, **kwargs) | ||||
|
r7071 | if section == "paths": | ||
|
r9488 | repos += getzcpaths() | ||
return repos | ||||
|
r7071 | |||
|
r28250 | def configsuboptions(orig, self, section, name, *args, **kwargs): | ||
opt, sub = orig(self, section, name, *args, **kwargs) | ||||
if section == "paths" and name.startswith("zc-"): | ||||
# We have to find the URL in the zeroconf paths. We can't cons up any | ||||
# suboptions, so we use any that we found in the original config. | ||||
for zcname, zcurl in getzcpaths(): | ||||
if zcname == name: | ||||
return zcurl, sub | ||||
return opt, sub | ||||
|
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) | ||||
|
r28308 | extensions.wrapfunction(uimod.ui, 'config', config) | ||
extensions.wrapfunction(uimod.ui, 'configitems', configitems) | ||||
extensions.wrapfunction(uimod.ui, 'configsuboptions', configsuboptions) | ||||
|
r10342 | extensions.wrapfunction(hg, 'defaultdest', defaultdest) | ||
|
r18190 | extensions.wrapfunction(servermod, 'create_server', zc_create_server) | ||