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