You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

143 lines
5.1 KiB

  1. import os
  2. import posixpath
  3. import shutil
  4. from send2trash import send2trash
  5. from pyadb.internal import config
  6. from pyadb.internal.directory import Directory, get_unmodified
  7. from pyadb.internal.cli_wrap import AdbWrapper
  8. def merge(src, dst, log=False):
  9. if not os.path.exists(dst):
  10. return False
  11. ok = True
  12. for path, dirs, files in os.walk(src):
  13. relPath = os.path.relpath(path, src)
  14. destPath = os.path.join(dst, relPath)
  15. if not os.path.exists(destPath):
  16. os.makedirs(destPath)
  17. for file in files:
  18. destFile = os.path.join(destPath, file)
  19. if os.path.isfile(destFile):
  20. if log:
  21. print("Skipping existing file: " + os.path.join(relPath, file))
  22. ok = False
  23. continue
  24. srcFile = os.path.join(path, file)
  25. shutil.move(srcFile, destFile)
  26. for path, dirs, files in os.walk(src, False):
  27. if len(files) == 0 and len(dirs) == 0:
  28. os.rmdir(path)
  29. return ok
  30. class FileSystem(AdbWrapper):
  31. STAT_TYPE = 1
  32. def stat(self, file):
  33. """Returns information on file"""
  34. # %a Access bits (octal) |%A Access bits (flags)|%b Size/512
  35. # %B Bytes per %b (512) |%d Device ID (dec) |%D Device ID (hex)
  36. # %f All mode bits (hex) |%F File type |%g Group ID
  37. # %G Group name |%h Hard links |%i Inode
  38. # %m Mount point |%n Filename |%N Long filename
  39. # %o I/O block size |%s Size (bytes) |%t Devtype major
  40. # %T Devtype minor (hex) |%u User ID |%U User name
  41. # %x Access time |%X Access unix time |%y File write time
  42. # %Y File write unix time|%z Dir change time |%Z Dir change time
  43. # The valid format escape sequences for filesystems:
  44. # %a Available blocks |%b Total blocks |%c Total inodes
  45. # %d Free inodes |%f Free blocks |%i File system ID
  46. # %l Max filename length |%n File name |%s Fragment size
  47. # %S Best transfer size |%t FS type (hex) |%T FS type
  48. command = 'stat -c "%A;%F;%U;%G" {};echo $?'.format(file)
  49. stdout, stderr = self.sudo(command, True)
  50. output, res = stdout.read().decode().rstrip().split(config.LINEFEED)
  51. if res == "0":
  52. return output.split(";")
  53. def exists(self, file):
  54. return self.stat(file) is not None
  55. def isfile(self, file):
  56. return self.stat(file)[FileSystem.STAT_TYPE] == "file"
  57. def isdir(self, file):
  58. return self.stat(file)[FileSystem.STAT_TYPE] == "directory"
  59. def islink(self, file):
  60. return self.stat(file)[FileSystem.STAT_TYPE] == "symbolic link"
  61. def delete(self, path):
  62. return self.sudo(["rm", "-rf", path], output="out")
  63. def pull(self, remote, local, override=False):
  64. parent = os.path.dirname(local)
  65. if not os.path.exists(parent):
  66. os.makedirs(parent)
  67. if os.path.exists(local):
  68. if override:
  69. if os.path.isdir(local):
  70. shutil.rmtree(local)
  71. else:
  72. os.remove(local)
  73. else:
  74. raise FileExistsError
  75. self.execute(["pull", remote, local])
  76. def push(self, local, remote):
  77. self.execute(["push", remote, local])
  78. def merge_local_with_remote(self, remote, local, dry=True, delete=False):
  79. """Merge local directory with directory on phone, optionally deleting""" # noqa
  80. stdout, stderr = self.execute(
  81. ["cd", remote, ";", "find", ".", "-type", "f"], output_streams=True
  82. )
  83. listing = stdout.read().decode().rstrip().split(config.LINEFEED)
  84. remote_dirtree = Directory.from_dir_listing(listing, is_listening=True)
  85. prev_working = os.getcwd()
  86. os.chdir(local)
  87. for root, dirs, files in os.walk("."):
  88. new_root = root.split(os.sep)
  89. parent = remote_dirtree.traverse(new_root, create_intermediate=False)
  90. for file in files:
  91. fp = os.path.join(root, file)
  92. try:
  93. parent.remove_child(file)
  94. except FileNotFoundError:
  95. if delete:
  96. if dry:
  97. print("Removing:", fp)
  98. else:
  99. send2trash(fp)
  100. os.chdir(prev_working)
  101. for file in get_unmodified(remote_dirtree):
  102. if config.IS_WINDOWS:
  103. local_path = os.path.join(local, file.replace("/", os.sep))
  104. else:
  105. local_path = os.path.join(local, file)
  106. remote_path = posixpath.join(remote, file)
  107. if dry:
  108. print(remote_path, local_path, sep="->")
  109. else:
  110. self.pull(remote_path, local_path)
  111. class NormalDevice(FileSystem):
  112. """Device model that represents when the device is booted in an os
  113. """
  114. pass
  115. class RecoveryMode(FileSystem):
  116. """Device model that represents wehn the device is in recovery mode
  117. """