1
1
import platform , os , sys , stat , tempfile , re , subprocess
2
2
from browserstack .bserrors import BrowserStackLocalError
3
+ import gzip
3
4
4
5
try :
5
- from urllib .request import urlopen
6
+ from urllib .request import urlopen , Request
6
7
except ImportError :
7
- from urllib2 import urlopen
8
+ from urllib2 import urlopen , Request
8
9
9
10
class LocalBinary :
11
+ _version = None
12
+
10
13
def __init__ (self ):
11
14
is_64bits = sys .maxsize > 2 ** 32
12
15
self .is_windows = False
13
16
osname = platform .system ()
17
+ source_url = "https://www.browserstack.com/local-testing/downloads/binaries/"
18
+
14
19
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"
16
21
elif osname == 'Linux' :
17
22
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"
19
24
else :
20
25
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"
22
27
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"
24
29
else :
25
30
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"
27
32
28
33
self .ordered_paths = [
29
34
os .path .join (os .path .expanduser ('~' ), '.browserstack' ),
@@ -32,11 +37,13 @@ def __init__(self):
32
37
]
33
38
self .path_index = 0
34
39
40
+ @staticmethod
41
+ def set_version (version ):
42
+ LocalBinary ._version = version
43
+
35
44
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
40
47
41
48
def __make_path (self , dest_path ):
42
49
try :
@@ -57,33 +64,60 @@ def __available_dir(self):
57
64
raise BrowserStackLocalError ('Error trying to download BrowserStack Local binary' )
58
65
59
66
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 ))
61
77
try :
62
- total_size = int (response .info ().getheader ('Content-Length' ).strip ())
78
+ total_size = int (response .info ().get ('Content-Length' , '' ).strip () or '0' )
63
79
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' )
65
81
bytes_so_far = 0
66
82
67
83
dest_parent_dir = self .__available_dir ()
68
84
dest_binary_name = 'BrowserStackLocal'
69
85
if self .is_windows :
70
86
dest_binary_name += '.exe'
71
87
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
+
72
100
with open (os .path .join (dest_parent_dir , dest_binary_name ), 'wb' ) as local_file :
73
101
while True :
74
- chunk = response . read (chunk_size )
102
+ chunk = read_chunk (chunk_size )
75
103
bytes_so_far += len (chunk )
76
104
77
105
if not chunk :
78
106
break
79
107
80
- if progress_hook :
108
+ if total_size > 0 and progress_hook :
81
109
progress_hook (bytes_so_far , chunk_size , total_size )
82
110
83
111
try :
84
112
local_file .write (chunk )
85
113
except :
86
114
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 ()
87
121
88
122
final_path = os .path .join (dest_parent_dir , dest_binary_name )
89
123
st = os .stat (final_path )
0 commit comments