~ubuntu-branches/ubuntu/karmic/zeroinstall-injector/karmic

« back to all changes in this revision

Viewing changes to zeroinstall/injector/download.py

  • Committer: Bazaar Package Importer
  • Author(s): Thomas Leonard
  • Date: 2008-09-06 11:24:04 UTC
  • mfrom: (1.1.7 upstream) (6.1.2 sid)
  • Revision ID: james.westby@ubuntu.com-20080906112404-oogt9d2ir3tx8238
Tags: 0.36-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
9
9
# Copyright (C) 2006, Thomas Leonard
10
10
# See the README file for details, or visit http://0install.net.
11
11
 
12
 
import tempfile, os, sys
 
12
import tempfile, os, sys, subprocess
13
13
from zeroinstall import SafeException
14
14
from zeroinstall.support import tasks
15
15
import traceback
45
45
        @type downloaded: L{tasks.Blocker}
46
46
        @ivar hint: hint passed by and for caller
47
47
        @type hint: object
48
 
        @ivar child_pid: the child process's PID
49
 
        @type child_pid: int
 
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
52
52
        """
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']
55
55
 
56
56
        def __init__(self, url, hint = None):
57
57
                """Create a new download object.
70
70
                self.expected_size = None       # Final size (excluding skipped bytes)
71
71
                self._final_total_size = None   # Set when download is finished
72
72
 
73
 
                self.child_pid = None
 
73
                self.child = None
74
74
        
75
75
        def start(self):
76
76
                """Create a temporary file and begin the download.
85
85
 
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()
89
88
                self.errors = ''
90
89
 
91
 
                self.child_pid = os.fork()
92
 
                if self.child_pid == 0:
93
 
                        # We are the child
94
 
                        try:
95
 
                                os.close(error_r)
96
 
                                os.dup2(error_w, 2)
97
 
                                os.close(error_w)
98
 
                                self._download_as_child()
99
 
                        finally:
100
 
                                os._exit(1)
 
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)
101
93
 
102
 
                # We are the parent
103
 
                os.close(error_w)
104
94
                self.status = download_fetching
105
95
 
106
96
                # Wait for child to exit, collecting error output as we go
107
97
 
108
98
                while True:
109
 
                        yield tasks.InputBlocker(error_r, "read data from " + self.url)
 
99
                        yield tasks.InputBlocker(self.child.stderr, "read data from " + self.url)
110
100
 
111
 
                        data = os.read(error_r, 100)
 
101
                        data = os.read(self.child.stderr.fileno(), 100)
112
102
                        if not data:
113
103
                                break
114
104
                        self.errors += data
117
107
 
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
121
111
 
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()
 
113
                self.child = None
125
114
 
126
115
                errors = self.errors
127
116
                self.errors = None
158
147
                        self.status = download_complete
159
148
                        self.downloaded.trigger()
160
149
        
161
 
        def _download_as_child(self):
162
 
                try:
163
 
                        from httplib import HTTPException
164
 
                        from urllib2 import urlopen, HTTPError, URLError
165
 
                        import shutil
166
 
                        try:
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 " \
171
 
                                                        "exist!" % self.url
172
 
                                                return
173
 
                                        src = file(self.url)
174
 
                                elif self.url.startswith('http:') or self.url.startswith('ftp:'):
175
 
                                        src = urlopen(self.url)
176
 
                                else:
177
 
                                        raise Exception('Unsupported URL protocol in: ' + self.url)
178
 
 
179
 
                                shutil.copyfileobj(src, self.tempfile, length=1)
180
 
                                self.tempfile.flush()
181
 
                                
182
 
                                os._exit(0)
183
 
                        except (HTTPError, URLError, HTTPException), ex:
184
 
                                print >>sys.stderr, "Error downloading '" + self.url + "': " + (str(ex) or str(ex.__class__.__name__))
185
 
                except:
186
 
                        traceback.print_exc()
187
 
        
188
150
        def abort(self):
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)
193
155
                        import signal
194
 
                        os.kill(self.child_pid, signal.SIGTERM)
 
156
                        os.kill(self.child.pid, signal.SIGTERM)
195
157
                        self.aborted_by_user = True
196
158
                else:
197
159
                        self.status = download_failed
222
184
        
223
185
        def __str__(self):
224
186
                return "<Download from %s>" % self.url
 
187
 
 
188
if __name__ == '__main__':
 
189
        def _download_as_child(url):
 
190
                from httplib import HTTPException
 
191
                from urllib2 import urlopen, HTTPError, URLError
 
192
                import shutil
 
193
                try:
 
194
                        #print "Child downloading", url
 
195
                        if url.startswith('/'):
 
196
                                if not os.path.isfile(url):
 
197
                                        print >>sys.stderr, "File '%s' does not " \
 
198
                                                "exist!" % url
 
199
                                        return
 
200
                                src = file(url)
 
201
                        elif url.startswith('http:') or url.startswith('ftp:'):
 
202
                                src = urlopen(url)
 
203
                        else:
 
204
                                raise Exception('Unsupported URL protocol in: ' + url)
 
205
 
 
206
                        if hasattr(src, 'fileno'):
 
207
                                try:
 
208
                                        fd = 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()
 
213
 
 
214
                                while True:
 
215
                                        data = os.read(fd, 256)
 
216
                                        if not data: break
 
217
                                        os.write(1, data)
 
218
                        else:
 
219
                                # Windows doesn't have fileno
 
220
                                while True:
 
221
                                        data = src.read(1)
 
222
                                        if not data: break
 
223
                                        os.write(1, data)
 
224
 
 
225
                        sys.exit(0)
 
226
                except (HTTPError, URLError, HTTPException), ex:
 
227
                        print >>sys.stderr, "Error downloading '" + url + "': " + (str(ex) or str(ex.__class__.__name__))
 
228
                        sys.exit(1)
 
229
        assert len(sys.argv) == 2, "Usage: download URL, not %s" % sys.argv
 
230
        _download_as_child(sys.argv[1])