##// END OF EJS Templates
wrapfunction: use sysstr instead of bytes as argument in "zeroconf"...
marmoute -
r51687:c3d7a082 default
parent child Browse files
Show More
@@ -1,242 +1,242 b''
1 # zeroconf.py - zeroconf support for Mercurial
1 # zeroconf.py - zeroconf support for Mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@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 The zeroconf extension will advertise :hg:`serve` instances over
9 The zeroconf extension will advertise :hg:`serve` instances over
10 DNS-SD so that they can be discovered using the :hg:`paths` command
10 DNS-SD so that they can be discovered using the :hg:`paths` command
11 without knowing the server's IP address.
11 without knowing the server's 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
25
26 import os
26 import os
27 import socket
27 import socket
28 import time
28 import time
29
29
30 from . import Zeroconf
30 from . import Zeroconf
31 from mercurial import (
31 from mercurial import (
32 dispatch,
32 dispatch,
33 encoding,
33 encoding,
34 extensions,
34 extensions,
35 hg,
35 hg,
36 pycompat,
36 pycompat,
37 rcutil,
37 rcutil,
38 ui as uimod,
38 ui as uimod,
39 )
39 )
40 from mercurial.hgweb import server as servermod
40 from mercurial.hgweb import server as servermod
41
41
42 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
42 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
43 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
43 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
44 # be specifying the version(s) of Mercurial they are tested with, or
44 # be specifying the version(s) of Mercurial they are tested with, or
45 # leave the attribute unspecified.
45 # leave the attribute unspecified.
46 testedwith = b'ships-with-hg-core'
46 testedwith = b'ships-with-hg-core'
47
47
48 # publish
48 # publish
49
49
50 server = None
50 server = None
51 localip = None
51 localip = None
52
52
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
85
86 def publish(name, desc, path, port):
86 def publish(name, desc, path, port):
87 global server, localip
87 global server, localip
88 if not server:
88 if not server:
89 ip = getip()
89 ip = getip()
90 if ip.startswith('127.'):
90 if ip.startswith('127.'):
91 # if we have no internet connection, this can happen.
91 # if we have no internet connection, this can happen.
92 return
92 return
93 localip = socket.inet_aton(ip)
93 localip = socket.inet_aton(ip)
94 server = Zeroconf.Zeroconf(ip)
94 server = Zeroconf.Zeroconf(ip)
95
95
96 hostname = socket.gethostname().split('.')[0]
96 hostname = socket.gethostname().split('.')[0]
97 host = hostname + ".local"
97 host = hostname + ".local"
98 name = "%s-%s" % (hostname, name)
98 name = "%s-%s" % (hostname, name)
99
99
100 # advertise to browsers
100 # advertise to browsers
101 svc = Zeroconf.ServiceInfo(
101 svc = Zeroconf.ServiceInfo(
102 b'_http._tcp.local.',
102 b'_http._tcp.local.',
103 pycompat.bytestr(name + '._http._tcp.local.'),
103 pycompat.bytestr(name + '._http._tcp.local.'),
104 server=host,
104 server=host,
105 port=port,
105 port=port,
106 properties={b'description': desc, b'path': b"/" + path},
106 properties={b'description': desc, b'path': b"/" + path},
107 address=localip,
107 address=localip,
108 weight=0,
108 weight=0,
109 priority=0,
109 priority=0,
110 )
110 )
111 server.registerService(svc)
111 server.registerService(svc)
112
112
113 # advertise to Mercurial clients
113 # advertise to Mercurial clients
114 svc = Zeroconf.ServiceInfo(
114 svc = Zeroconf.ServiceInfo(
115 b'_hg._tcp.local.',
115 b'_hg._tcp.local.',
116 pycompat.bytestr(name + '._hg._tcp.local.'),
116 pycompat.bytestr(name + '._hg._tcp.local.'),
117 server=host,
117 server=host,
118 port=port,
118 port=port,
119 properties={b'description': desc, b'path': b"/" + path},
119 properties={b'description': desc, b'path': b"/" + path},
120 address=localip,
120 address=localip,
121 weight=0,
121 weight=0,
122 priority=0,
122 priority=0,
123 )
123 )
124 server.registerService(svc)
124 server.registerService(svc)
125
125
126
126
127 def zc_create_server(create_server, ui, app):
127 def zc_create_server(create_server, ui, app):
128 httpd = create_server(ui, app)
128 httpd = create_server(ui, app)
129 port = httpd.port
129 port = httpd.port
130
130
131 try:
131 try:
132 repos = app.repos
132 repos = app.repos
133 except AttributeError:
133 except AttributeError:
134 # single repo
134 # single repo
135 with app._obtainrepo() as repo:
135 with app._obtainrepo() as repo:
136 name = app.reponame or os.path.basename(repo.root)
136 name = app.reponame or os.path.basename(repo.root)
137 path = repo.ui.config(b"web", b"prefix", b"").strip(b'/')
137 path = repo.ui.config(b"web", b"prefix", b"").strip(b'/')
138 desc = repo.ui.config(b"web", b"description")
138 desc = repo.ui.config(b"web", b"description")
139 if not desc:
139 if not desc:
140 desc = name
140 desc = name
141 publish(name, desc, path, port)
141 publish(name, desc, path, port)
142 else:
142 else:
143 # webdir
143 # webdir
144 prefix = app.ui.config(b"web", b"prefix", b"").strip(b'/') + b'/'
144 prefix = app.ui.config(b"web", b"prefix", b"").strip(b'/') + b'/'
145 for repo, path in repos:
145 for repo, path in repos:
146 u = app.ui.copy()
146 u = app.ui.copy()
147 if rcutil.use_repo_hgrc():
147 if rcutil.use_repo_hgrc():
148 u.readconfig(os.path.join(path, b'.hg', b'hgrc'))
148 u.readconfig(os.path.join(path, b'.hg', b'hgrc'))
149 name = os.path.basename(repo)
149 name = os.path.basename(repo)
150 path = (prefix + repo).strip(b'/')
150 path = (prefix + repo).strip(b'/')
151 desc = u.config(b'web', b'description')
151 desc = u.config(b'web', b'description')
152 if not desc:
152 if not desc:
153 desc = name
153 desc = name
154 publish(name, desc, path, port)
154 publish(name, desc, path, port)
155 return httpd
155 return httpd
156
156
157
157
158 # listen
158 # listen
159
159
160
160
161 class listener:
161 class listener:
162 def __init__(self):
162 def __init__(self):
163 self.found = {}
163 self.found = {}
164
164
165 def removeService(self, server, type, name):
165 def removeService(self, server, type, name):
166 if repr(name) in self.found:
166 if repr(name) in self.found:
167 del self.found[repr(name)]
167 del self.found[repr(name)]
168
168
169 def addService(self, server, type, name):
169 def addService(self, server, type, name):
170 self.found[repr(name)] = server.getServiceInfo(type, name)
170 self.found[repr(name)] = server.getServiceInfo(type, name)
171
171
172
172
173 def getzcpaths():
173 def getzcpaths():
174 ip = getip()
174 ip = getip()
175 if ip.startswith('127.'):
175 if ip.startswith('127.'):
176 return
176 return
177 server = Zeroconf.Zeroconf(ip)
177 server = Zeroconf.Zeroconf(ip)
178 l = listener()
178 l = listener()
179 Zeroconf.ServiceBrowser(server, b"_hg._tcp.local.", l)
179 Zeroconf.ServiceBrowser(server, b"_hg._tcp.local.", l)
180 time.sleep(1)
180 time.sleep(1)
181 server.close()
181 server.close()
182 for value in l.found.values():
182 for value in l.found.values():
183 name = value.name[: value.name.index(b'.')]
183 name = value.name[: value.name.index(b'.')]
184 url = "http://%s:%s%s" % (
184 url = "http://%s:%s%s" % (
185 socket.inet_ntoa(value.address),
185 socket.inet_ntoa(value.address),
186 value.port,
186 value.port,
187 value.properties.get("path", "/"),
187 value.properties.get("path", "/"),
188 )
188 )
189 yield b"zc-" + name, pycompat.bytestr(url)
189 yield b"zc-" + name, pycompat.bytestr(url)
190
190
191
191
192 def config(orig, self, section, key, *args, **kwargs):
192 def config(orig, self, section, key, *args, **kwargs):
193 if section == b"paths" and key.startswith(b"zc-"):
193 if section == b"paths" and key.startswith(b"zc-"):
194 for name, path in getzcpaths():
194 for name, path in getzcpaths():
195 if name == key:
195 if name == key:
196 return path
196 return path
197 return orig(self, section, key, *args, **kwargs)
197 return orig(self, section, key, *args, **kwargs)
198
198
199
199
200 def configitems(orig, self, section, *args, **kwargs):
200 def configitems(orig, self, section, *args, **kwargs):
201 repos = orig(self, section, *args, **kwargs)
201 repos = orig(self, section, *args, **kwargs)
202 if section == b"paths":
202 if section == b"paths":
203 repos += getzcpaths()
203 repos += getzcpaths()
204 return repos
204 return repos
205
205
206
206
207 def configsuboptions(orig, self, section, name, *args, **kwargs):
207 def configsuboptions(orig, self, section, name, *args, **kwargs):
208 opt, sub = orig(self, section, name, *args, **kwargs)
208 opt, sub = orig(self, section, name, *args, **kwargs)
209 if section == b"paths" and name.startswith(b"zc-"):
209 if section == b"paths" and name.startswith(b"zc-"):
210 # We have to find the URL in the zeroconf paths. We can't cons up any
210 # We have to find the URL in the zeroconf paths. We can't cons up any
211 # suboptions, so we use any that we found in the original config.
211 # suboptions, so we use any that we found in the original config.
212 for zcname, zcurl in getzcpaths():
212 for zcname, zcurl in getzcpaths():
213 if zcname == name:
213 if zcname == name:
214 return zcurl, sub
214 return zcurl, sub
215 return opt, sub
215 return opt, sub
216
216
217
217
218 def defaultdest(orig, source):
218 def defaultdest(orig, source):
219 for name, path in getzcpaths():
219 for name, path in getzcpaths():
220 if path == source:
220 if path == source:
221 return name.encode(encoding.encoding)
221 return name.encode(encoding.encoding)
222 return orig(source)
222 return orig(source)
223
223
224
224
225 def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc):
225 def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc):
226 try:
226 try:
227 return orig(ui, options, cmd, cmdfunc)
227 return orig(ui, options, cmd, cmdfunc)
228 finally:
228 finally:
229 # we need to call close() on the server to notify() the various
229 # we need to call close() on the server to notify() the various
230 # threading Conditions and allow the background threads to exit
230 # threading Conditions and allow the background threads to exit
231 global server
231 global server
232 if server:
232 if server:
233 server.close()
233 server.close()
234
234
235
235
236 extensions.wrapfunction(dispatch, b'_runcommand', cleanupafterdispatch)
236 extensions.wrapfunction(dispatch, '_runcommand', cleanupafterdispatch)
237
237
238 extensions.wrapfunction(uimod.ui, b'config', config)
238 extensions.wrapfunction(uimod.ui, 'config', config)
239 extensions.wrapfunction(uimod.ui, b'configitems', configitems)
239 extensions.wrapfunction(uimod.ui, 'configitems', configitems)
240 extensions.wrapfunction(uimod.ui, b'configsuboptions', configsuboptions)
240 extensions.wrapfunction(uimod.ui, 'configsuboptions', configsuboptions)
241 extensions.wrapfunction(hg, b'defaultdest', defaultdest)
241 extensions.wrapfunction(hg, 'defaultdest', defaultdest)
242 extensions.wrapfunction(servermod, b'create_server', zc_create_server)
242 extensions.wrapfunction(servermod, 'create_server', zc_create_server)
General Comments 0
You need to be logged in to leave comments. Login now