11import platform , os , sys , stat , tempfile , re , subprocess
22from browserstack .bserrors import BrowserStackLocalError
3+ import gzip
34
45try :
5- from urllib .request import urlopen
6+ from urllib .request import urlopen , Request
67except ImportError :
7- from urllib2 import urlopen
8+ from urllib2 import urlopen , Request
89
910class LocalBinary :
11+ _version = None
12+
1013 def __init__ (self ):
1114 is_64bits = sys .maxsize > 2 ** 32
1215 self .is_windows = False
1316 osname = platform .system ()
17+ source_url = "https://www.browserstack.com/local-testing/downloads/binaries/"
18+
1419 if osname == 'Darwin' :
15- self .http_path = "https://www.browserstack.com/local-testing/downloads/binaries/ BrowserStackLocal-darwin-x64"
20+ self .http_path = source_url + " BrowserStackLocal-darwin-x64"
1621 elif osname == 'Linux' :
1722 if self .is_alpine ():
18- self .http_path = "https://www.browserstack.com/local-testing/downloads/binaries/ BrowserStackLocal-alpine"
23+ self .http_path = source_url + " BrowserStackLocal-alpine"
1924 else :
2025 if is_64bits :
21- self .http_path = "https://www.browserstack.com/local-testing/downloads/binaries/ BrowserStackLocal-linux-x64"
26+ self .http_path = source_url + " BrowserStackLocal-linux-x64"
2227 else :
23- self .http_path = "https://www.browserstack.com/local-testing/downloads/binaries/ BrowserStackLocal-linux-ia32"
28+ self .http_path = source_url + " BrowserStackLocal-linux-ia32"
2429 else :
2530 self .is_windows = True
26- self .http_path = "https://www.browserstack.com/local-testing/downloads/binaries/ BrowserStackLocal.exe"
31+ self .http_path = source_url + " BrowserStackLocal.exe"
2732
2833 self .ordered_paths = [
2934 os .path .join (os .path .expanduser ('~' ), '.browserstack' ),
@@ -32,11 +37,13 @@ def __init__(self):
3237 ]
3338 self .path_index = 0
3439
40+ @staticmethod
41+ def set_version (version ):
42+ LocalBinary ._version = version
43+
3544 def is_alpine (self ):
36- grepOutput = subprocess .run ("grep -w NAME /etc/os-release" , capture_output = True , shell = True )
37- if grepOutput .stdout .decode ('utf-8' ).find ('Alpine' ) > - 1 :
38- return True
39- return False
45+ response = subprocess .check_output (["grep" , "-w" , "NAME" , "/etc/os-release" ])
46+ return response .decode ('utf-8' ).find ('Alpine' ) > - 1
4047
4148 def __make_path (self , dest_path ):
4249 try :
@@ -57,33 +64,60 @@ def __available_dir(self):
5764 raise BrowserStackLocalError ('Error trying to download BrowserStack Local binary' )
5865
5966 def download (self , chunk_size = 8192 , progress_hook = None ):
60- response = urlopen (self .http_path )
67+ headers = {
68+ 'User-Agent' : '/' .join (('browserstack-local-python' , LocalBinary ._version )),
69+ 'Accept-Encoding' : 'gzip, *' ,
70+ }
71+
72+ if sys .version_info < (3 , 2 ):
73+ # lack of support for gzip decoding for stream, response is expected to have a tell() method
74+ headers .pop ('Accept-Encoding' , None )
75+
76+ response = urlopen (Request (self .http_path , headers = headers ))
6177 try :
62- total_size = int (response .info ().getheader ('Content-Length' ).strip ())
78+ total_size = int (response .info ().get ('Content-Length' , '' ).strip () or '0' )
6379 except :
64- total_size = int (response .info ().get_all ('Content-Length' )[0 ].strip ())
80+ total_size = int (response .info ().get_all ('Content-Length' )[0 ].strip () or '0' )
6581 bytes_so_far = 0
6682
6783 dest_parent_dir = self .__available_dir ()
6884 dest_binary_name = 'BrowserStackLocal'
6985 if self .is_windows :
7086 dest_binary_name += '.exe'
7187
88+ content_encoding = response .info ().get ('Content-Encoding' , '' )
89+ gzip_file = gzip .GzipFile (fileobj = response , mode = 'rb' ) if content_encoding .lower () == 'gzip' else None
90+
91+ if os .getenv ('BROWSERSTACK_LOCAL_DEBUG_GZIP' ) and gzip_file :
92+ print ('using gzip in ' + headers ['User-Agent' ])
93+
94+ def read_chunk (chunk_size ):
95+ if gzip_file :
96+ return gzip_file .read (chunk_size )
97+ else :
98+ return response .read (chunk_size )
99+
72100 with open (os .path .join (dest_parent_dir , dest_binary_name ), 'wb' ) as local_file :
73101 while True :
74- chunk = response . read (chunk_size )
102+ chunk = read_chunk (chunk_size )
75103 bytes_so_far += len (chunk )
76104
77105 if not chunk :
78106 break
79107
80- if progress_hook :
108+ if total_size > 0 and progress_hook :
81109 progress_hook (bytes_so_far , chunk_size , total_size )
82110
83111 try :
84112 local_file .write (chunk )
85113 except :
86114 return self .download (chunk_size , progress_hook )
115+
116+ if gzip_file :
117+ gzip_file .close ()
118+
119+ if callable (getattr (response , 'close' , None )):
120+ response .close ()
87121
88122 final_path = os .path .join (dest_parent_dir , dest_binary_name )
89123 st = os .stat (final_path )
0 commit comments