Skip to content
This repository was archived by the owner on Jan 10, 2023. It is now read-only.

Commit bc74501

Browse files
MohammadAGfahhem
authored andcommitted
Added a callback to show file transfer progress (#78)
* Added a callback to show file transfer progress * Avoid notifying of progress again when data was fully written * Wrap progress_callback with try/catch * Separate progress callback code * Make progress generator reusable * Implemented pull progress callback
1 parent 89a202d commit bc74501

2 files changed

Lines changed: 45 additions & 14 deletions

File tree

adb/adb_commands.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def Devices(cls):
109109
def GetState(self):
110110
return self._device_state
111111

112-
def Install(self, apk_path, destination_dir='', replace_existing=True, timeout_ms=None):
112+
def Install(self, apk_path, destination_dir='', timeout_ms=None, replace_existing=True, transfer_progress_callback=None):
113113
"""Install an apk to the device.
114114
115115
Doesn't support verifier file, instead allows destination directory to be
@@ -121,6 +121,7 @@ def Install(self, apk_path, destination_dir='', replace_existing=True, timeout_m
121121
persistent applications.
122122
replace_existing: whether to replace existing application
123123
timeout_ms: Expected timeout for pushing and installing.
124+
transfer_progress_callback: callback method that accepts filename, bytes_written and total_bytes of APK transfer
124125
125126
Returns:
126127
The pm install output.
@@ -129,15 +130,14 @@ def Install(self, apk_path, destination_dir='', replace_existing=True, timeout_m
129130
destination_dir = '/data/local/tmp/'
130131
basename = os.path.basename(apk_path)
131132
destination_path = destination_dir + basename
132-
self.Push(apk_path, destination_path, timeout_ms=timeout_ms)
133-
133+
self.Push(apk_path, destination_path, timeout_ms=timeout_ms, progress_callback=transfer_progress_callback)
134+
134135
cmd = ['pm install']
135136
if replace_existing:
136137
cmd.append('-r')
137138
cmd.append('"%s"' % destination_path)
138-
139139
return self.Shell(' '.join(cmd), timeout_ms=timeout_ms)
140-
140+
141141
def Uninstall(self, package_name, keep_data=False, timeout_ms=None):
142142
"""Removes a package from the device.
143143
@@ -155,7 +155,7 @@ def Uninstall(self, package_name, keep_data=False, timeout_ms=None):
155155
cmd.append('"%s"' % package_name)
156156
return self.Shell(' '.join(cmd), timeout_ms=timeout_ms)
157157

158-
def Push(self, source_file, device_filename, mtime='0', timeout_ms=None):
158+
def Push(self, source_file, device_filename, mtime='0', timeout_ms=None, progress_callback=None):
159159
"""Push a file or directory to the device.
160160
161161
Args:
@@ -164,26 +164,28 @@ def Push(self, source_file, device_filename, mtime='0', timeout_ms=None):
164164
device_filename: Destination on the device to write to.
165165
mtime: Optional, modification time to set on the file.
166166
timeout_ms: Expected timeout for any part of the push.
167+
progress_callback: callback method that accepts filename, bytes_written and total_bytes,
168+
total_bytes will be -1 for file-like objects
167169
"""
168170
should_close = False
169171
if isinstance(source_file, str):
170172
if os.path.isdir(source_file):
171173
self.Shell("mkdir " + device_filename)
172174
for f in os.listdir(source_file):
173-
self.Push(os.path.join(source_file, f), device_filename + '/' + f)
175+
self.Push(os.path.join(source_file, f), device_filename + '/' + f, progress_callback=progress_callback)
174176
return
175177
source_file = open(source_file, "rb")
176178
should_close = True
177179

178180
connection = self.protocol_handler.Open(
179181
self.handle, destination=b'sync:', timeout_ms=timeout_ms)
180182
self.filesync_handler.Push(connection, source_file, device_filename,
181-
mtime=int(mtime))
183+
mtime=int(mtime), progress_callback=progress_callback)
182184
if should_close:
183185
source_file.close()
184186
connection.Close()
185187

