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