##// END OF EJS Templates
zeroconf: use port from server instead of picking port from config (issue3746)...
Benoit Boissinot -
r18190:d57879e7 default
parent child Browse files
Show More
@@ -1,188 +1,188 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, util, dispatch
30 from mercurial import ui, hg, encoding, dispatch
31 from mercurial import extensions
31 from mercurial import extensions
32 from mercurial.hgweb import hgweb_mod
32 from mercurial.hgweb import server as servermod
33 from mercurial.hgweb import hgwebdir_mod
34
33
35 testedwith = 'internal'
34 testedwith = 'internal'
36
35
37 # publish
36 # publish
38
37
39 server = None
38 server = None
40 localip = None
39 localip = None
41
40
42 def getip():
41 def getip():
43 # finds external-facing interface without sending any packets (Linux)
42 # finds external-facing interface without sending any packets (Linux)
44 try:
43 try:
45 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
44 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
46 s.connect(('1.0.0.1', 0))
45 s.connect(('1.0.0.1', 0))
47 ip = s.getsockname()[0]
46 ip = s.getsockname()[0]
48 return ip
47 return ip
49 except socket.error:
48 except socket.error:
50 pass
49 pass
51
50
52 # Generic method, sometimes gives useless results
51 # Generic method, sometimes gives useless results
53 try:
52 try:
54 dumbip = socket.gethostbyaddr(socket.gethostname())[2][0]
53 dumbip = socket.gethostbyaddr(socket.gethostname())[2][0]
55 if not dumbip.startswith('127.') and ':' not in dumbip:
54 if not dumbip.startswith('127.') and ':' not in dumbip:
56 return dumbip
55 return dumbip
57 except (socket.gaierror, socket.herror):
56 except (socket.gaierror, socket.herror):
58 dumbip = '127.0.0.1'
57 dumbip = '127.0.0.1'
59
58
60 # works elsewhere, but actually sends a packet
59 # works elsewhere, but actually sends a packet
61 try:
60 try:
62 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
61 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
63 s.connect(('1.0.0.1', 1))
62 s.connect(('1.0.0.1', 1))
64 ip = s.getsockname()[0]
63 ip = s.getsockname()[0]
65 return ip
64 return ip
66 except socket.error:
65 except socket.error:
67 pass
66 pass
68
67
69 return dumbip
68 return dumbip
70
69
71 def publish(name, desc, path, port):
70 def publish(name, desc, path, port):
72 global server, localip
71 global server, localip
73 if not server:
72 if not server:
74 ip = getip()
73 ip = getip()
75 if ip.startswith('127.'):
74 if ip.startswith('127.'):
76 # if we have no internet connection, this can happen.
75 # if we have no internet connection, this can happen.
77 return
76 return
78 localip = socket.inet_aton(ip)
77 localip = socket.inet_aton(ip)
79 server = Zeroconf.Zeroconf(ip)
78 server = Zeroconf.Zeroconf(ip)
80
79
81 hostname = socket.gethostname().split('.')[0]
80 hostname = socket.gethostname().split('.')[0]
82 host = hostname + ".local"
81 host = hostname + ".local"
83 name = "%s-%s" % (hostname, name)
82 name = "%s-%s" % (hostname, name)
84
83
85 # advertise to browsers
84 # advertise to browsers
86 svc = Zeroconf.ServiceInfo('_http._tcp.local.',
85 svc = Zeroconf.ServiceInfo('_http._tcp.local.',
87 name + '._http._tcp.local.',
86 name + '._http._tcp.local.',
88 server = host,
87 server = host,
89 port = port,
88 port = port,
90 properties = {'description': desc,
89 properties = {'description': desc,
91 'path': "/" + path},
90 'path': "/" + path},
92 address = localip, weight = 0, priority = 0)
91 address = localip, weight = 0, priority = 0)
93 server.registerService(svc)
92 server.registerService(svc)
94
93
95 # advertise to Mercurial clients
94 # advertise to Mercurial clients
96 svc = Zeroconf.ServiceInfo('_hg._tcp.local.',
95 svc = Zeroconf.ServiceInfo('_hg._tcp.local.',
97 name + '._hg._tcp.local.',
96 name + '._hg._tcp.local.',
98 server = host,
97 server = host,
99 port = port,
98 port = port,
100 properties = {'description': desc,
99 properties = {'description': desc,
101 'path': "/" + path},
100 'path': "/" + path},
102 address = localip, weight = 0, priority = 0)
101 address = localip, weight = 0, priority = 0)
103 server.registerService(svc)
102 server.registerService(svc)
104
103
105 class hgwebzc(hgweb_mod.hgweb):
104 def zc_create_server(create_server, ui, app):
106 def __init__(self, repo, name=None, baseui=None):
105 httpd = create_server(ui, app)
107 super(hgwebzc, self).__init__(repo, name=name, baseui=baseui)
106 port = httpd.port
108 name = self.reponame or os.path.basename(self.repo.root)
109 path = self.repo.ui.config("web", "prefix", "").strip('/')
110 desc = self.repo.ui.config("web", "description", name)
111 publish(name, desc, path,
112 util.getport(self.repo.ui.config("web", "port", 8000)))
113
107
114 class hgwebdirzc(hgwebdir_mod.hgwebdir):
108 try:
115 def __init__(self, conf, baseui=None):
109 repos = app.repos
116 super(hgwebdirzc, self).__init__(conf, baseui=baseui)
110 except AttributeError:
117 prefix = self.ui.config("web", "prefix", "").strip('/') + '/'
111 # single repo
118 for repo, path in self.repos:
112 name = app.reponame or os.path.basename(app.repo.root)
119 u = self.ui.copy()
113 path = app.repo.ui.config("web", "prefix", "").strip('/')
114 desc = app.repo.ui.config("web", "description", name)
115 publish(name, desc, path, port)
116 else:
117 # webdir
118 prefix = app.ui.config("web", "prefix", "").strip('/') + '/'
119 for repo, path in repos:
120 u = app.ui.copy()
120 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
121 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
121 name = os.path.basename(repo)
122 name = os.path.basename(repo)
122 path = (prefix + repo).strip('/')
123 path = (prefix + repo).strip('/')
123 desc = u.config('web', 'description', name)
124 desc = u.config('web', 'description', name)
124 publish(name, desc, path,
125 publish(name, desc, path, port)
125 util.getport(u.config("web", "port", 8000)))
126 return httpd
126
127
127 # listen
128 # listen
128
129
129 class listener(object):
130 class listener(object):
130 def __init__(self):
131 def __init__(self):
131 self.found = {}
132 self.found = {}
132 def removeService(self, server, type, name):
133 def removeService(self, server, type, name):
133 if repr(name) in self.found:
134 if repr(name) in self.found:
134 del self.found[repr(name)]
135 del self.found[repr(name)]
135 def addService(self, server, type, name):
136 def addService(self, server, type, name):
136 self.found[repr(name)] = server.getServiceInfo(type, name)
137 self.found[repr(name)] = server.getServiceInfo(type, name)
137
138
138 def getzcpaths():
139 def getzcpaths():
139 ip = getip()
140 ip = getip()
140 if ip.startswith('127.'):
141 if ip.startswith('127.'):
141 return
142 return
142 server = Zeroconf.Zeroconf(ip)
143 server = Zeroconf.Zeroconf(ip)
143 l = listener()
144 l = listener()
144 Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l)
145 Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l)
145 time.sleep(1)
146 time.sleep(1)
146 server.close()
147 server.close()
147 for value in l.found.values():
148 for value in l.found.values():
148 name = value.name[:value.name.index('.')]
149 name = value.name[:value.name.index('.')]
149 url = "http://%s:%s%s" % (socket.inet_ntoa(value.address), value.port,
150 url = "http://%s:%s%s" % (socket.inet_ntoa(value.address), value.port,
150 value.properties.get("path", "/"))
151 value.properties.get("path", "/"))
151 yield "zc-" + name, url
152 yield "zc-" + name, url
152
153
153 def config(orig, self, section, key, default=None, untrusted=False):
154 def config(orig, self, section, key, default=None, untrusted=False):
154 if section == "paths" and key.startswith("zc-"):
155 if section == "paths" and key.startswith("zc-"):
155 for name, path in getzcpaths():
156 for name, path in getzcpaths():
156 if name == key:
157 if name == key:
157 return path
158 return path
158 return orig(self, section, key, default, untrusted)
159 return orig(self, section, key, default, untrusted)
159
160
160 def configitems(orig, self, section, untrusted=False):
161 def configitems(orig, self, section, untrusted=False):
161 repos = orig(self, section, untrusted)
162 repos = orig(self, section, untrusted)
162 if section == "paths":
163 if section == "paths":
163 repos += getzcpaths()
164 repos += getzcpaths()
164 return repos
165 return repos
165
166
166 def defaultdest(orig, source):
167 def defaultdest(orig, source):
167 for name, path in getzcpaths():
168 for name, path in getzcpaths():
168 if path == source:
169 if path == source:
169 return name.encode(encoding.encoding)
170 return name.encode(encoding.encoding)
170 return orig(source)
171 return orig(source)
171
172
172 def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc):
173 def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc):
173 try:
174 try:
174 return orig(ui, options, cmd, cmdfunc)
175 return orig(ui, options, cmd, cmdfunc)
175 finally:
176 finally:
176 # we need to call close() on the server to notify() the various
177 # we need to call close() on the server to notify() the various
177 # threading Conditions and allow the background threads to exit
178 # threading Conditions and allow the background threads to exit
178 global server
179 global server
179 if server:
180 if server:
180 server.close()
181 server.close()
181
182
182 extensions.wrapfunction(dispatch, '_runcommand', cleanupafterdispatch)
183 extensions.wrapfunction(dispatch, '_runcommand', cleanupafterdispatch)
183
184
184 extensions.wrapfunction(ui.ui, 'config', config)
185 extensions.wrapfunction(ui.ui, 'config', config)
185 extensions.wrapfunction(ui.ui, 'configitems', configitems)
186 extensions.wrapfunction(ui.ui, 'configitems', configitems)
186 extensions.wrapfunction(hg, 'defaultdest', defaultdest)
187 extensions.wrapfunction(hg, 'defaultdest', defaultdest)
187 hgweb_mod.hgweb = hgwebzc
188 extensions.wrapfunction(servermod, 'create_server', zc_create_server)
188 hgwebdir_mod.hgwebdir = hgwebdirzc
General Comments 0
You need to be logged in to leave comments. Login now