22
22
import tarfile
23
23
import zipfile
24
24
import re
25
+ import time
26
+ import argparse
27
+
28
+ # Initialize start_time globally
29
+ start_time = - 1
25
30
26
31
if sys .version_info [0 ] == 3 :
27
32
from urllib .request import urlretrieve
@@ -62,53 +67,174 @@ def mkdir_p(path):
62
67
raise
63
68
64
69
65
- def report_progress (count , blockSize , totalSize ):
70
+ def format_time (seconds ):
71
+ minutes , seconds = divmod (seconds , 60 )
72
+ return "{:02}:{:05.2f}" .format (int (minutes ), seconds )
73
+
74
+
75
+ def report_progress (block_count , block_size , total_size , start_time ):
76
+ downloaded_size = block_count * block_size
77
+ time_elapsed = time .time () - start_time
78
+ current_speed = downloaded_size / (time_elapsed )
79
+
66
80
if sys .stdout .isatty ():
67
- if totalSize > 0 :
68
- percent = int (count * blockSize * 100 / totalSize )
69
- percent = min (100 , percent )
70
- sys .stdout .write ("\r %d%%" % percent )
81
+ if total_size > 0 :
82
+ percent_complete = min ((downloaded_size / total_size ) * 100 , 100 )
83
+ sys .stdout .write (
84
+ f"\r Downloading... { percent_complete :.2f} % - { downloaded_size / 1024 / 1024 :.2f} MB downloaded - Elapsed Time: { format_time (time_elapsed )} - Speed: { current_speed / 1024 / 1024 :.2f} MB/s" # noqa: E501
85
+ )
71
86
else :
72
- sofar = (count * blockSize ) / 1024
73
- if sofar >= 1000 :
74
- sofar /= 1024
75
- sys .stdout .write ("\r %dMB " % (sofar ))
76
- else :
77
- sys .stdout .write ("\r %dKB" % (sofar ))
87
+ sys .stdout .write (
88
+ f"\r Downloading... { downloaded_size / 1024 / 1024 :.2f} MB downloaded - Elapsed Time: { format_time (time_elapsed )} - Speed: { current_speed / 1024 / 1024 :.2f} MB/s" # noqa: E501
89
+ )
78
90
sys .stdout .flush ()
79
91
80
92
81
- def unpack (filename , destination ):
93
+ def print_verification_progress (total_files , i , t1 ):
94
+ if sys .stdout .isatty ():
95
+ sys .stdout .write (f"\r Elapsed time { format_time (time .time () - t1 )} " )
96
+ sys .stdout .flush ()
97
+
98
+
99
+ def verify_files (filename , destination , rename_to ):
100
+ # Set the path of the extracted directory
101
+ extracted_dir_path = destination
102
+ t1 = time .time ()
103
+ if filename .endswith (".zip" ):
104
+ try :
105
+ with zipfile .ZipFile (filename , "r" ) as archive :
106
+ first_dir = archive .namelist ()[0 ].split ("/" )[0 ]
107
+ total_files = len (archive .namelist ())
108
+ for i , zipped_file in enumerate (archive .namelist (), 1 ):
109
+ local_path = os .path .join (extracted_dir_path , zipped_file .replace (first_dir , rename_to , 1 ))
110
+ if not os .path .exists (local_path ):
111
+ # print(f'\nMissing {zipped_file} on location: {extracted_dir_path}')
112
+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
113
+ return False
114
+ print_verification_progress (total_files , i , t1 )
115
+ except zipfile .BadZipFile :
116
+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
117
+ return False
118
+ elif filename .endswith (".tar.gz" ):
119
+ try :
120
+ with tarfile .open (filename , "r:gz" ) as archive :
121
+ first_dir = archive .getnames ()[0 ].split ("/" )[0 ]
122
+ total_files = len (archive .getnames ())
123
+ for i , zipped_file in enumerate (archive .getnames (), 1 ):
124
+ local_path = os .path .join (extracted_dir_path , zipped_file .replace (first_dir , rename_to , 1 ))
125
+ if not os .path .exists (local_path ):
126
+ # print(f'\nMissing {zipped_file} on location: {extracted_dir_path}')
127
+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
128
+ return False
129
+ print_verification_progress (total_files , i , t1 )
130
+ except tarfile .ReadError :
131
+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
132
+ return False
133
+ elif filename .endswith (".tar.xz" ):
134
+ try :
135
+ with tarfile .open (filename , "r:xz" ) as archive :
136
+ first_dir = archive .getnames ()[0 ].split ("/" )[0 ]
137
+ total_files = len (archive .getnames ())
138
+ for i , zipped_file in enumerate (archive .getnames (), 1 ):
139
+ local_path = os .path .join (extracted_dir_path , zipped_file .replace (first_dir , rename_to , 1 ))
140
+ if not os .path .exists (local_path ):
141
+ print (f'\n Missing { zipped_file } on location: { extracted_dir_path } ' )
142
+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
143
+ return False
144
+ print_verification_progress (total_files , i , t1 )
145
+ except tarfile .ReadError :
146
+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
147
+ return False
148
+ else :
149
+ raise NotImplementedError ("Unsupported archive type" )
150
+
151
+ # if(verbose):
152
+ # print(f"\nVerification passed; completed in {format_time(time.time() - t1)}")
153
+ return True
154
+
155
+
156
+ def unpack (filename , destination , force_extract ):
82
157
dirname = ""
83
- print ("Extracting {0} ..." .format (os .path .basename (filename )))
84
- sys .stdout .flush ()
158
+ cfile = None # Compressed file
159
+ file_is_corrupted = False
160
+ if not force_extract :
161
+ print (" > Verify archive... " , end = "" , flush = True )
162
+
163
+ try :
164
+ if filename .endswith ("tar.gz" ):
165
+ if tarfile .is_tarfile (filename ):
166
+ cfile = tarfile .open (filename , "r:gz" )
167
+ dirname = cfile .getnames ()[0 ]
168
+ else :
169
+ print ("File corrupted!" )
170
+ file_is_corrupted = True
171
+ elif filename .endswith ("tar.xz" ):
172
+ if tarfile .is_tarfile (filename ):
173
+ cfile = tarfile .open (filename , "r:xz" )
174
+ dirname = cfile .getnames ()[0 ]
175
+ else :
176
+ print ("File corrupted!" )
177
+ file_is_corrupted = True
178
+ elif filename .endswith ("zip" ):
179
+ if zipfile .is_zipfile (filename ):
180
+ cfile = zipfile .ZipFile (filename )
181
+ dirname = cfile .namelist ()[0 ]
182
+ else :
183
+ print ("File corrupted!" )
184
+ file_is_corrupted = True
185
+ else :
186
+ raise NotImplementedError ("Unsupported archive type" )
187
+ except EOFError :
188
+ print ("File corrupted or incomplete!" )
189
+ cfile = None
190
+ file_is_corrupted = True
191
+
192
+ if file_is_corrupted :
193
+ corrupted_filename = filename + ".corrupted"
194
+ os .rename (filename , corrupted_filename )
195
+ if verbose :
196
+ print (f"Renaming corrupted archive to { corrupted_filename } " )
197
+ return False
198
+
199
+ # A little trick to rename tool directories so they don't contain version number
200
+ rename_to = re .match (r"^([a-z][^\-]*\-*)+" , dirname ).group (0 ).strip ("-" )
201
+ if rename_to == dirname and dirname .startswith ("esp32-arduino-libs-" ):
202
+ rename_to = "esp32-arduino-libs"
203
+
204
+ if not force_extract :
205
+ if verify_files (filename , destination , rename_to ):
206
+ print (" Files ok. Skipping Extraction" )
207
+ return True
208
+ else :
209
+ print (" Extracting archive..." )
210
+ else :
211
+ print (" Forcing extraction" )
212
+
85
213
if filename .endswith ("tar.gz" ):
86
- tfile = tarfile . open ( filename , "r:gz" )
87
- tfile . extractall ( destination )
88
- dirname = tfile . getnames ()[ 0 ]
214
+ if not cfile :
215
+ cfile = tarfile . open ( filename , "r:gz" )
216
+ cfile . extractall ( destination )
89
217
elif filename .endswith ("tar.xz" ):
90
- tfile = tarfile . open ( filename , "r:xz" )
91
- tfile . extractall ( destination )
92
- dirname = tfile . getnames ()[ 0 ]
218
+ if not cfile :
219
+ cfile = tarfile . open ( filename , "r:xz" )
220
+ cfile . extractall ( destination )
93
221
elif filename .endswith ("zip" ):
94
- zfile = zipfile . ZipFile ( filename )
95
- zfile . extractall ( destination )
96
- dirname = zfile . namelist ()[ 0 ]
222
+ if not cfile :
223
+ cfile = zipfile . ZipFile ( filename )
224
+ cfile . extractall ( destination )
97
225
else :
98
226
raise NotImplementedError ("Unsupported archive type" )
99
227
100
- # a little trick to rename tool directories so they don't contain version number
101
- rename_to = re .match (r"^([a-z][^\-]*\-*)+" , dirname ).group (0 ).strip ("-" )
102
- if rename_to == dirname and dirname .startswith ("esp32-arduino-libs-" ):
103
- rename_to = "esp32-arduino-libs"
104
228
if rename_to != dirname :
105
229
print ("Renaming {0} to {1} ..." .format (dirname , rename_to ))
106
230
if os .path .isdir (rename_to ):
107
231
shutil .rmtree (rename_to )
108
232
shutil .move (dirname , rename_to )
109
233
234
+ return True
235
+
110
236
111
- def download_file_with_progress (url , filename ):
237
+ def download_file_with_progress (url , filename , start_time ):
112
238
import ssl
113
239
import contextlib
114
240
@@ -124,16 +250,16 @@ def download_file_with_progress(url, filename):
124
250
with open (filename , "wb" ) as out_file :
125
251
out_file .write (block )
126
252
block_count += 1
127
- report_progress (block_count , block_size , total_size )
253
+ report_progress (block_count , block_size , total_size , start_time )
128
254
while True :
129
255
block = fp .read (block_size )
130
256
if not block :
131
257
break
132
258
out_file .write (block )
133
259
block_count += 1
134
- report_progress (block_count , block_size , total_size )
260
+ report_progress (block_count , block_size , total_size , start_time )
135
261
else :
136
- raise Exception ("nonexisting file or connection error" )
262
+ raise Exception ("Non-existing file or connection error" )
137
263
138
264
139
265
def download_file (url , filename ):
@@ -155,16 +281,21 @@ def download_file(url, filename):
155
281
break
156
282
out_file .write (block )
157
283
else :
158
- raise Exception ("nonexisting file or connection error" )
284
+ raise Exception ("Non-existing file or connection error" )
159
285
160
286
161
- def get_tool (tool ):
287
+ def get_tool (tool , force_download , force_extract ):
162
288
sys_name = platform .system ()
163
289
archive_name = tool ["archiveFileName" ]
290
+ checksum = tool ["checksum" ][8 :]
164
291
local_path = dist_dir + archive_name
165
292
url = tool ["url" ]
166
- if not os .path .isfile (local_path ):
167
- print ("Downloading " + archive_name + " ..." )
293
+ start_time = time .time ()
294
+ if not os .path .isfile (local_path ) or force_download :
295
+ if verbose :
296
+ print ("Downloading '" + archive_name + "' to '" + local_path + "'" )
297
+ else :
298
+ print ("Downloading '" + archive_name + "' ..." )
168
299
sys .stdout .flush ()
169
300
if "CYGWIN_NT" in sys_name :
170
301
import ssl
@@ -186,13 +317,18 @@ def get_tool(tool):
186
317
try :
187
318
urlretrieve (url , local_path , report_progress )
188
319
except : # noqa: E722
189
- download_file_with_progress (url , local_path )
190
- sys .stdout .write ("\r Done \n " )
320
+ download_file_with_progress (url , local_path , start_time )
321
+ sys .stdout .write (" - Done \n " )
191
322
sys .stdout .flush ()
192
323
else :
193
324
print ("Tool {0} already downloaded" .format (archive_name ))
194
325
sys .stdout .flush ()
195
- unpack (local_path , "." )
326
+
327
+ if "esp32-arduino-libs" not in archive_name and sha256sum (local_path ) != checksum :
328
+ print ("Checksum mismatch for {0}" .format (archive_name ))
329
+ return False
330
+
331
+ return unpack (local_path , "." , force_extract )
196
332
197
333
198
334
def load_tools_list (filename , platform ):
@@ -241,9 +377,58 @@ def identify_platform():
241
377
242
378
243
379
if __name__ == "__main__" :
244
- is_test = len (sys .argv ) > 1 and sys .argv [1 ] == "-h"
380
+ parser = argparse .ArgumentParser (description = "Download and extract tools" )
381
+
382
+ parser .add_argument ("-v" , "--verbose" ,
383
+ type = bool ,
384
+ default = False ,
385
+ required = False ,
386
+ help = "Print verbose output" )
387
+
388
+ parser .add_argument ("-d" , "--force_download" ,
389
+ type = bool ,
390
+ default = False ,
391
+ required = False ,
392
+ help = "Force download of tools" )
393
+
394
+ parser .add_argument ("-e" , "--force_extract" ,
395
+ type = bool ,
396
+ default = False ,
397
+ required = False ,
398
+ help = "Force extraction of tools" )
399
+
400
+ parser .add_argument ("-f" , "--force_all" ,
401
+ type = bool ,
402
+ default = False ,
403
+ required = False ,
404
+ help = "Force download and extraction of tools" )
405
+
406
+ parser .add_argument ("-t" , "--test" ,
407
+ type = bool ,
408
+ default = False ,
409
+ required = False ,
410
+ help = argparse .SUPPRESS )
411
+
412
+ args = parser .parse_args ()
413
+
414
+ verbose = args .verbose
415
+ force_download = args .force_download
416
+ force_extract = args .force_extract
417
+ force_all = args .force_all
418
+ is_test = args .test
419
+
420
+ if is_test and (force_download or force_extract or force_all ):
421
+ print ("Cannot combine test (-t) and forced execution (-d | -e | -f)" )
422
+ parser .print_help (sys .stderr )
423
+ sys .exit (1 )
424
+
245
425
if is_test :
246
426
print ("Test run!" )
427
+
428
+ if force_all :
429
+ force_download = True
430
+ force_extract = True
431
+
247
432
identified_platform = identify_platform ()
248
433
print ("Platform: {0}" .format (identified_platform ))
249
434
tools_to_download = load_tools_list (
@@ -254,5 +439,11 @@ def identify_platform():
254
439
if is_test :
255
440
print ("Would install: {0}" .format (tool ["archiveFileName" ]))
256
441
else :
257
- get_tool (tool )
442
+ if not get_tool (tool , force_download , force_extract ):
443
+ if verbose :
444
+ print (f"Tool { tool ['archiveFileName' ]} was corrupted. Re-downloading...\n " )
445
+ if not get_tool (
446
+ tool , True , force_extract
447
+ ): # Corrupted file was renamed, will not be found and will be re-downloaded
448
+ print (f"Tool { tool ['archiveFileName' ]} was corrupted, but re-downloading did not help!\n " )
258
449
print ("Platform Tools Installed" )
0 commit comments