33"""Script to download and extract tools
44
55This script will download and extract required tools into the current directory.
6- Tools list is obtained from package/package_esp8266com_index .template.json file.
6+ Tools list is obtained from package/package_esp32_index .template.json file.
77"""
88
99from __future__ import print_function
2222import tarfile
2323import zipfile
2424import re
25+ import time
26+ import argparse
27+
28+ # Initialize start_time globally
29+ start_time = - 1
2530
2631if sys .version_info [0 ] == 3 :
2732 from urllib .request import urlretrieve
4247elif __file__ :
4348 current_dir = os .path .dirname (os .path .realpath (unicode (__file__ )))
4449
45- # current_dir = os.path.dirname(os.path.realpath(unicode(__file__)))
4650dist_dir = current_dir + "/dist/"
4751
4852
@@ -62,53 +66,175 @@ def mkdir_p(path):
6266 raise
6367
6468
65- def report_progress (count , blockSize , totalSize ):
69+ def format_time (seconds ):
70+ minutes , seconds = divmod (seconds , 60 )
71+ return "{:02}:{:05.2f}" .format (int (minutes ), seconds )
72+
73+
74+ def report_progress (block_count , block_size , total_size , start_time ):
75+ downloaded_size = block_count * block_size
76+ time_elapsed = time .time () - start_time
77+ current_speed = downloaded_size / (time_elapsed )
78+
6679 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 )
80+ if total_size > 0 :
81+ percent_complete = min ((downloaded_size / total_size ) * 100 , 100 )
82+ sys .stdout .write (
83+ 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
84+ )
7185 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 ))
86+ sys .stdout .write (
87+ 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
88+ )
7889 sys .stdout .flush ()
7990
8091
81- def unpack (filename , destination ):
92+ def print_verification_progress (total_files , i , t1 ):
93+ if sys .stdout .isatty ():
94+ sys .stdout .write (f"\r Elapsed time { format_time (time .time () - t1 )} " )
95+ sys .stdout .flush ()
96+
97+
98+ def verify_files (filename , destination , rename_to ):
99+ # Set the path of the extracted directory
100+ extracted_dir_path = destination
101+ t1 = time .time ()
102+ if filename .endswith (".zip" ):
103+ try :
104+ with zipfile .ZipFile (filename , "r" ) as archive :
105+ first_dir = archive .namelist ()[0 ].split ("/" )[0 ]
106+ total_files = len (archive .namelist ())
107+ for i , zipped_file in enumerate (archive .namelist (), 1 ):
108+ local_path = os .path .join (extracted_dir_path , zipped_file .replace (first_dir , rename_to , 1 ))
109+ if not os .path .exists (local_path ):
110+ print (f"\n Missing { zipped_file } on location: { extracted_dir_path } " )
111+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
112+ return False
113+ print_verification_progress (total_files , i , t1 )
114+ except zipfile .BadZipFile :
115+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
116+ return False
117+ elif filename .endswith (".tar.gz" ):
118+ try :
119+ with tarfile .open (filename , "r:gz" ) as archive :
120+ first_dir = archive .getnames ()[0 ].split ("/" )[0 ]
121+ total_files = len (archive .getnames ())
122+ for i , zipped_file in enumerate (archive .getnames (), 1 ):
123+ local_path = os .path .join (extracted_dir_path , zipped_file .replace (first_dir , rename_to , 1 ))
124+ if not os .path .exists (local_path ):
125+ print (f"\n Missing { zipped_file } on location: { extracted_dir_path } " )
126+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
127+ return False
128+ print_verification_progress (total_files , i , t1 )
129+ except tarfile .ReadError :
130+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
131+ return False
132+ elif filename .endswith (".tar.xz" ):
133+ try :
134+ with tarfile .open (filename , "r:xz" ) as archive :
135+ first_dir = archive .getnames ()[0 ].split ("/" )[0 ]
136+ total_files = len (archive .getnames ())
137+ for i , zipped_file in enumerate (archive .getnames (), 1 ):
138+ local_path = os .path .join (extracted_dir_path , zipped_file .replace (first_dir , rename_to , 1 ))
139+ if not os .path .exists (local_path ):
140+ print (f"\n Missing { zipped_file } on location: { extracted_dir_path } " )
141+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
142+ return False
143+ print_verification_progress (total_files , i , t1 )
144+ except tarfile .ReadError :
145+ print (f"Verification failed; aborted in { format_time (time .time () - t1 )} " )
146+ return False
147+ else :
148+ raise NotImplementedError ("Unsupported archive type" )
149+
150+ if verbose :
151+ print (f"\n Verification passed; completed in { format_time (time .time () - t1 )} " )
152+
153+ return True
154+
155+
156+ def unpack (filename , destination , force_extract ): # noqa: C901
82157 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 ].split ("/" )[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 ].split ("/" )[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 ].split ("/" )[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+
85213 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 )
89217 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 )
93221 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 )
97225 else :
98226 raise NotImplementedError ("Unsupported archive type" )
99227
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"
104228 if rename_to != dirname :
105229 print ("Renaming {0} to {1} ..." .format (dirname , rename_to ))
106230 if os .path .isdir (rename_to ):
107231 shutil .rmtree (rename_to )
108232 shutil .move (dirname , rename_to )
109233
234+ return True
235+
110236
111- def download_file_with_progress (url , filename ):
237+ def download_file_with_progress (url , filename , start_time ):
112238 import ssl
113239 import contextlib
114240
@@ -124,16 +250,16 @@ def download_file_with_progress(url, filename):
124250 with open (filename , "wb" ) as out_file :
125251 out_file .write (block )
126252 block_count += 1
127- report_progress (block_count , block_size , total_size )
253+ report_progress (block_count , block_size , total_size , start_time )
128254 while True :
129255 block = fp .read (block_size )
130256 if not block :
131257 break
132258 out_file .write (block )
133259 block_count += 1
134- report_progress (block_count , block_size , total_size )
260+ report_progress (block_count , block_size , total_size , start_time )
135261 else :
136- raise Exception ("nonexisting file or connection error" )
262+ raise Exception ("Non-existing file or connection error" )
137263
138264
139265def download_file (url , filename ):
@@ -155,16 +281,21 @@ def download_file(url, filename):
155281 break
156282 out_file .write (block )
157283 else :
158- raise Exception ("nonexisting file or connection error" )
284+ raise Exception ("Non-existing file or connection error" )
159285
160286
161- def get_tool (tool ):
287+ def get_tool (tool , force_download , force_extract ):
162288 sys_name = platform .system ()
163289 archive_name = tool ["archiveFileName" ]
290+ checksum = tool ["checksum" ][8 :]
164291 local_path = dist_dir + archive_name
165292 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 + "' ..." )
168299 sys .stdout .flush ()
169300 if "CYGWIN_NT" in sys_name :
170301 import ssl
@@ -186,13 +317,18 @@ def get_tool(tool):
186317 try :
187318 urlretrieve (url , local_path , report_progress )
188319 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 " )
191322 sys .stdout .flush ()
192323 else :
193324 print ("Tool {0} already downloaded" .format (archive_name ))
194325 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 )
196332
197333
198334def load_tools_list (filename , platform ):
@@ -241,9 +377,44 @@ def identify_platform():
241377
242378
243379if __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" , type = bool , default = False , required = False , help = "Print verbose output" )
383+
384+ parser .add_argument (
385+ "-d" , "--force_download" , type = bool , default = False , required = False , help = "Force download of tools"
386+ )
387+
388+ parser .add_argument (
389+ "-e" , "--force_extract" , type = bool , default = False , required = False , help = "Force extraction of tools"
390+ )
391+
392+ parser .add_argument (
393+ "-f" , "--force_all" , type = bool , default = False , required = False , help = "Force download and extraction of tools"
394+ )
395+
396+ parser .add_argument ("-t" , "--test" , type = bool , default = False , required = False , help = argparse .SUPPRESS )
397+
398+ args = parser .parse_args ()
399+
400+ verbose = args .verbose
401+ force_download = args .force_download
402+ force_extract = args .force_extract
403+ force_all = args .force_all
404+ is_test = args .test
405+
406+ if is_test and (force_download or force_extract or force_all ):
407+ print ("Cannot combine test (-t) and forced execution (-d | -e | -f)" )
408+ parser .print_help (sys .stderr )
409+ sys .exit (1 )
410+
245411 if is_test :
246412 print ("Test run!" )
413+
414+ if force_all :
415+ force_download = True
416+ force_extract = True
417+
247418 identified_platform = identify_platform ()
248419 print ("Platform: {0}" .format (identified_platform ))
249420 tools_to_download = load_tools_list (
@@ -254,5 +425,11 @@ def identify_platform():
254425 if is_test :
255426 print ("Would install: {0}" .format (tool ["archiveFileName" ]))
256427 else :
257- get_tool (tool )
428+ if not get_tool (tool , force_download , force_extract ):
429+ if verbose :
430+ print (f"Tool { tool ['archiveFileName' ]} was corrupted. Re-downloading...\n " )
431+ if not get_tool (tool , True , force_extract ):
432+ print (f"Tool { tool ['archiveFileName' ]} was corrupted, but re-downloading did not help!\n " )
433+ sys .exit (1 )
434+
258435 print ("Platform Tools Installed" )
0 commit comments