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
+ import urllib .request
7
+
8
+ def urlopen (url , headers = None ):
9
+ return urllib .request .urlopen (urllib .request .Request (url , headers = headers ))
6
10
except ImportError :
7
- from urllib2 import urlopen
11
+ import urllib2
12
+
13
+ def urlopen (url , headers = None ):
14
+ return urllib2 .urlopen (urllib2 .Request (url , headers = headers ))
8
15
9
16
class LocalBinary :
17
+ _version = None
18
+
10
19
def __init__ (self ):
11
20
is_64bits = sys .maxsize > 2 ** 32
12
21
self .is_windows = False
@@ -32,11 +41,13 @@ def __init__(self):
32
41
]
33
42
self .path_index = 0
34
43
44
+ @staticmethod
45
+ def set_version (version ):
46
+ LocalBinary ._version = version
47
+
35
48
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
49
+ response = subprocess .check_output (["grep" , "-w" , "NAME" , "/etc/os-release" ])
50
+ return response .decode ('utf-8' ).find ('Alpine' ) > - 1
40
51
41
52
def __make_path (self , dest_path ):
42
53
try :
@@ -57,33 +68,57 @@ def __available_dir(self):
57
68
raise BrowserStackLocalError ('Error trying to download BrowserStack Local binary' )
58
69
59
70
def download (self , chunk_size = 8192 , progress_hook = None ):
60
- response = urlopen (self .http_path )
71
+ headers = {
72
+ 'User-Agent' : '/' .join (('browserstack-local-python' , LocalBinary ._version )),
73
+ 'Accept-Encoding' : 'gzip, *' ,
74
+ }
75
+
76
+ if sys .version_info < (3 , 2 ):
77
+ # lack of support for gzip decoding for stream, response is expected to have a tell() method
78
+ headers .pop ('Accept-Encoding' , None )
79
+
80
+ response = urlopen (self .http_path , headers = headers )
61
81
try :
62
- total_size = int (response .info ().getheader ('Content-Length' ).strip ())
82
+ total_size = int (response .info ().get ('Content-Length' , '' ).strip () or '0' )
63
83
except :
64
- total_size = int (response .info ().get_all ('Content-Length' )[0 ].strip ())
84
+ total_size = int (response .info ().get_all ('Content-Length' )[0 ].strip () or '0' )
65
85
bytes_so_far = 0
66
86
67
87
dest_parent_dir = self .__available_dir ()
68
88
dest_binary_name = 'BrowserStackLocal'
69
89
if self .is_windows :
70
90
dest_binary_name += '.exe'
71
91
92
+ content_encoding = response .info ().get ('Content-Encoding' , '' )
93
+ gzip_file = gzip .GzipFile (fileobj = response , mode = 'rb' ) if content_encoding .lower () == 'gzip' else None
94
+
95
+ def read_chunk (chunk_size ):
96
+ if gzip_file :
97
+ return gzip_file .read (chunk_size )
98
+ else :
99
+ return response .read (chunk_size )
100
+
72
101
with open (os .path .join (dest_parent_dir , dest_binary_name ), 'wb' ) as local_file :
73
102
while True :
74
- chunk = response . read (chunk_size )
103
+ chunk = read_chunk (chunk_size )
75
104
bytes_so_far += len (chunk )
76
105
77
106
if not chunk :
78
107
break
79
108
80
- if progress_hook :
109
+ if total_size > 0 and progress_hook :
81
110
progress_hook (bytes_so_far , chunk_size , total_size )
82
111
83
112
try :
84
113
local_file .write (chunk )
85
114
except :
86
115
return self .download (chunk_size , progress_hook )
116
+
117
+ if gzip_file :
118
+ gzip_file .close ()
119
+
120
+ if callable (getattr (response , 'close' , None )):
121
+ response .close ()
87
122
88
123
final_path = os .path .join (dest_parent_dir , dest_binary_name )
89
124
st = os .stat (final_path )
0 commit comments