Status: | Needs review |
---|---|
Proposed branch: | lp:~mvo/click/acquire |
Merge into: | lp:click/devel |
Diff against target: |
740 lines (+631/-3) 8 files modified
acquire/http-udm (+28/-0) acquire/pycurl (+30/-0) click/acquire.py (+369/-0) click/commands/install.py (+17/-1) click/paths.py.in (+3/-0) click/tests/test_acquire.py (+174/-0) debian/control (+2/-2) setup.py.in (+8/-0) |
To merge this branch: | bzr merge lp:~mvo/click/acquire |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Colin Watson | Needs Fixing | ||
Review via email: mp+235753@code.launchpad.net |
Commit message
Provide support to download click packages.
Description of the change
This branch implements "acquire" support in click to support "click install http://
The model is similar with what apt is using and click can reuse the apt methods to download packages. A pycurl based implementation is also provided. Feedback welcome especially suggestsions if the low-level read_messages() code can be done in a more elegant way.
PS Jenkins bot (ps-jenkins) wrote : | # |
- 542. By Michael Vogt
-
click/acquire.py: make dbus optional
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:542
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 543. By Michael Vogt
-
add missing python3-pycurl depdendency
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:543
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 544. By Michael Vogt
-
add python3-pycurl to b-d (needed during the tests
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:544
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:544
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Colin Watson (cjwatson) wrote : | # |
Overall this looks like nice work, but I think you could productively simplify some things.
- 545. By Michael Vogt
-
first round of addressing review points from Colin
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:545
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 546. By Michael Vogt
-
click/acquire.py: add note about ssl options for curl
- 547. By Michael Vogt
-
rename read_messages() -> _read_messages()
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:546
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 548. By Michael Vogt
-
add missing python3-dbus build-dependency
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:548
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Michael Vogt (mvo) wrote : | # |
> Overall this looks like nice work, but I think you could productively simplify
> some things.
Thanks a lot for your detailed review. I addressed all the points you raised now.
The only one left is the blocking/
Alternatively we can ditch some of the compatibility with the apt methods and move the progress into the method. The downside is that if/when we support parallel downloads its more difficult to present a overall progress when its done in the individual downloaders.
- 549. By Michael Vogt
-
improve error erporting
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:549
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 550. By Michael Vogt
-
merged lp:click/devel
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:550
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Unmerged revisions
- 550. By Michael Vogt
-
merged lp:click/devel
- 549. By Michael Vogt
-
improve error erporting
- 548. By Michael Vogt
-
add missing python3-dbus build-dependency
- 547. By Michael Vogt
-
rename read_messages() -> _read_messages()
- 546. By Michael Vogt
-
click/acquire.py: add note about ssl options for curl
- 545. By Michael Vogt
-
first round of addressing review points from Colin
- 544. By Michael Vogt
-
add python3-pycurl to b-d (needed during the tests
- 543. By Michael Vogt
-
add missing python3-pycurl depdendency
- 542. By Michael Vogt
-
click/acquire.py: make dbus optional
- 541. By Michael Vogt
-
remove debug message
Preview Diff
1 | === added directory 'acquire' |
2 | === added symlink 'acquire/file' |
3 | === target is u'pycurl' |
4 | === added symlink 'acquire/ftp' |
5 | === target is u'pycurl' |
6 | === added symlink 'acquire/http' |
7 | === target is u'pycurl' |
8 | === added file 'acquire/http-udm' |
9 | --- acquire/http-udm 1970-01-01 00:00:00 +0000 |
10 | +++ acquire/http-udm 2014-10-14 13:55:44 +0000 |
11 | @@ -0,0 +1,28 @@ |
12 | +#!/usr/bin/python3 |
13 | + |
14 | +# Copyright (C) 2014 Canonical Ltd. |
15 | +# Author: Michael Vogt <michael.vogt@ubuntu.com> |
16 | + |
17 | +# This program is free software: you can redistribute it and/or modify |
18 | +# it under the terms of the GNU General Public License as published by |
19 | +# the Free Software Foundation; version 3 of the License. |
20 | +# |
21 | +# This program is distributed in the hope that it will be useful, |
22 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
23 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
24 | +# GNU General Public License for more details. |
25 | +# |
26 | +# You should have received a copy of the GNU General Public License |
27 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
28 | + |
29 | +"""Acquire method for ubuntu-download-manager.""" |
30 | + |
31 | +from click.acquire import ClickAcquireMethodUbuntuDownloadManager |
32 | + |
33 | +from dbus.mainloop.glib import DBusGMainLoop |
34 | +DBusGMainLoop(set_as_default=True) |
35 | + |
36 | + |
37 | +if __name__ == "__main__": |
38 | + m = ClickAcquireMethodUbuntuDownloadManager() |
39 | + m.run() |
40 | |
41 | === added symlink 'acquire/https' |
42 | === target is u'pycurl' |
43 | === added file 'acquire/pycurl' |
44 | --- acquire/pycurl 1970-01-01 00:00:00 +0000 |
45 | +++ acquire/pycurl 2014-10-14 13:55:44 +0000 |
46 | @@ -0,0 +1,30 @@ |
47 | +#!/usr/bin/python3 |
48 | + |
49 | +# Copyright (C) 2014 Canonical Ltd. |
50 | +# Author: Michael Vogt <michael.vogt@ubuntu.com> |
51 | + |
52 | +# This program is free software: you can redistribute it and/or modify |
53 | +# it under the terms of the GNU General Public License as published by |
54 | +# the Free Software Foundation; version 3 of the License. |
55 | +# |
56 | +# This program is distributed in the hope that it will be useful, |
57 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
58 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
59 | +# GNU General Public License for more details. |
60 | +# |
61 | +# You should have received a copy of the GNU General Public License |
62 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
63 | + |
64 | +"""Acquire pycurl method click.""" |
65 | + |
66 | +import signal |
67 | +import sys |
68 | + |
69 | +from click.acquire import ClickAcquireMethodPycurl |
70 | + |
71 | + |
72 | +if __name__ == "__main__": |
73 | + # apt will send a SIGINT to all methods that do not send "Need-Cleanup: 1" |
74 | + signal.signal(signal.SIGINT, lambda *args: sys.exit()) |
75 | + m = ClickAcquireMethodPycurl() |
76 | + m.run() |
77 | |
78 | === added file 'click/acquire.py' |
79 | --- click/acquire.py 1970-01-01 00:00:00 +0000 |
80 | +++ click/acquire.py 2014-10-14 13:55:44 +0000 |
81 | @@ -0,0 +1,369 @@ |
82 | +# Copyright (C) 2014 Canonical Ltd. |
83 | +# Author: Michael Vogt <michael.vogt@ubuntu.com> |
84 | + |
85 | +# This program is free software: you can redistribute it and/or modify |
86 | +# it under the terms of the GNU General Public License as published by |
87 | +# the Free Software Foundation; version 3 of the License. |
88 | +# |
89 | +# This program is distributed in the hope that it will be useful, |
90 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
91 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
92 | +# GNU General Public License for more details. |
93 | +# |
94 | +# You should have received a copy of the GNU General Public License |
95 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
96 | + |
97 | +"""Acquire of Click packages.""" |
98 | + |
99 | +from __future__ import print_function |
100 | + |
101 | +__metaclass__ = type |
102 | +__all__ = [ |
103 | + 'ClickAcquire', |
104 | + 'ClickAcquireError', |
105 | + 'ClickAcquireMethod', |
106 | + 'ClickAcquireMethodPycurl', |
107 | + 'ClickAcquireMethodUbuntuDownloadManager', |
108 | + 'ClickAcquireStatus', |
109 | + 'ClickAcquireStatusText', |
110 | + ] |
111 | + |
112 | +import os |
113 | +import select |
114 | +import subprocess |
115 | +import sys |
116 | +from textwrap import dedent |
117 | + |
118 | +import apt_pkg |
119 | +from debian.deb822 import Deb822 |
120 | +import dbus |
121 | +from gi.repository import GLib |
122 | +import pycurl |
123 | +from six.moves.urllib.parse import urlparse |
124 | + |
125 | +from click.paths import acquire_methods_dir |
126 | + |
127 | + |
128 | +class ClickAcquireError(Exception): |
129 | + """Error during acquire""" |
130 | + pass |
131 | + |
132 | + |
133 | +def extract_one_message(lines): |
134 | + msg = [] |
135 | + while True: |
136 | + try: |
137 | + line = lines.pop(0) |
138 | + except IndexError: |
139 | + break |
140 | + if os.environ.get("CLICK_DEBUG_ACQUIRE", ""): |
141 | + sys.stderr.write("[%s] raw_line: '%s'\n" % ( |
142 | + os.getpid(), line.replace("\n", "\\n"))) |
143 | + msg.append(line) |
144 | + if len(msg) > 0 and line == "": |
145 | + # we are done, collect all remaining "\n" and stop |
146 | + while len(lines) > 0 and lines[0] == "": |
147 | + lines.pop(0) |
148 | + break |
149 | + if len(msg) < 2: |
150 | + return -1 , "" |
151 | + if os.environ.get("CLICK_DEBUG_ACQUIRE", ""): |
152 | + sys.stderr.write("[%s] msg: '%s'\n" % (os.getpid(), str(msg))) |
153 | + number = int(msg[0].split()[0]) |
154 | + return number, "\n".join(msg) |
155 | + |
156 | + |
157 | +def _read_messages(input_file, timeout=None): |
158 | + infd = input_file.fileno() |
159 | + msgs = [] |
160 | + rl, wl, xl = select.select([infd], [], [], timeout) |
161 | + if rl: |
162 | + # FIXME: 16k message limit is arbitrary |
163 | + buf = os.read(infd, 16*1024).decode("utf-8") |
164 | + if os.environ.get("CLICK_DEBUG_ACQUIRE", ""): |
165 | + sys.stderr.write("[%s] read buf: '%s'\n" % ( |
166 | + os.getpid(), buf)) |
167 | + lines = buf.split("\n") |
168 | + while True: |
169 | + number, msg = extract_one_message(lines) |
170 | + if number < 0: |
171 | + break |
172 | + msgs.append( (number, msg) ) |
173 | + return msgs |
174 | + |
175 | + |
176 | +class ClickAcquireStatus: |
177 | + """Base class for the status reporting """ |
178 | + |
179 | + def __init__(self): |
180 | + self.fetched_bytes = 0 |
181 | + self.total_bytes = 1.0 |
182 | + self.uri = "" |
183 | + |
184 | + def pulse(self): |
185 | + pass |
186 | + |
187 | + def done(self): |
188 | + pass |
189 | + |
190 | + |
191 | +class ClickAcquireStatusText(ClickAcquireStatus): |
192 | + """Text based progress reporting for the acquire progress""" |
193 | + |
194 | + def pulse(self): |
195 | + sys.stdout.write("\r") |
196 | + sys.stdout.write("[%3.2f %%] Fetching %s" % ( |
197 | + (self.fetched_bytes/self.total_bytes)*100.0, |
198 | + os.path.basename(self.uri))) |
199 | + sys.stdout.flush() |
200 | + |
201 | + def done(self): |
202 | + self.pulse() |
203 | + sys.stdout.write("\n") |
204 | + sys.stdout.write("Done fetching %s\n" % self.uri) |
205 | + |
206 | + |
207 | +# similar to Acquire/AcquireWorker |
208 | +class ClickAcquire: |
209 | + """Acquire from remote locations""" |
210 | + |
211 | + # default status reporting timeout |
212 | + TIMEOUT = 0.05 |
213 | + |
214 | + # acquire method status codecs |
215 | + M_CAPABILITIES = 100 |
216 | + M_STATUS = 102 |
217 | + M_REDIRECT = 103 |
218 | + URI_START = 200 |
219 | + URI_SUCCESS = 201 |
220 | + URI_FAILURE = 400 |
221 | + |
222 | + def __init__(self, status=ClickAcquireStatus()): |
223 | + self._fetch_queue = [] |
224 | + self._status = status |
225 | + |
226 | + def _uri_acquire(self, pipe, uri, destfile): |
227 | + cmd = dedent("""\ |
228 | + 600 URI Acquire |
229 | + URI: {uri} |
230 | + Filename: {destfile} |
231 | + |
232 | + """).format(uri=uri, destfile=destfile) |
233 | + pipe.write(cmd) |
234 | + pipe.flush() |
235 | + |
236 | + def _redirect(self, pipe, new_uri, destfile): |
237 | + self._uri_acquire(pipe, new_uri, destfile) |
238 | + |
239 | + def _run_acquire_method(self, uri, destfile): |
240 | + parsed_uri = urlparse(uri) |
241 | + cmd = os.path.join(acquire_methods_dir, parsed_uri.scheme) |
242 | + p = subprocess.Popen([cmd], |
243 | + stdout=subprocess.PIPE, stdin=subprocess.PIPE, |
244 | + universal_newlines=True) |
245 | + while True: |
246 | + for number, raw_message in _read_messages(p.stdout, self.TIMEOUT): |
247 | + message = Deb822(raw_message) |
248 | + if number == self.M_CAPABILITIES: |
249 | + self._uri_acquire(p.stdin, uri, destfile) |
250 | + elif number == self.M_STATUS: |
251 | + pass |
252 | + elif number == self.M_REDIRECT: |
253 | + self._redirect(p.stdin, message.get("New-URI"), destfile) |
254 | + elif number == self.URI_START: |
255 | + self._status.uri = message.get("URI", 0) |
256 | + self._status.total_bytes = int(message.get("Size", 0)) |
257 | + elif number == self.URI_SUCCESS: |
258 | + self._status.fetched_bytes = int(message.get("Size")) |
259 | + self._status.done() |
260 | + p.stdout.close() |
261 | + p.stdin.close() |
262 | + return True |
263 | + elif number == self.URI_FAILURE: |
264 | + p.stdout.close() |
265 | + p.stdin.close() |
266 | + raise ClickAcquireError("Uri failure for %s: %s" % ( |
267 | + message.get("uri"), message.get("Message"))) |
268 | + # update progress |
269 | + if os.path.exists(destfile): |
270 | + self._status.fetched_bytes = os.path.getsize(destfile) |
271 | + self._status.pulse() |
272 | + return False |
273 | + |
274 | + def fetch(self, uri, destfile): |
275 | + self._run_acquire_method(uri, destfile) |
276 | + |
277 | + |
278 | +# similar to the apt AcquireMethod |
279 | +class ClickAcquireMethod: |
280 | + |
281 | + M_CONFIGURATION = 601 |
282 | + M_FETCH = 600 |
283 | + |
284 | + VERSION = 0.1 |
285 | + |
286 | + def __init__(self): |
287 | + s = dedent("""\ |
288 | + 100 Capabilities |
289 | + Version: {version} |
290 | + Single-Instance: true |
291 | + |
292 | + """).format(version=self.VERSION) |
293 | + sys.stdout.write(s) |
294 | + sys.stdout.flush() |
295 | + |
296 | + def run(self): |
297 | + while True: |
298 | + msgs = _read_messages(sys.stdin) |
299 | + if not msgs: |
300 | + break |
301 | + for number, raw_message in msgs: |
302 | + message = Deb822(raw_message) |
303 | + if number == self.M_CONFIGURATION: |
304 | + pass |
305 | + elif number == self.M_FETCH: |
306 | + self.fetch(message.get("URI"), message.get("FileName")) |
307 | + |
308 | + def uri_start(self, uri, filename, size): |
309 | + # note that apt itself does not use "Filename" here because it |
310 | + # will set a filename and expects the method to use it, however |
311 | + # this will not work with UbuntuDownloadManager as it downloads |
312 | + # to its own location |
313 | + sys.stdout.write(dedent("""\ |
314 | + 200 URI Start |
315 | + URI: {uri} |
316 | + Filename: {filename} |
317 | + Size: {size} |
318 | + |
319 | + """).format(uri=uri, filename=filename, size=size)) |
320 | + sys.stdout.flush() |
321 | + |
322 | + def uri_done(self, uri, filename): |
323 | + # bug in python-apt |
324 | + hashes = apt_pkg.Hashes(filename.encode("utf-8")) |
325 | + sys.stdout.write(dedent("""\ |
326 | + 201 URI Done |
327 | + URI: {uri} |
328 | + Filename: {filename} |
329 | + Size: {size} |
330 | + Sha256-Hash: {sha256hash} |
331 | + |
332 | + """).format(uri=uri, filename=filename, size=os.path.getsize(filename), |
333 | + sha256hash=hashes.sha256)) |
334 | + sys.stdout.flush() |
335 | + |
336 | + def fail(self, uri, err="unknown error"): |
337 | + sys.stdout.write(dedent("""\ |
338 | + 400 URI Failure |
339 | + URI: {uri} |
340 | + Message: {err} |
341 | + |
342 | + """).format(uri=uri, err=err)) |
343 | + sys.stdout.flush() |
344 | + |
345 | + def fetch(self, uri, destfile): |
346 | + pass |
347 | + |
348 | + |
349 | +class ClickAcquireMethodPycurl(ClickAcquireMethod): |
350 | + |
351 | + def _write_to_file_callback(self, data): |
352 | + self._destfile_fp.write(data) |
353 | + return len(data) |
354 | + |
355 | + def _progress_info(self, dltotal, dlnow, ultotal, ulnow): |
356 | + if dltotal > 0 and not self._uri_start_reported: |
357 | + self.uri_start( |
358 | + self.uri, self._destfile_fp.name, int(dltotal)) |
359 | + self._uri_start_reported = True |
360 | + |
361 | + def fetch(self, uri, destfile): |
362 | + self._uri_start_reported = False |
363 | + self.uri = uri |
364 | + self._destfile_fp = open(destfile, "wb") |
365 | + curl = pycurl.Curl() |
366 | + curl.setopt(pycurl.URL, uri) |
367 | + curl.setopt(pycurl.WRITEFUNCTION, self._write_to_file_callback) |
368 | + curl.setopt(pycurl.NOPROGRESS, 0) |
369 | + curl.setopt(pycurl.PROGRESSFUNCTION, self._progress_info) |
370 | + curl.setopt(pycurl.FOLLOWLOCATION, 1) |
371 | + curl.setopt(pycurl.MAXREDIRS, 5) |
372 | + curl.setopt(pycurl.FAILONERROR, 1) |
373 | + # timeout 120s for conenction |
374 | + curl.setopt(pycurl.CONNECTTIMEOUT, 120) |
375 | + # timeout if the speed is 120s below 10 bytes/sec |
376 | + curl.setopt(pycurl.LOW_SPEED_LIMIT, 10); |
377 | + curl.setopt(pycurl.LOW_SPEED_TIME, 120); |
378 | + # ssl: no need to set any option here, |
379 | + # SSL_VERIFYPEER=1, SSL_VERIFYHOST=2, |
380 | + # CAINFO=/etc/ssl/certs/ca-certificates.crt |
381 | + # by default in libcurl3 these days |
382 | + try: |
383 | + curl.perform() |
384 | + self._destfile_fp.close() |
385 | + self.uri_done(uri, destfile) |
386 | + except pycurl.error as e: |
387 | + self.fail(self.uri, e.args) |
388 | + self._destfile_fp.close() |
389 | + |
390 | + |
391 | +class ClickAcquireMethodUbuntuDownloadManager(ClickAcquireMethod): |
392 | + |
393 | + MANAGER_PATH = '/' |
394 | + MANAGER_IFACE = 'com.canonical.applications.DownloadManager' |
395 | + DOWNLOAD_IFACE = 'com.canonical.applications.Download' |
396 | + |
397 | + def __init__(self): |
398 | + super(ClickAcquireMethodUbuntuDownloadManager, self).__init__() |
399 | + self.bus = dbus.SessionBus() |
400 | + self.loop = GLib.MainLoop() |
401 | + |
402 | + def _created_callback(self, dbus_path): |
403 | + pass |
404 | + |
405 | + def _finished_callback(self, path, loop): |
406 | + self.down_path = path |
407 | + loop.quit() |
408 | + |
409 | + def _progress_callback(self, total, progress): |
410 | + #print('Progress is %s/%s' % (progress, total)) |
411 | + if not self._started: |
412 | + # FIXME: we need the tmpfile path from udm |
413 | + tmpfile = "meep" |
414 | + self.uri_start(self.uri, tmpfile, total) |
415 | + self._started = True |
416 | + |
417 | + def fetch(self, uri, destfile): |
418 | + self.uri = uri |
419 | + self._started = False |
420 | + manager = self.bus.get_object( |
421 | + 'com.canonical.applications.Downloader', self.MANAGER_PATH) |
422 | + manager_dev_iface = dbus.Interface( |
423 | + manager, dbus_interface=self.MANAGER_IFACE) |
424 | + manager_dev_iface.connect_to_signal( |
425 | + 'downloadCreated', self._created_callback) |
426 | + down_path = manager_dev_iface.createDownload( |
427 | + (uri, "", "", dbus.Dictionary({}, signature="sv"), |
428 | + dbus.Dictionary({}, signature="ss"))) |
429 | + download1 = self.bus.get_object('com.canonical.applications.Downloader', |
430 | + down_path) |
431 | + download_dev_iface1 = dbus.Interface( |
432 | + download1, dbus_interface=self.DOWNLOAD_IFACE) |
433 | + download_dev_iface1.connect_to_signal( |
434 | + 'progress', self._progress_callback) |
435 | + download_dev_iface1.connect_to_signal( |
436 | + 'finished', |
437 | + lambda path: self._finished_callback(path, self.loop)) |
438 | + download_dev_iface1.start() |
439 | + self.loop.run() |
440 | + # FIXME error handling, i.e. send self.fail() |
441 | + self.uri_done(uri, self.down_path) |
442 | + |
443 | + |
444 | +if __name__ == "__main__": |
445 | + log = ClickAcquireStatusText() |
446 | + acq = ClickAcquire(log) |
447 | + uri = sys.argv[1] |
448 | + if not acq.fetch(uri, os.path.basename(uri)): |
449 | + sys.exit(1) |
450 | + sys.exit(0) |
451 | |
452 | === modified file 'click/commands/install.py' |
453 | --- click/commands/install.py 2014-09-10 12:28:49 +0000 |
454 | +++ click/commands/install.py 2014-10-14 13:55:44 +0000 |
455 | @@ -19,10 +19,17 @@ |
456 | |
457 | from optparse import OptionParser |
458 | import sys |
459 | +import tempfile |
460 | from textwrap import dedent |
461 | |
462 | +from six.moves.urllib.parse import urlparse |
463 | + |
464 | from gi.repository import Click |
465 | |
466 | +from click.acquire import ( |
467 | + ClickAcquire, |
468 | + ClickAcquireStatusText, |
469 | +) |
470 | from click.install import ClickInstaller, ClickInstallerError |
471 | |
472 | |
473 | @@ -53,10 +60,19 @@ |
474 | db.read(db_dir=None) |
475 | if options.root is not None: |
476 | db.add(options.root) |
477 | - package_path = args[0] |
478 | + package_uri = args[0] |
479 | installer = ClickInstaller( |
480 | db=db, force_missing_framework=options.force_missing_framework, |
481 | allow_unauthenticated=options.allow_unauthenticated) |
482 | + parsed_uri = urlparse(package_uri) |
483 | + if parsed_uri.scheme != "": |
484 | + t = tempfile.NamedTemporaryFile() |
485 | + package_path = t.name |
486 | + log = ClickAcquireStatusText() |
487 | + acq = ClickAcquire(log) |
488 | + acq.fetch(package_uri, package_path) |
489 | + else: |
490 | + package_path = package_uri |
491 | try: |
492 | installer.install( |
493 | package_path, user=options.user, all_users=options.all_users) |
494 | |
495 | === modified file 'click/paths.py.in' |
496 | --- click/paths.py.in 2014-05-08 15:48:01 +0000 |
497 | +++ click/paths.py.in 2014-10-14 13:55:44 +0000 |
498 | @@ -14,6 +14,9 @@ |
499 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
500 | |
501 | """Click paths.""" |
502 | +import os |
503 | |
504 | preload_path = "@pkglibdir@/libclickpreload.so" |
505 | frameworks_dir = "@pkgdatadir@/frameworks" |
506 | +acquire_methods_dir = (os.environ.get("CLICK_ACQUIRE_METHODS_DIR", "") or |
507 | + "@pkglibdir@/acquire") |
508 | |
509 | === added file 'click/tests/test_acquire.py' |
510 | --- click/tests/test_acquire.py 1970-01-01 00:00:00 +0000 |
511 | +++ click/tests/test_acquire.py 2014-10-14 13:55:44 +0000 |
512 | @@ -0,0 +1,174 @@ |
513 | +# Copyright (C) 2014 Canonical Ltd. |
514 | +# Author: Michael Vogt <michael.vogt@ubuntu.com> |
515 | + |
516 | +# This program is free software: you can redistribute it and/or modify |
517 | +# it under the terms of the GNU General Public License as published by |
518 | +# the Free Software Foundation; version 3 of the License. |
519 | +# |
520 | +# This program is distributed in the hope that it will be useful, |
521 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
522 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
523 | +# GNU General Public License for more details. |
524 | +# |
525 | +# You should have received a copy of the GNU General Public License |
526 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
527 | + |
528 | +"""Unit tests for click.acquire.""" |
529 | + |
530 | +from __future__ import print_function |
531 | + |
532 | +__metaclass__ = type |
533 | +__all__ = [ |
534 | + 'TestClickAcquire', |
535 | + ] |
536 | + |
537 | +import multiprocessing |
538 | +import os.path |
539 | +import time |
540 | + |
541 | +from six.moves import ( |
542 | + SimpleHTTPServer, |
543 | + socketserver, |
544 | +) |
545 | + |
546 | +from click.acquire import ( |
547 | + ClickAcquire, |
548 | + ClickAcquireError, |
549 | + _read_messages, |
550 | +) |
551 | + |
552 | +from click.tests.helpers import ( |
553 | + TestCase, |
554 | +) |
555 | + |
556 | +import click.acquire |
557 | +click.acquire.acquire_methods_dir = os.path.join( |
558 | + os.path.dirname(__file__), "..", "..", "acquire") |
559 | +os.environ["PYTHONPATH"] = os.path.abspath( |
560 | + os.path.join(os.path.dirname(__file__), "..", "..")) |
561 | + |
562 | +# local httpd |
563 | +LOCALHOST = "localhost" |
564 | +PORT = 8128 |
565 | + |
566 | + |
567 | +class MyHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): |
568 | + def log_message(self, format, *args): |
569 | + pass |
570 | + |
571 | + |
572 | +class MySocketServer(socketserver.TCPServer): |
573 | + allow_reuse_address = True |
574 | + |
575 | + |
576 | +class Httpd(multiprocessing.Process): |
577 | + |
578 | + def __init__(self, basedir): |
579 | + super(Httpd, self).__init__() |
580 | + self.basedir = basedir |
581 | + |
582 | + def run(self): |
583 | + os.chdir(self.basedir) |
584 | + server = MySocketServer((LOCALHOST, PORT), MyHandler) |
585 | + while True: |
586 | + server.handle_request() |
587 | + server.shutdown() |
588 | + |
589 | + def stop(self): |
590 | + self.terminate() |
591 | + self.join() |
592 | + |
593 | + |
594 | +class TestClickAcquire(TestCase): |
595 | + |
596 | + def setUp(self): |
597 | + super(TestClickAcquire, self).setUp() |
598 | + self.use_temp_dir() |
599 | + self.httpd = Httpd(self.temp_dir) |
600 | + self.httpd.start() |
601 | + |
602 | + def tearDown(self): |
603 | + self.httpd.stop() |
604 | + |
605 | + def test_acquire_fail(self): |
606 | + acq = ClickAcquire() |
607 | + destfile = os.path.join(self.temp_dir, "meep") |
608 | + with self.assertRaises(ClickAcquireError): |
609 | + acq.fetch("http://%s:%s/not-here" % (LOCALHOST, PORT), destfile) |
610 | + |
611 | + def test_acquire_good(self): |
612 | + acq = ClickAcquire() |
613 | + destfile = os.path.join(self.temp_dir, "meep") |
614 | + canary_str = "hello" |
615 | + with open(os.path.join(self.temp_dir, "i-am-here"), "w") as f: |
616 | + f.write(canary_str) |
617 | + acq.fetch("http://%s:%s/i-am-here" % (LOCALHOST, PORT), destfile) |
618 | + with open(destfile) as f: |
619 | + data = f.read() |
620 | + self.assertEqual(canary_str, data) |
621 | + |
622 | + |
623 | +class TestClickAcquireReadMessages(TestCase): |
624 | + |
625 | + def test_forked_read_message(self): |
626 | + read_end, write_end = os.pipe() |
627 | + pid = os.fork() |
628 | + if pid == 0: |
629 | + os.close(write_end) |
630 | + with os.fdopen(read_end) as f: |
631 | + number, msg = _read_messages(f)[0] |
632 | + self.assertEqual(msg, "102 Status\nFoo: Bar\n") |
633 | + self.assertEqual(number, 102) |
634 | + os._exit(0) |
635 | + os.close(read_end) |
636 | + os.write(write_end, "102 Status\nFoo: Bar\n\n".encode("utf-8")) |
637 | + os.waitpid(pid, 0) |
638 | + os.close(write_end) |
639 | + |
640 | + def test_forked_read_multiple_messages(self): |
641 | + read_end, write_end = os.pipe() |
642 | + pid = os.fork() |
643 | + if pid == 0: |
644 | + os.close(write_end) |
645 | + with os.fdopen(read_end) as f: |
646 | + msgs = _read_messages(f) |
647 | + self.assertEqual(len(msgs), 2) |
648 | + self.assertEqual(msgs[0][0], 100) |
649 | + self.assertEqual(msgs[1][0], 200) |
650 | + os._exit(0) |
651 | + os.close(read_end) |
652 | + os.write(write_end, "100 Status\n\n200 meep\n\n".encode("utf-8")) |
653 | + os.waitpid(pid, 0) |
654 | + os.close(write_end) |
655 | + |
656 | + def test_forked_read_message_broken_pipe(self): |
657 | + read_end, write_end = os.pipe() |
658 | + pid = os.fork() |
659 | + if pid == 0: |
660 | + os.close(write_end) |
661 | + with os.fdopen(read_end) as f: |
662 | + self.assertEqual(_read_messages(f), []) |
663 | + os._exit(0) |
664 | + os.close(read_end) |
665 | + os.write(write_end, "102 close-before-msg".encode("utf-8")) |
666 | + os.close(write_end) |
667 | + os.waitpid(pid, 0) |
668 | + |
669 | + def test_forked_read_message_with_timeout(self): |
670 | + read_end, write_end = os.pipe() |
671 | + pid = os.fork() |
672 | + if pid == 0: |
673 | + os.close(write_end) |
674 | + with os.fdopen(read_end) as f: |
675 | + self.assertEqual(_read_messages(f, 0.1), []) |
676 | + msgs = _read_messages(f) |
677 | + self.assertEqual(len(msgs), 2) |
678 | + self.assertEqual(msgs[0][1], "100 aaa\n") |
679 | + self.assertEqual(msgs[1][1], "200 bbb\n") |
680 | + os._exit(0) |
681 | + os.close(read_end) |
682 | + # simulate slow method |
683 | + time.sleep(0.2) |
684 | + os.write(write_end, "100 aaa\n\n".encode("utf-8")) |
685 | + os.write(write_end, "200 bbb\n\n".encode("utf-8")) |
686 | + os.waitpid(pid, 0) |
687 | |
688 | === modified file 'debian/control' |
689 | --- debian/control 2014-10-10 07:10:23 +0000 |
690 | +++ debian/control 2014-10-14 13:55:44 +0000 |
691 | @@ -3,7 +3,7 @@ |
692 | Priority: optional |
693 | Maintainer: Colin Watson <cjwatson@ubuntu.com> |
694 | Standards-Version: 3.9.5 |
695 | -Build-Depends: debhelper (>= 9~), dh-autoreconf, intltool, python3:any (>= 3.2), python3-all:any, python3-setuptools, python3-apt, python3-debian, python3-gi, python3:any (>= 3.3) | python3-mock, pep8, python3-pep8, pyflakes, python3-sphinx, pkg-config, valac, gobject-introspection (>= 0.6.7), libgirepository1.0-dev (>= 0.6.7), libglib2.0-dev (>= 2.34), gir1.2-glib-2.0, libjson-glib-dev (>= 0.10), libgee-0.8-dev, libpackagekit-glib2-dev (>= 0.7.2), python3-coverage, python3-six, dh-systemd (>= 1.3) |
696 | +Build-Depends: debhelper (>= 9~), dh-autoreconf, intltool, python3:any (>= 3.2), python3-all:any, python3-setuptools, python3-apt, python3-debian, python3-gi, python3:any (>= 3.3) | python3-mock, pep8, python3-pep8, pyflakes, python3-sphinx, pkg-config, valac, gobject-introspection (>= 0.6.7), libgirepository1.0-dev (>= 0.6.7), libglib2.0-dev (>= 2.34), gir1.2-glib-2.0, libjson-glib-dev (>= 0.10), libgee-0.8-dev, libpackagekit-glib2-dev (>= 0.7.2), python3-coverage, python3-six, dh-systemd (>= 1.3), python3-pycurl, python3-dbus |
697 | Vcs-Bzr: https://code.launchpad.net/~ubuntu-managed-branches/click/click |
698 | Vcs-Browser: http://bazaar.launchpad.net/~ubuntu-managed-branches/click/click/files |
699 | X-Auto-Uploader: no-rewrite-version |
700 | @@ -14,7 +14,7 @@ |
701 | Package: click |
702 | Architecture: any |
703 | Pre-Depends: ${misc:Pre-Depends} |
704 | -Depends: ${shlibs:Depends}, ${misc:Depends}, ${python3:Depends}, python3-click (= ${binary:Version}), adduser |
705 | +Depends: ${shlibs:Depends}, ${misc:Depends}, ${python3:Depends}, python3-click (= ${binary:Version}), adduser, python3-pycurl, python3-dbus |
706 | Recommends: click-apparmor |
707 | Suggests: click-reviewers-tools (>= 0.9), ubuntu-app-launch-tools | upstart-app-launch-tools |
708 | Conflicts: click-package |
709 | |
710 | === modified file 'setup.py.in' |
711 | --- setup.py.in 2014-06-30 16:48:31 +0000 |
712 | +++ setup.py.in 2014-10-14 13:55:44 +0000 |
713 | @@ -1,5 +1,6 @@ |
714 | #! /usr/bin/env python3 |
715 | |
716 | +import glob |
717 | import sys |
718 | |
719 | from setuptools import find_packages, setup |
720 | @@ -18,6 +19,8 @@ |
721 | if sys.version < "3.3": |
722 | require('mock') |
723 | require('chardet') |
724 | +require('click.paths') |
725 | +import click.paths |
726 | |
727 | if "@GCOVR@": |
728 | require('coverage') |
729 | @@ -47,6 +50,11 @@ |
730 | license="GNU GPL", |
731 | packages=find_packages(), |
732 | scripts=['bin/click'], |
733 | + data_files=[ |
734 | + # we need to remove the prefix here or we add it twice |
735 | + (click.paths.acquire_methods_dir[1:].lstrip(sys.prefix), |
736 | + glob.glob("acquire/*")), |
737 | + ], |
738 | install_requires=requirements, |
739 | cmdclass={"test": test_extra}, |
740 | test_suite="click.tests", |
FAILED: Continuous integration, rev:541 /code.launchpad .net/~mvo/ click/acquire/ +merge/ 235753/ +edit-commit- message
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
http:// jenkins. qa.ubuntu. com/job/ click-devel- ci/71/ jenkins. qa.ubuntu. com/job/ click-devel- utopic- amd64-ci/ 73/console jenkins. qa.ubuntu. com/job/ click-devel- utopic- armhf-ci/ 71/console jenkins. qa.ubuntu. com/job/ click-devel- utopic- i386-ci/ 71/console
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/click- devel-ci/ 71/rebuild
http://