##// END OF EJS Templates
zeroconf: fail nicely on IPv6 only system...
Simon Farnsworth -
r30885:564a96a5 default
parent child Browse files
Show More
@@ -1,213 +1,215
1 1 # zeroconf.py - zeroconf support for Mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 '''discover and advertise repositories on the local network
8 8
9 9 Zeroconf-enabled repositories will be announced in a network without
10 10 the need to configure a server or a service. They can be discovered
11 11 without knowing their actual IP address.
12 12
13 13 To allow other people to discover your repository using run
14 14 :hg:`serve` in your repository::
15 15
16 16 $ cd test
17 17 $ hg serve
18 18
19 19 You can discover Zeroconf-enabled repositories by running
20 20 :hg:`paths`::
21 21
22 22 $ hg paths
23 23 zc-test = http://example.com:8000/test
24 24 '''
25 25 from __future__ import absolute_import
26 26
27 27 import os
28 28 import socket
29 29 import time
30 30
31 31 from . import Zeroconf
32 32 from mercurial import (
33 33 dispatch,
34 34 encoding,
35 35 extensions,
36 36 hg,
37 37 ui as uimod,
38 38 )
39 39 from mercurial.hgweb import (
40 40 server as servermod
41 41 )
42 42
43 43 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
44 44 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
45 45 # be specifying the version(s) of Mercurial they are tested with, or
46 46 # leave the attribute unspecified.
47 47 testedwith = 'ships-with-hg-core'
48 48
49 49 # publish
50 50
51 51 server = None
52 52 localip = None
53 53
54 54 def getip():
55 55 # finds external-facing interface without sending any packets (Linux)
56 56 try:
57 57 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
58 58 s.connect(('1.0.0.1', 0))
59 59 ip = s.getsockname()[0]
60 60 return ip
61 61 except socket.error:
62 62 pass
63 63
64 64 # Generic method, sometimes gives useless results
65 65 try:
66 66 dumbip = socket.gethostbyaddr(socket.gethostname())[2][0]
67 if not dumbip.startswith('127.') and ':' not in dumbip:
67 if ':' in dumbip:
68 dumbip = '127.0.0.1'
69 if not dumbip.startswith('127.'):
68 70 return dumbip
69 71 except (socket.gaierror, socket.herror):
70 72 dumbip = '127.0.0.1'
71 73
72 74 # works elsewhere, but actually sends a packet
73 75 try:
74 76 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
75 77 s.connect(('1.0.0.1', 1))
76 78 ip = s.getsockname()[0]
77 79 return ip
78 80 except socket.error:
79 81 pass
80 82
81 83 return dumbip
82 84
83 85 def publish(name, desc, path, port):
84 86 global server, localip
85 87 if not server:
86 88 ip = getip()
87 89 if ip.startswith('127.'):
88 90 # if we have no internet connection, this can happen.
89 91 return
90 92 localip = socket.inet_aton(ip)
91 93 server = Zeroconf.Zeroconf(ip)
92 94
93 95 hostname = socket.gethostname().split('.')[0]
94 96 host = hostname + ".local"
95 97 name = "%s-%s" % (hostname, name)
96 98
97 99 # advertise to browsers
98 100 svc = Zeroconf.ServiceInfo('_http._tcp.local.',
99 101 name + '._http._tcp.local.',
100 102 server = host,
101 103 port = port,
102 104 properties = {'description': desc,
103 105 'path': "/" + path},
104 106 address = localip, weight = 0, priority = 0)
105 107 server.registerService(svc)
106 108
107 109 # advertise to Mercurial clients
108 110 svc = Zeroconf.ServiceInfo('_hg._tcp.local.',
109 111 name + '._hg._tcp.local.',
110 112 server = host,
111 113 port = port,
112 114 properties = {'description': desc,
113 115 'path': "/" + path},
114 116 address = localip, weight = 0, priority = 0)
115 117 server.registerService(svc)
116 118
117 119 def zc_create_server(create_server, ui, app):
118 120 httpd = create_server(ui, app)
119 121 port = httpd.port
120 122
121 123 try:
122 124 repos = app.repos
123 125 except AttributeError:
124 126 # single repo
125 127 with app._obtainrepo() as repo:
126 128 name = app.reponame or os.path.basename(repo.root)
127 129 path = repo.ui.config("web", "prefix", "").strip('/')
128 130 desc = repo.ui.config("web", "description", name)
129 131 publish(name, desc, path, port)
130 132 else:
131 133 # webdir
132 134 prefix = app.ui.config("web", "prefix", "").strip('/') + '/'
133 135 for repo, path in repos:
134 136 u = app.ui.copy()
135 137 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
136 138 name = os.path.basename(repo)
137 139 path = (prefix + repo).strip('/')
138 140 desc = u.config('web', 'description', name)
139 141 publish(name, desc, path, port)
140 142 return httpd
141 143
142 144 # listen
143 145
144 146 class listener(object):
145 147 def __init__(self):
146 148 self.found = {}
147 149 def removeService(self, server, type, name):
148 150 if repr(name) in self.found:
149 151 del self.found[repr(name)]
150 152 def addService(self, server, type, name):
151 153 self.found[repr(name)] = server.getServiceInfo(type, name)
152 154
153 155 def getzcpaths():
154 156 ip = getip()
155 157 if ip.startswith('127.'):
156 158 return
157 159 server = Zeroconf.Zeroconf(ip)
158 160 l = listener()
159 161 Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l)
160 162 time.sleep(1)
161 163 server.close()
162 164 for value in l.found.values():
163 165 name = value.name[:value.name.index('.')]
164 166 url = "http://%s:%s%s" % (socket.inet_ntoa(value.address), value.port,
165 167 value.properties.get("path", "/"))
166 168 yield "zc-" + name, url
167 169
168 170 def config(orig, self, section, key, default=None, untrusted=False):
169 171 if section == "paths" and key.startswith("zc-"):
170 172 for name, path in getzcpaths():
171 173 if name == key:
172 174 return path
173 175 return orig(self, section, key, default, untrusted)
174 176
175 177 def configitems(orig, self, section, *args, **kwargs):
176 178 repos = orig(self, section, *args, **kwargs)
177 179 if section == "paths":
178 180 repos += getzcpaths()
179 181 return repos
180 182
181 183 def configsuboptions(orig, self, section, name, *args, **kwargs):
182 184 opt, sub = orig(self, section, name, *args, **kwargs)
183 185 if section == "paths" and name.startswith("zc-"):
184 186 # We have to find the URL in the zeroconf paths. We can't cons up any
185 187 # suboptions, so we use any that we found in the original config.
186 188 for zcname, zcurl in getzcpaths():
187 189 if zcname == name:
188 190 return zcurl, sub
189 191 return opt, sub
190 192
191 193 def defaultdest(orig, source):
192 194 for name, path in getzcpaths():
193 195 if path == source:
194 196 return name.encode(encoding.encoding)
195 197 return orig(source)
196 198
197 199 def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc):
198 200 try:
199 201 return orig(ui, options, cmd, cmdfunc)
200 202 finally:
201 203 # we need to call close() on the server to notify() the various
202 204 # threading Conditions and allow the background threads to exit
203 205 global server
204 206 if server:
205 207 server.close()
206 208
207 209 extensions.wrapfunction(dispatch, '_runcommand', cleanupafterdispatch)
208 210
209 211 extensions.wrapfunction(uimod.ui, 'config', config)
210 212 extensions.wrapfunction(uimod.ui, 'configitems', configitems)
211 213 extensions.wrapfunction(uimod.ui, 'configsuboptions', configsuboptions)
212 214 extensions.wrapfunction(hg, 'defaultdest', defaultdest)
213 215 extensions.wrapfunction(servermod, 'create_server', zc_create_server)
General Comments 0
You need to be logged in to leave comments. Login now