9
9
# Copyright (C) 2006, Thomas Leonard
10
10
# See the README file for details, or visit http://0install.net.
12
import tempfile, os, sys
12
import tempfile, os, sys, subprocess
13
13
from zeroinstall import SafeException
14
14
from zeroinstall.support import tasks
45
45
@type downloaded: L{tasks.Blocker}
46
46
@ivar hint: hint passed by and for caller
48
@ivar child_pid: the child process's PID
48
@ivar child: the child process
49
@type child: L{subprocess.Popen}
50
50
@ivar aborted_by_user: whether anyone has called L{abort}
51
51
@type aborted_by_user: bool
53
53
__slots__ = ['url', 'tempfile', 'status', 'errors', 'expected_size', 'downloaded',
54
'hint', 'child_pid', '_final_total_size', 'aborted_by_user']
54
'hint', 'child', '_final_total_size', 'aborted_by_user']
56
56
def __init__(self, url, hint = None):
57
57
"""Create a new download object.
86
86
def _do_download(self):
87
87
"""Will trigger L{downloaded} when done (on success or failure)."""
88
error_r, error_w = os.pipe()
91
self.child_pid = os.fork()
92
if self.child_pid == 0:
98
self._download_as_child()
90
# Can't use fork here, because Windows doesn't have it
91
assert self.child is None, self.child
92
self.child = subprocess.Popen([sys.executable, __file__, self.url], stderr = subprocess.PIPE, stdout = self.tempfile)
104
94
self.status = download_fetching
106
96
# Wait for child to exit, collecting error output as we go
109
yield tasks.InputBlocker(error_r, "read data from " + self.url)
99
yield tasks.InputBlocker(self.child.stderr, "read data from " + self.url)
111
data = os.read(error_r, 100)
101
data = os.read(self.child.stderr.fileno(), 100)
114
104
self.errors += data
118
108
assert self.status is download_fetching
119
109
assert self.tempfile is not None
120
assert self.child_pid is not None
110
assert self.child is not None
122
pid, status = os.waitpid(self.child_pid, 0)
123
assert pid == self.child_pid
124
self.child_pid = None
112
status = self.child.wait()
126
115
errors = self.errors
127
116
self.errors = None
158
147
self.status = download_complete
159
148
self.downloaded.trigger()
161
def _download_as_child(self):
163
from httplib import HTTPException
164
from urllib2 import urlopen, HTTPError, URLError
167
#print "Child downloading", self.url
168
if self.url.startswith('/'):
169
if not os.path.isfile(self.url):
170
print >>sys.stderr, "File '%s' does not " \
174
elif self.url.startswith('http:') or self.url.startswith('ftp:'):
175
src = urlopen(self.url)
177
raise Exception('Unsupported URL protocol in: ' + self.url)
179
shutil.copyfileobj(src, self.tempfile, length=1)
180
self.tempfile.flush()
183
except (HTTPError, URLError, HTTPException), ex:
184
print >>sys.stderr, "Error downloading '" + self.url + "': " + (str(ex) or str(ex.__class__.__name__))
186
traceback.print_exc()
189
151
"""Signal the current download to stop.
190
152
@postcondition: L{aborted_by_user}"""
191
if self.child_pid is not None:
192
info("Killing download process %s", self.child_pid)
153
if self.child is not None:
154
info("Killing download process %s", self.child.pid)
194
os.kill(self.child_pid, signal.SIGTERM)
156
os.kill(self.child.pid, signal.SIGTERM)
195
157
self.aborted_by_user = True
197
159
self.status = download_failed
223
185
def __str__(self):
224
186
return "<Download from %s>" % self.url
188
if __name__ == '__main__':
189
def _download_as_child(url):
190
from httplib import HTTPException
191
from urllib2 import urlopen, HTTPError, URLError
194
#print "Child downloading", url
195
if url.startswith('/'):
196
if not os.path.isfile(url):
197
print >>sys.stderr, "File '%s' does not " \
201
elif url.startswith('http:') or url.startswith('ftp:'):
204
raise Exception('Unsupported URL protocol in: ' + url)
206
if hasattr(src, 'fileno'):
209
except AttributeError, ex:
210
# Hack: fileno on sockets broken in Python 2.4 and 2.5
211
# http://bugs.python.org/issue1327971
212
fd = src.fp._sock.fp.fileno()
215
data = os.read(fd, 256)
219
# Windows doesn't have fileno
226
except (HTTPError, URLError, HTTPException), ex:
227
print >>sys.stderr, "Error downloading '" + url + "': " + (str(ex) or str(ex.__class__.__name__))
229
assert len(sys.argv) == 2, "Usage: download URL, not %s" % sys.argv
230
_download_as_child(sys.argv[1])