Skip to content

Commit e6d5f9a

Browse files
Merge pull request #54 from shirish87/pr-gzip
feat: support for gzip download
2 parents 89f14b5 + e3362ff commit e6d5f9a

File tree

3 files changed

+52
-17
lines changed

3 files changed

+52
-17
lines changed

browserstack/local.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def __init__(self, key=None, binary_path=None, **kwargs):
1515
self.key = os.environ['BROWSERSTACK_ACCESS_KEY'] if 'BROWSERSTACK_ACCESS_KEY' in os.environ else key
1616
self.options = kwargs
1717
self.local_logfile_path = os.path.join(os.getcwd(), 'local.log')
18+
LocalBinary.set_version(self.get_package_version())
1819

1920
def __xstr(self, key, value):
2021
if key is None:

browserstack/local_binary.py

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,34 @@
11
import platform, os, sys, stat, tempfile, re, subprocess
22
from browserstack.bserrors import BrowserStackLocalError
3+
import gzip
34

45
try:
5-
from urllib.request import urlopen
6+
from urllib.request import urlopen, Request
67
except ImportError:
7-
from urllib2 import urlopen
8+
from urllib2 import urlopen, Request
89

910
class 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)

tests/test_local.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_proxy(self):
7070
self.assertIn('-proxyHost', self.local._generate_cmd())
7171
self.assertIn('localhost', self.local._generate_cmd())
7272
self.assertIn('-proxyPort', self.local._generate_cmd())
73-
self.assertIn(2000, self.local._generate_cmd())
73+
self.assertIn('2000', self.local._generate_cmd())
7474
self.assertIn('-proxyUser', self.local._generate_cmd())
7575
self.assertIn('hello', self.local._generate_cmd())
7676
self.assertIn('-proxyPass', self.local._generate_cmd())

0 commit comments

Comments
 (0)