Initial commit
This commit is contained in:
140
BetterADBSync/FileSystems/Base.py
Executable file
140
BetterADBSync/FileSystems/Base.py
Executable file
@@ -0,0 +1,140 @@
|
||||
from __future__ import annotations
|
||||
from typing import Iterable, List, Tuple, Union
|
||||
import logging
|
||||
import os
|
||||
import stat
|
||||
|
||||
from ..SAOLogging import perror
|
||||
|
||||
class FileSystem():
|
||||
def __init__(self, adb_arguments: List[str]) -> None:
|
||||
self.adb_arguments = adb_arguments
|
||||
|
||||
def _get_files_tree(self, tree_path: str, tree_path_stat: os.stat_result, follow_links: bool = False):
|
||||
# the reason to have two functions instead of one purely recursive one is to use self.lstat_in_dir ie ls
|
||||
# which is much faster than individually stat-ing each file. Hence we have get_files_tree's special first lstat
|
||||
if stat.S_ISLNK(tree_path_stat.st_mode):
|
||||
if not follow_links:
|
||||
logging.warning(f"Ignoring symlink {tree_path}")
|
||||
return None
|
||||
logging.debug(f"Following symlink {tree_path}")
|
||||
try:
|
||||
tree_path_realpath = self.realpath(tree_path)
|
||||
tree_path_stat_realpath = self.lstat(tree_path_realpath)
|
||||
except (FileNotFoundError, NotADirectoryError, PermissionError) as e:
|
||||
perror(f"Skipping symlink {tree_path}", e)
|
||||
return None
|
||||
return self._get_files_tree(tree_path_realpath, tree_path_stat_realpath, follow_links = follow_links)
|
||||
elif stat.S_ISDIR(tree_path_stat.st_mode):
|
||||
tree = {".": (60 * (int(tree_path_stat.st_atime) // 60), 60 * (int(tree_path_stat.st_mtime) // 60))}
|
||||
for filename, stat_object_child, in self.lstat_in_dir(tree_path):
|
||||
if filename in [".", ".."]:
|
||||
continue
|
||||
tree[filename] = self._get_files_tree(
|
||||
self.join(tree_path, filename),
|
||||
stat_object_child,
|
||||
follow_links = follow_links)
|
||||
return tree
|
||||
elif stat.S_ISREG(tree_path_stat.st_mode):
|
||||
return (60 * (int(tree_path_stat.st_atime) // 60), 60 * (int(tree_path_stat.st_mtime) // 60)) # minute resolution
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_files_tree(self, tree_path: str, follow_links: bool = False):
|
||||
statObject = self.lstat(tree_path)
|
||||
return self._get_files_tree(tree_path, statObject, follow_links = follow_links)
|
||||
|
||||
def remove_tree(self, tree_path: str, tree: Union[Tuple[int, int], dict], dry_run: bool = True) -> None:
|
||||
if isinstance(tree, tuple):
|
||||
logging.info(f"Removing {tree_path}")
|
||||
if not dry_run:
|
||||
self.unlink(tree_path)
|
||||
elif isinstance(tree, dict):
|
||||
remove_folder = tree.pop(".", False)
|
||||
for key, value in tree.items():
|
||||
self.remove_tree(self.normpath(self.join(tree_path, key)), value, dry_run = dry_run)
|
||||
if remove_folder:
|
||||
logging.info(f"Removing folder {tree_path}")
|
||||
if not dry_run:
|
||||
self.rmdir(tree_path)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def push_tree_here(self,
|
||||
tree_path: str,
|
||||
relative_tree_path: str, # for logging paths of files / folders copied relative to the source root / destination root
|
||||
# nicely instead of repeating the root every time; rsync does this nice logging
|
||||
tree: Union[Tuple[int, int], dict],
|
||||
destination_root: str,
|
||||
fs_source: FileSystem,
|
||||
dry_run: bool = True,
|
||||
show_progress: bool = False
|
||||
) -> None:
|
||||
if isinstance(tree, tuple):
|
||||
if dry_run:
|
||||
logging.info(f"{relative_tree_path}")
|
||||
else:
|
||||
if not show_progress:
|
||||
# log this instead of letting adb display output
|
||||
logging.info(f"{relative_tree_path}")
|
||||
self.push_file_here(tree_path, destination_root, show_progress = show_progress)
|
||||
self.utime(destination_root, tree)
|
||||
elif isinstance(tree, dict):
|
||||
try:
|
||||
tree.pop(".") # directory needs making
|
||||
logging.info(f"{relative_tree_path}{self.sep}")
|
||||
if not dry_run:
|
||||
self.makedirs(destination_root)
|
||||
except KeyError:
|
||||
pass
|
||||
for key, value in tree.items():
|
||||
self.push_tree_here(
|
||||
fs_source.normpath(fs_source.join(tree_path, key)),
|
||||
fs_source.join(relative_tree_path, key),
|
||||
value,
|
||||
self.normpath(self.join(destination_root, key)),
|
||||
fs_source,
|
||||
dry_run = dry_run,
|
||||
show_progress = show_progress
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
# Abstract methods below implemented in Local.py and Android.py
|
||||
|
||||
@property
|
||||
def sep(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def unlink(self, path: str) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def rmdir(self, path: str) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def makedirs(self, path: str) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def realpath(self, path: str) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def lstat(self, path: str) -> os.stat_result:
|
||||
raise NotImplementedError
|
||||
|
||||
def lstat_in_dir(self, path: str) -> Iterable[Tuple[str, os.stat_result]]:
|
||||
raise NotImplementedError
|
||||
|
||||
def utime(self, path: str, times: Tuple[int, int]) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def join(self, base: str, leaf: str) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def split(self, path: str) -> Tuple[str, str]:
|
||||
raise NotImplementedError
|
||||
|
||||
def normpath(self, path: str) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def push_file_here(self, source: str, destination: str, show_progress: bool = False) -> None:
|
||||
raise NotImplementedError
|
||||
Reference in New Issue
Block a user