def version_wrapper() -> str: from util import spawn_process git_tag = spawn_process('git tag --points-at HEAD')[1] worktree_flag = spawn_process('git diff-index --quiet HEAD')[0] worktree_status = '-dirty' if worktree_flag != 0 else '' if len(git_tag) > 0: return git_tag + worktree_status else: git_branch = spawn_process('git rev-parse --abbrev-ref HEAD')[1] git_hash = spawn_process('git rev-parse --short HEAD')[1] git_time = spawn_process('git show -s --format=%cI HEAD')[1] return f'{git_branch}-{git_hash}-{git_time}{worktree_status}'
def _create_remote_dir(self, path: str): args = ['adb', 'shell', 'mkdir', "'%s'" % path.replace("'", "'\"'\"'")] stdout, stderr = spawn_process(args, 'utf8') if stderr.rstrip('\r\n').endswith('No such file or directory'): # recursive mode if path == '/': raise RuntimeError(stderr) self._create_remote_dir(self._abs_path(path + '/..')) # retry after parent dir created stdout, stderr = spawn_process(args, 'utf8') if len(stderr) > 0: raise RuntimeError(stderr)
def _remove_remote(path: str): stdout, stderr = spawn_process([ 'adb', 'shell', 'rm', '-rf', "'%s'" % path.replace("'", "'\"'\"'") ], 'utf8') if len(stderr) > 0: raise RuntimeError(stderr)
def _adb_stat(self, path: str, retry_count: int = 5) -> FileMeta: if retry_count == 0: raise RuntimeError( 'Adb repeatedly returned empty stat result for path %s' % path) # escape char (') in linux shell path_escaped = path.replace("'", "'\"'\"'") stdout, stderr = spawn_process([ 'adb', 'shell', 'stat', '-L', '-c', "'%A/%s/%X/%Y/%W/%n'", "'%s'" % path_escaped ], 'utf8') if len(stderr) > 0: raise RuntimeError(stderr) if len(stdout) == 0: # unknown reason for stat returns nothing return self._adb_stat(path, retry_count - 1) parts = stdout.rstrip('\r\n').split('/') def _cvt_ts(x): return 0 if x == '?' else int(x) # debug try: return FileMeta( path_id=0, file_name=parts[-1], file_size=int(parts[1]), access_time=datetime.datetime.fromtimestamp(_cvt_ts(parts[2])), mod_time=datetime.datetime.fromtimestamp(_cvt_ts(parts[3])), create_time=datetime.datetime.fromtimestamp(_cvt_ts(parts[4])), is_dir=int(parts[0][0] == 'd')) except IndexError: warn('Invalid scheme: "%s" for path "%s"' % (stdout, path)) raise
def send_click(self, x: float, y: float, stay_time: float = 0.1): px, py = self._translate_normalized_coord(x, y) stdout = _handle_adb_ipc_output( spawn_process([ self._adb, 'shell', 'input touchscreen swipe %d %d %d %d %d' % (px, py, px, py, int(round(stay_time * 1000))) ])) if len(stdout) > 0: logger.debug('Adb output: %s' % stdout)
def __init__(self, path: str, thread_count: int = 4, max_history_backup: int = 30): if not os.path.exists(path): os.makedirs(path, exist_ok=True) assert os.path.isdir(path), 'path must be a directory' assert thread_count > 0, 'thread_count must be positive' assert max_history_backup > 0, 'max_history_backup must be positive' self._path = path self._sql_file = os.path.join(self._path, 'entries.db') if not os.path.isfile(self._sql_file): open(self._sql_file, 'wb').close() self._sql_conn = SqliteAccessor(self._sql_file) # creating repository directory for i in range(256): os.makedirs(os.path.join(self._path, 'objects', '%02x' % i), exist_ok=True) spawn_process('adb start-server', 'utf8') self._thread_count = thread_count self._max_history_backup = max_history_backup
def _push_file(self, path: str, meta: FileMeta): local_path = os.path.join(self._path, 'objects', '%02x' % meta.sha256[0], meta.sha256.hex()) if os.path.exists(local_path): os.utime(local_path, (get_datetime_timestamp( meta.access_time), get_datetime_timestamp(meta.mod_time))) stdout, stderr = spawn_process(['adb', 'push', local_path, path], 'utf8') if len(stderr) > 0: raise RuntimeError(stderr) else: warn("Could not push file %s: object %s not found" % (path, local_path))
def __init__(self, adb_executable: Optional[str] = None, crop_16_9: bool = True): self._adb = None if adb_executable is not None: assert os.path.isfile( adb_executable ), 'Adb (Android Debug Bridge) executable not exists' self._adb = adb_executable else: candidate_paths = list(sys.path) candidate_paths.extend(os.getenv('PATH').split(os.pathsep)) for path in candidate_paths: candidate_file = os.path.join(path, 'adb.exe') if os.path.isfile(candidate_file): self._adb = candidate_file break if self._adb is None: raise RuntimeError( 'Could not find adb.exe in PATH, please specify it by parameter' ) logger.info('Found adb.exe in %s' % self._adb) # spawn_process([self._adb, 'kill-server']) spawn_process([self._adb, 'start-server']) thd = threading.Thread(target=self._shutdown_adb_server, daemon=False, name='Adb server shutdown thread') thd.start() self._crop_16_9 = False self._device_screen_size = self._get_screenshot_internal().shape logger.debug('Device resolution: %s' % str(self._device_screen_size[1::-1])) self._crop_16_9 = crop_16_9 w = self._device_screen_size[0] / 9.0 * 16.0 beg_x = int(round(self._device_screen_size[1] - w) / 2) self._16_9_screen_slice_x = slice(beg_x, beg_x + int(round(w)))
def _pull_file(self, path: str, meta: FileMeta): local_path = os.path.join( self._path, 'tmp_adb_pull_file_%d' % threading.get_ident()) try: open(local_path, 'wb').close() stdout, stderr = spawn_process(['adb', 'pull', path, local_path], 'utf8') if len(stderr) > 0: raise RuntimeError(stderr) if stdout.startswith("adb: error:"): raise RuntimeError(stdout) with open(local_path, 'rb') as f: md5_hash = hashlib.md5() sha256_hash = hashlib.sha256() while True: b = f.read(4096) if len(b) == 0: break md5_hash.update(b) sha256_hash.update(b) meta.md5 = md5_hash.digest() meta.sha256 = sha256_hash.digest() dest_path = os.path.join(self._path, 'objects', '%02x' % meta.sha256[0], meta.sha256.hex()) if not os.path.exists(dest_path): shutil.move(local_path, dest_path) else: st_dest = os.stat(dest_path) st_src = os.stat(local_path) if st_dest.st_size == st_src.st_size: os.remove(local_path) else: raise RuntimeError( 'Hash conflict for object %s (path: %s)' % (meta.sha256.hex(), path)) db_meta = self._sql_conn.select(FileMeta, 1, path_id=meta.path_id, file_name=meta.file_name) if db_meta is None: self._sql_conn.insert(meta) elif meta != db_meta: self._sql_conn.update(meta) except FileNotFoundError: warn('Could not pull file: %s' % path)
def send_slide(self, p_from: Tuple[float, float], p_to: Tuple[float, float], stay_time_before_move: float = 0.1, stay_time_move: float = 0.8, stay_time_after_move: float = 0.1): p1 = self._translate_normalized_coord(*p_from) p2 = self._translate_normalized_coord(*p_to) if not self.__warn_func_disabled: self.__warn_func_disabled = True logger.warning( 'Param stay_time_before_move and stay_time_after_move is disabled for Adb attacher' ) stdout = _handle_adb_ipc_output( spawn_process([ 'adb', 'shell', 'input touchscreen swipe %d %d %d %d %d' % (p1[0], p1[1], p2[0], p2[1], int(round(stay_time_move * 1000))) ])) if len(stdout) > 0: logger.debug('Adb output: %s' % stdout)
def _adb_ls(path: str, retry_count: int = 5) -> Tuple[List[str], List[str]]: if retry_count == 0: raise RuntimeError( 'Adb repeatedly returned empty ls result for path %s' % path) if not path.endswith('/'): path = path + '/' # escape char (') in linux shell path = path.replace("'", "'\"'\"'") stdout, stderr = spawn_process( ['adb', 'shell', 'ls', '-al', "'%s'" % path], 'utf8') if len(stderr) > 0: raise RuntimeError(stderr) if len(stdout) == 0: return BackupManager._adb_ls(path, retry_count - 1) dirs = [] files = [] for line in stdout.split('\n'): if len(line) == 0: continue match = re.match(ls_al_pattern, line) if match is None: continue # '\ ' will be used in newest android OS filename = match.group('name').replace('\\', '') permission = match.group('permission') if filename == '.' or filename == '..': continue if permission[0] == 'd': dirs.append(filename) elif permission[0] == '-': files.append(filename) else: print('Unsupported file permission attribute:', permission) return dirs, files