1 import tempfile
, os
, sys
2 from model
import Interface
, DownloadSource
, SafeException
4 from urllib2
import urlopen
, HTTPError
, URLError
5 from logging
import warn
7 download_starting
= "starting" # Waiting for UI to start it
8 download_fetching
= "fetching" # In progress
9 download_checking
= "checking" # Checking GPG sig (possibly interactive)
10 download_complete
= "complete" # Downloaded and cached OK
11 download_failed
= "failed"
13 _downloads
= {} # URL -> Download
15 class DownloadError(SafeException
):
20 tempfile
= None # Stream for result
21 status
= None # download_*
27 def __init__(self
, url
):
28 "Initial status is starting."
30 self
.status
= download_starting
33 """Returns stderr stream from child. Call error_stream_closed() when
35 assert self
.status
== download_starting
36 self
.tempfile
= tempfile
.TemporaryFile(prefix
= 'injector-dl-data-')
38 error_r
, error_w
= os
.pipe()
41 self
.child_pid
= os
.fork()
42 if self
.child_pid
== 0:
48 self
.download_as_child()
54 self
.status
= download_fetching
55 return os
.fdopen(error_r
, 'r')
57 def download_as_child(self
):
60 #print "Child downloading", self.url
61 if self
.url
.startswith('/'):
62 if not os
.path
.isfile(self
.url
):
63 print >>sys
.stderr
, "File '%s' does not " \
67 elif self
.url
.startswith('http:') or self
.url
.startswith('ftp:'):
68 src
= urlopen(self
.url
)
70 raise Exception('Unsupported URL protocol in: ' + self
.url
)
72 shutil
.copyfileobj(src
, self
.tempfile
)
76 except (HTTPError
, URLError
), ex
:
77 print >>sys
.stderr
, "Error downloading '" + self
.url
+ "': " + str(ex
)
81 def error_stream_data(self
, data
):
82 """Passed with result of os.read(error_stream, n). Can be
83 called multiple times, once for each read."""
85 assert self
.status
is download_fetching
88 def error_stream_closed(self
):
89 """Ends a download. Status changes from fetching to checking.
90 Returns data stream."""
91 assert self
.status
is download_fetching
92 assert self
.tempfile
is not None
93 assert self
.child_pid
is not None
95 pid
, status
= os
.waitpid(self
.child_pid
, 0)
96 assert pid
== self
.child_pid
102 if status
and not errors
:
103 errors
= 'Download process exited with error status ' \
104 'code ' + hex(status
)
106 stream
= self
.tempfile
110 self
.status
= download_failed
111 raise DownloadError(errors
)
113 self
.status
= download_checking
119 if self
.child_pid
is not None:
120 warn("Killing download process %s", self
.child_pid
)
122 os
.kill(self
.child_pid
, signal
.SIGTERM
)
124 self
.status
= download_failed
126 class InterfaceDownload(Download
):
127 def __init__(self
, interface
, url
= None):
128 assert isinstance(interface
, Interface
)
129 Download
.__init
__(self
, url
or interface
.uri
)
130 self
.interface
= interface
132 class ImplementationDownload(Download
):
133 def __init__(self
, source
):
134 assert isinstance(source
, DownloadSource
)
135 Download
.__init
__(self
, source
.url
)
138 def error_stream_closed(self
):
139 stream
= Download
.error_stream_closed(self
)
140 size
= os
.fstat(stream
.fileno()).st_size
141 if size
!= self
.source
.size
:
142 raise SafeException('Downloaded archive has incorrect size.\n'
144 'Expected: %d bytes\n'
145 'Received: %d bytes' % (self
.url
, self
.source
.size
, size
))
148 def get_current_fraction(self
):
149 if self
.status
is download_starting
:
151 if self
.tempfile
is None:
153 current_size
= os
.fstat(self
.tempfile
.fileno()).st_size
154 return float(current_size
) / self
.source
.size
156 def begin_iface_download(interface
, force
):
157 """Start downloading interface.
158 If a Download object already exists (any state; in progress, failed or
159 completed) and force is False, does nothing and returns None.
160 If force is True, any existing download is destroyed and a new one created."""
161 return _begin_download(InterfaceDownload(interface
), force
)
163 path_dirs
= os
.environ
.get('PATH', '/bin:/usr/bin').split(':')
164 def _available_in_path(command
):
166 if os
.path
.isfile(os
.path
.join(x
, command
)):
170 def begin_impl_download(source
, force
= False):
171 #print "Need to downlaod", source.url
172 if source
.url
.endswith('.rpm'):
173 if not _available_in_path('rpm2cpio'):
174 raise SafeException("The URL '%s' looks like an RPM, but you don't have the rpm2cpio command "
175 "I need to extract it. Install the 'rpm' package first (this works even if "
176 "you're on a non-RPM-based distribution such as Debian)." % source
.url
)
177 return _begin_download(ImplementationDownload(source
), force
)
179 def _begin_download(new_dl
, force
):
180 dl
= _downloads
.get(new_dl
.url
, None)
184 del _downloads
[new_dl
.url
]
186 return None # Already downloading
188 _downloads
[new_dl
.url
] = new_dl
190 assert new_dl
.status
== download_starting