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