##// END OF EJS Templates
zeroconf: import ui as uimod per test-check-module-imports
Augie Fackler -
r28308:4de74eda default
parent child Browse files
Show More
@@ -1,213 +1,213 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,
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 = 'internal' for
43 # Note for extension authors: ONLY specify testedwith = 'internal' 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 = 'internal'
47 testedwith = 'internal'
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 not dumbip.startswith('127.') and ':' not in dumbip:
67 if not dumbip.startswith('127.') and ':' not in dumbip:
68 return dumbip
68 return dumbip
69 except (socket.gaierror, socket.herror):
69 except (socket.gaierror, socket.herror):
70 dumbip = '127.0.0.1'
70 dumbip = '127.0.0.1'
71
71
72 # works elsewhere, but actually sends a packet
72 # works elsewhere, but actually sends a packet
73 try:
73 try:
74 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
74 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
75 s.connect(('1.0.0.1', 1))
75 s.connect(('1.0.0.1', 1))
76 ip = s.getsockname()[0]
76 ip = s.getsockname()[0]
77 return ip
77 return ip
78 except socket.error:
78 except socket.error:
79 pass
79 pass
80
80
81 return dumbip
81 return dumbip
82
82
83 def publish(name, desc, path, port):
83 def publish(name, desc, path, port):
84 global server, localip
84 global server, localip
85 if not server:
85 if not server:
86 ip = getip()
86 ip = getip()
87 if ip.startswith('127.'):
87 if ip.startswith('127.'):
88 # if we have no internet connection, this can happen.
88 # if we have no internet connection, this can happen.
89 return
89 return
90 localip = socket.inet_aton(ip)
90 localip = socket.inet_aton(ip)
91 server = Zeroconf.Zeroconf(ip)
91 server = Zeroconf.Zeroconf(ip)
92
92
93 hostname = socket.gethostname().split('.')[0]
93 hostname = socket.gethostname().split('.')[0]
94 host = hostname + ".local"
94 host = hostname + ".local"
95 name = "%s-%s" % (hostname, name)
95 name = "%s-%s" % (hostname, name)
96
96
97 # advertise to browsers
97 # advertise to browsers
98 svc = Zeroconf.ServiceInfo('_http._tcp.local.',
98 svc = Zeroconf.ServiceInfo('_http._tcp.local.',
99 name + '._http._tcp.local.',
99 name + '._http._tcp.local.',
100 server = host,
100 server = host,
101 port = port,
101 port = port,
102 properties = {'description': desc,
102 properties = {'description': desc,
103 'path': "/" + path},
103 'path': "/" + path},
104 address = localip, weight = 0, priority = 0)
104 address = localip, weight = 0, priority = 0)
105 server.registerService(svc)
105 server.registerService(svc)
106
106
107 # advertise to Mercurial clients
107 # advertise to Mercurial clients
108 svc = Zeroconf.ServiceInfo('_hg._tcp.local.',
108 svc = Zeroconf.ServiceInfo('_hg._tcp.local.',
109 name + '._hg._tcp.local.',
109 name + '._hg._tcp.local.',
110 server = host,
110 server = host,
111 port = port,
111 port = port,
112 properties = {'description': desc,
112 properties = {'description': desc,
113 'path': "/" + path},
113 'path': "/" + path},
114 address = localip, weight = 0, priority = 0)
114 address = localip, weight = 0, priority = 0)
115 server.registerService(svc)
115 server.registerService(svc)
116
116
117 def zc_create_server(create_server, ui, app):
117 def zc_create_server(create_server, ui, app):
118 httpd = create_server(ui, app)
118 httpd = create_server(ui, app)
119 port = httpd.port
119 port = httpd.port
120
120
121 try:
121 try:
122 repos = app.repos
122 repos = app.repos
123 except AttributeError:
123 except AttributeError:
124 # single repo
124 # single repo
125 with app._obtainrepo() as repo:
125 with app._obtainrepo() as repo:
126 name = app.reponame or os.path.basename(repo.root)
126 name = app.reponame or os.path.basename(repo.root)
127 path = repo.ui.config("web", "prefix", "").strip('/')
127 path = repo.ui.config("web", "prefix", "").strip('/')
128 desc = repo.ui.config("web", "description", name)
128 desc = repo.ui.config("web", "description", name)
129 publish(name, desc, path, port)
129 publish(name, desc, path, port)
130 else:
130 else:
131 # webdir
131 # webdir
132 prefix = app.ui.config("web", "prefix", "").strip('/') + '/'
132 prefix = app.ui.config("web", "prefix", "").strip('/') + '/'
133 for repo, path in repos:
133 for repo, path in repos:
134 u = app.ui.copy()
134 u = app.ui.copy()
135 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
135 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
136 name = os.path.basename(repo)
136 name = os.path.basename(repo)
137 path = (prefix + repo).strip('/')
137 path = (prefix + repo).strip('/')
138 desc = u.config('web', 'description', name)
138 desc = u.config('web', 'description', name)
139 publish(name, desc, path, port)
139 publish(name, desc, path, port)
140 return httpd
140 return httpd
141
141
142 # listen
142 # listen
143
143
144 class listener(object):
144 class listener(object):
145 def __init__(self):
145 def __init__(self):
146 self.found = {}
146 self.found = {}
147 def removeService(self, server, type, name):
147 def removeService(self, server, type, name):
148 if repr(name) in self.found:
148 if repr(name) in self.found:
149 del self.found[repr(name)]
149 del self.found[repr(name)]
150 def addService(self, server, type, name):
150 def addService(self, server, type, name):
151 self.found[repr(name)] = server.getServiceInfo(type, name)
151 self.found[repr(name)] = server.getServiceInfo(type, name)
152
152
153 def getzcpaths():
153 def getzcpaths():
154 ip = getip()
154 ip = getip()
155 if ip.startswith('127.'):
155 if ip.startswith('127.'):
156 return
156 return
157 server = Zeroconf.Zeroconf(ip)
157 server = Zeroconf.Zeroconf(ip)
158 l = listener()
158 l = listener()
159 Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l)
159 Zeroconf.ServiceBrowser(server, "_hg._tcp.local.", l)
160 time.sleep(1)
160 time.sleep(1)
161 server.close()
161 server.close()
162 for value in l.found.values():
162 for value in l.found.values():
163 name = value.name[:value.name.index('.')]
163 name = value.name[:value.name.index('.')]
164 url = "http://%s:%s%s" % (socket.inet_ntoa(value.address), value.port,
164 url = "http://%s:%s%s" % (socket.inet_ntoa(value.address), value.port,
165 value.properties.get("path", "/"))
165 value.properties.get("path", "/"))
166 yield "zc-" + name, url
166 yield "zc-" + name, url
167
167
168 def config(orig, self, section, key, default=None, untrusted=False):
168 def config(orig, self, section, key, default=None, untrusted=False):
169 if section == "paths" and key.startswith("zc-"):
169 if section == "paths" and key.startswith("zc-"):
170 for name, path in getzcpaths():
170 for name, path in getzcpaths():
171 if name == key:
171 if name == key:
172 return path
172 return path
173 return orig(self, section, key, default, untrusted)
173 return orig(self, section, key, default, untrusted)
174
174
175 def configitems(orig, self, section, *args, **kwargs):
175 def configitems(orig, self, section, *args, **kwargs):
176 repos = orig(self, section, *args, **kwargs)
176 repos = orig(self, section, *args, **kwargs)
177 if section == "paths":
177 if section == "paths":
178 repos += getzcpaths()
178 repos += getzcpaths()
179 return repos
179 return repos
180
180
181 def configsuboptions(orig, self, section, name, *args, **kwargs):
181 def configsuboptions(orig, self, section, name, *args, **kwargs):
182 opt, sub = orig(self, section, name, *args, **kwargs)
182 opt, sub = orig(self, section, name, *args, **kwargs)
183 if section == "paths" and name.startswith("zc-"):
183 if section == "paths" and name.startswith("zc-"):
184 # We have to find the URL in the zeroconf paths. We can't cons up any
184 # We have to find the URL in the zeroconf paths. We can't cons up any
185 # suboptions, so we use any that we found in the original config.
185 # suboptions, so we use any that we found in the original config.
186 for zcname, zcurl in getzcpaths():
186 for zcname, zcurl in getzcpaths():
187 if zcname == name:
187 if zcname == name:
188 return zcurl, sub
188 return zcurl, sub
189 return opt, sub
189 return opt, sub
190
190
191 def defaultdest(orig, source):
191 def defaultdest(orig, source):
192 for name, path in getzcpaths():
192 for name, path in getzcpaths():
193 if path == source:
193 if path == source:
194 return name.encode(encoding.encoding)
194 return name.encode(encoding.encoding)
195 return orig(source)
195 return orig(source)
196
196
197 def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc):
197 def cleanupafterdispatch(orig, ui, options, cmd, cmdfunc):
198 try:
198 try:
199 return orig(ui, options, cmd, cmdfunc)
199 return orig(ui, options, cmd, cmdfunc)
200 finally:
200 finally:
201 # we need to call close() on the server to notify() the various
201 # we need to call close() on the server to notify() the various
202 # threading Conditions and allow the background threads to exit
202 # threading Conditions and allow the background threads to exit
203 global server
203 global server
204 if server:
204 if server:
205 server.close()
205 server.close()
206
206
207 extensions.wrapfunction(dispatch, '_runcommand', cleanupafterdispatch)
207 extensions.wrapfunction(dispatch, '_runcommand', cleanupafterdispatch)
208
208
209 extensions.wrapfunction(ui.ui, 'config', config)
209 extensions.wrapfunction(uimod.ui, 'config', config)
210 extensions.wrapfunction(ui.ui, 'configitems', configitems)
210 extensions.wrapfunction(uimod.ui, 'configitems', configitems)
211 extensions.wrapfunction(ui.ui, 'configsuboptions', configsuboptions)
211 extensions.wrapfunction(uimod.ui, 'configsuboptions', configsuboptions)
212 extensions.wrapfunction(hg, 'defaultdest', defaultdest)
212 extensions.wrapfunction(hg, 'defaultdest', defaultdest)
213 extensions.wrapfunction(servermod, 'create_server', zc_create_server)
213 extensions.wrapfunction(servermod, 'create_server', zc_create_server)
General Comments 0
You need to be logged in to leave comments. Login now