186-
def Pull(self, device_filename, dest_file='', timeout_ms=None):
188+
def Pull(self, device_filename, dest_file='', progress_callback=None, timeout_ms=None):
187189
"""Pull a file from the device.
188190
189191
Args:
@@ -201,7 +203,7 @@ def Pull(self, device_filename, dest_file='', timeout_ms=None):
201203
connection = self.protocol_handler.Open(
202204
self.handle, destination=b'sync:',
203205
timeout_ms=timeout_ms)
204-
self.filesync_handler.Pull(connection, device_filename, dest_file)
206+
self.filesync_handler.Pull(connection, device_filename, dest_file, progress_callback)
205207
connection.Close()
206208
if isinstance(dest_file, io.BytesIO):
207209
return dest_file.getvalue()

adb/filesync_protocol.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"""
1919

2020
import collections
21+
import os
2122
import stat
2223
import struct
2324
import time
@@ -76,18 +77,36 @@ def List(cls, connection, path):
7677
return files
7778

7879
@classmethod
79-
def Pull(cls, connection, filename, dest_file):
80+
def Pull(cls, connection, filename, dest_file, progress_callback):
8081
"""Pull a file from the device into the file-like dest_file."""
82+
if progress_callback:
83+
total_bytes = cls.Stat(connection, filename)[1]
84+
progress = cls._HandleProgress(lambda current: progress_callback(filename, current, total_bytes))
85+
next(progress)
86+
8187
cnxn = FileSyncConnection(connection, b'<2I')
8288
cnxn.Send(b'RECV', filename)
8389
for cmd_id, _, data in cnxn.ReadUntil((b'DATA',), b'DONE'):
8490
if cmd_id == b'DONE':
8591
break
8692
dest_file.write(data)
93+
if progress_callback:
94+
progress.send(len(data))
95+
96+
@classmethod
97+
def _HandleProgress(cls, progress_callback):
98+
"""Calls the callback with the current progress and total ."""
99+
current = 0
100+
while True:
101+
current += yield
102+
try:
103+
progress_callback(current)
104+
except Exception: # pylint: disable=broad-except
105+
continue
87106

88107
@classmethod
89108
def Push(cls, connection, datafile, filename,
90-
st_mode=DEFAULT_PUSH_MODE, mtime=0):
109+
st_mode=DEFAULT_PUSH_MODE, mtime=0, progress_callback=None):
91110
"""Push a file-like object to the device.
92111
93112
Args:
@@ -96,6 +115,7 @@ def Push(cls, connection, datafile, filename,
96115
filename: Filename to push to
97116
st_mode: stat mode for filename
98117
mtime: modification time
118+
progress_callback: callback method that accepts filename, bytes_written and total_bytes,
99119
100120
Raises:
101121
PushFailedError: Raised on push failure.
@@ -107,11 +127,20 @@ def Push(cls, connection, datafile, filename,
107127
cnxn = FileSyncConnection(connection, b'<2I')
108128
cnxn.Send(b'SEND', fileinfo)
109129

130+
if progress_callback:
131+
total_bytes = os.fstat(datafile.fileno()).st_size if isinstance(datafile, file) else -1
132+
progress = cls._HandleProgress(lambda current: progress_callback(filename, current, total_bytes))
133+
next(progress)
134+
110135
while True:
111136
data = datafile.read(MAX_PUSH_DATA)
112-
if not data:
137+
if data:
138+
cnxn.Send(b'DATA', data)
139+
140+
if progress_callback:
141+
progress.send(len(data))
142+
else:
113143
break
114-
cnxn.Send(b'DATA', data)
115144

116145
if mtime == 0:
117146
mtime = int(time.time())

0 commit comments

Comments
 (0)