diff options
| -rw-r--r-- | tools/mpremote/README.md | 3 | ||||
| -rw-r--r-- | tools/mpremote/mpremote/commands.py | 21 | ||||
| -rw-r--r-- | tools/mpremote/mpremote/main.py | 11 | ||||
| -rw-r--r-- | tools/mpremote/mpremote/transport.py | 19 |
4 files changed, 46 insertions, 8 deletions
diff --git a/tools/mpremote/README.md b/tools/mpremote/README.md index 3d428f128..2bf784be2 100644 --- a/tools/mpremote/README.md +++ b/tools/mpremote/README.md @@ -20,7 +20,7 @@ The full list of supported commands are: mpremote exec <string> -- execute the string mpremote run <file> -- run the given local script mpremote fs <command> <args...> -- execute filesystem commands on the device - command may be: cat, ls, cp, rm, mkdir, rmdir + command may be: cat, ls, cp, rm, mkdir, rmdir, sha256sum use ":" as a prefix to specify a file on the device mpremote repl -- enter REPL options: @@ -78,6 +78,7 @@ Examples: mpremote cp :main.py . mpremote cp main.py : mpremote cp -r dir/ : + mpremote sha256sum :main.py mpremote mip install aioble mpremote mip install github:org/repo@branch mpremote mip install gitlab:org/repo@branch diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index db2be3b13..d9466864b 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -1,3 +1,4 @@ +import hashlib import os import sys import tempfile @@ -127,7 +128,7 @@ def _remote_path_basename(a): return a.rsplit("/", 1)[-1] -def do_filesystem_cp(state, src, dest, multiple): +def do_filesystem_cp(state, src, dest, multiple, check_hash=False): if dest.startswith(":"): dest_exists = state.transport.fs_exists(dest[1:]) dest_isdir = dest_exists and state.transport.fs_isdir(dest[1:]) @@ -159,6 +160,19 @@ def do_filesystem_cp(state, src, dest, multiple): if dest_isdir: dest = ":" + _remote_path_join(dest[1:], filename) + # Skip copy if the destination file is identical. + if check_hash: + try: + remote_hash = state.transport.fs_hashfile(dest[1:], "sha256") + source_hash = hashlib.sha256(data).digest() + # remote_hash will be None if the device doesn't support + # hashlib.sha256 (and therefore won't match). + if remote_hash == source_hash: + print("Up to date:", dest[1:]) + return + except OSError: + pass + # Write to remote. state.transport.fs_writefile(dest[1:], data, progress_callback=show_progress_bar) else: @@ -274,7 +288,7 @@ def do_filesystem_recursive_cp(state, src, dest, multiple): else: dest_path_joined = os.path.join(dest, *dest_path_split) - do_filesystem_cp(state, src_path_joined, dest_path_joined, multiple=False) + do_filesystem_cp(state, src_path_joined, dest_path_joined, multiple=False, check_hash=True) def do_filesystem(state, args): @@ -333,6 +347,9 @@ def do_filesystem(state, args): state.transport.fs_rmdir(path) elif command == "touch": state.transport.fs_touchfile(path) + elif command.endswith("sum") and command[-4].isdigit(): + digest = state.transport.fs_hashfile(path, command[:-3]) + print(digest.hex()) elif command == "cp": if args.recursive: do_filesystem_recursive_cp(state, path, cp_dest, len(paths) > 1) diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index 427e11c74..c5e73b1cc 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -190,7 +190,9 @@ def argparse_filesystem(): "enable verbose output (defaults to True for all commands except cat)", ) cmd_parser.add_argument( - "command", nargs=1, help="filesystem command (e.g. cat, cp, ls, rm, rmdir, touch)" + "command", + nargs=1, + help="filesystem command (e.g. cat, cp, sha256sum, ls, rm, rmdir, touch)", ) cmd_parser.add_argument("path", nargs="+", help="local and remote paths") return cmd_parser @@ -308,12 +310,13 @@ _BUILTIN_COMMAND_EXPANSIONS = { }, # Filesystem shortcuts (use `cp` instead of `fs cp`). "cat": "fs cat", - "ls": "fs ls", "cp": "fs cp", - "rm": "fs rm", - "touch": "fs touch", + "ls": "fs ls", "mkdir": "fs mkdir", + "rm": "fs rm", "rmdir": "fs rmdir", + "sha256sum": "fs sha256sum", + "touch": "fs touch", # Disk used/free. "df": [ "exec", diff --git a/tools/mpremote/mpremote/transport.py b/tools/mpremote/mpremote/transport.py index 2acc8c3b2..cb4cd102d 100644 --- a/tools/mpremote/mpremote/transport.py +++ b/tools/mpremote/mpremote/transport.py @@ -24,7 +24,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import ast, os, sys +import ast, hashlib, os, sys from collections import namedtuple @@ -174,3 +174,20 @@ class Transport: self.exec("f=open('%s','a')\nf.close()" % path) except TransportError as e: raise _convert_filesystem_error(e, path) from None + + def fs_hashfile(self, path, algo, chunk_size=256): + try: + self.exec("import hashlib\nh = hashlib.{algo}()".format(algo=algo)) + except TransportError: + # hashlib (or hashlib.{algo}) not available on device. Do the hash locally. + data = self.fs_readfile(path, chunk_size=chunk_size) + return getattr(hashlib, algo)(data).digest() + try: + self.exec( + "buf = memoryview(bytearray({chunk_size}))\nwith open('{path}', 'rb') as f:\n while True:\n n = f.readinto(buf)\n if n == 0:\n break\n h.update(buf if n == {chunk_size} else buf[:n])\n".format( + chunk_size=chunk_size, path=path + ) + ) + return self.eval("h.digest()") + except TransportExecError as e: + raise _convert_filesystem_error(e, path) from None |
