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