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