Skip to content

Commit 7d12ccf

Browse files
author
neil
committed
multi thread download
1 parent 1db8d21 commit 7d12ccf

File tree

1 file changed

+120
-0
lines changed

1 file changed

+120
-0
lines changed

anyvm.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import shutil
1414
import shlex
1515
import re
16+
import threading
1617

1718
# Python 2/3 compatibility for urllib and input
1819
try:
@@ -193,10 +194,129 @@ def fetch_url_content(url):
193194
time.sleep(3)
194195
return None
195196

197+
def get_remote_file_info(url):
198+
req = Request(url)
199+
req.add_header('User-Agent', 'python-qemu-script')
200+
if hasattr(req, 'method'):
201+
req.method = 'HEAD'
202+
else:
203+
try:
204+
req.get_method = lambda: 'HEAD'
205+
except Exception:
206+
pass
207+
try:
208+
resp = urlopen(req)
209+
length = int(resp.headers.get('Content-Length', '0'))
210+
accept_ranges = resp.headers.get('Accept-Ranges', '').lower() == 'bytes'
211+
try:
212+
resp.close()
213+
except Exception:
214+
pass
215+
return length, accept_ranges
216+
except Exception:
217+
return 0, False
218+
219+
def download_file_multithread(url, dest, total_size, show_progress):
220+
tmp_dest = dest + ".part"
221+
try:
222+
with open(tmp_dest, 'wb') as f:
223+
f.truncate(total_size)
224+
except IOError:
225+
return False
226+
227+
num_threads = min(4, max(1, total_size // (8 * 1024 * 1024)))
228+
chunk_size = (total_size + num_threads - 1) // num_threads
229+
progress_lock = threading.Lock()
230+
downloaded = [0]
231+
last_percent = [-1]
232+
errors = []
233+
stop_event = threading.Event()
234+
235+
def update_progress():
236+
if not show_progress:
237+
return
238+
percent = int(downloaded[0] * 100 / total_size)
239+
if percent != last_percent[0]:
240+
last_percent[0] = percent
241+
sys.stdout.write("\r {:3d}% ({:.1f}/{:.1f} MB)".format(
242+
percent,
243+
downloaded[0] / (1024 * 1024.0),
244+
total_size / (1024 * 1024.0)
245+
))
246+
sys.stdout.flush()
247+
248+
def worker(start, end):
249+
if stop_event.is_set():
250+
return
251+
req = Request(url)
252+
req.add_header('User-Agent', 'python-qemu-script')
253+
req.add_header('Range', 'bytes={}-{}'.format(start, end))
254+
try:
255+
resp = urlopen(req)
256+
with open(tmp_dest, 'r+b') as f:
257+
f.seek(start)
258+
while not stop_event.is_set():
259+
chunk = resp.read(128 * 1024)
260+
if not chunk:
261+
break
262+
f.write(chunk)
263+
if show_progress:
264+
with progress_lock:
265+
downloaded[0] += len(chunk)
266+
update_progress()
267+
try:
268+
resp.close()
269+
except Exception:
270+
pass
271+
except Exception as e:
272+
stop_event.set()
273+
with progress_lock:
274+
errors.append(e)
275+
276+
threads = []
277+
for index in range(num_threads):
278+
start = index * chunk_size
279+
end = min(total_size - 1, start + chunk_size - 1)
280+
if start > end:
281+
break
282+
t = threading.Thread(target=worker, args=(start, end))
283+
t.daemon = True
284+
t.start()
285+
threads.append(t)
286+
287+
for t in threads:
288+
t.join()
289+
290+
if show_progress:
291+
sys.stdout.write("\n")
292+
sys.stdout.flush()
293+
294+
if errors or stop_event.is_set():
295+
try:
296+
os.remove(tmp_dest)
297+
except OSError:
298+
pass
299+
return False
300+
301+
try:
302+
if hasattr(os, 'replace'):
303+
os.replace(tmp_dest, dest)
304+
else:
305+
shutil.move(tmp_dest, dest)
306+
except Exception:
307+
return False
308+
return True
309+
196310
def download_file(url, dest):
197311
log("Downloading " + url)
198312
show_progress = sys.stdout.isatty()
199313

314+
size, can_range = get_remote_file_info(url)
315+
if can_range and size > 0:
316+
if download_file_multithread(url, dest, size, show_progress):
317+
return True
318+
log("Falling back to single-thread download...")
319+
200320
def make_progress_hook():
201321
if not show_progress:
202322
return None

0 commit comments

Comments
 (0)