""" Java NIO File standard library APIs implemented in Python. java.nio.file package - Path, Files, FileSystem, FileVisitor, etc. """ from .._core import Stub from .io import IOException from .lang import RuntimeException, Comparable import os as _os import shutil as _shutil import stat as _stat from pathlib import Path as _PathLibPath import tempfile as _tempfile IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) __all__ = [ "Path", "Paths", "Files", "FileSystem", "FileSystems", "FileStore", "FileVisitResult", "FileVisitor", "SimpleFileVisitor", "DirectoryStream", "DirectoryStreamFilter", "PathMatcher", "WatchService", "WatchKey", "WatchEvent", "Watchable", "CopyOption", "StandardCopyOption", "LinkOption", "OpenOption", "StandardOpenOption", "FileAlreadyExistsException", "DirectoryNotEmptyException", "NoSuchFileException", "FileSystemLoopException", "FileSystemNotFoundException", "ProviderMismatchException", "ProviderNotFoundException", "NotDirectoryException", "NotLinkException", "AtomicMoveNotSupportedException", "AccessDeniedException", "AccessMode", ] # ============================================================ # Exception classes # ============================================================ class FileAlreadyExistsException(IOException): pass class DirectoryNotEmptyException(IOException): pass class NoSuchFileException(IOException): def __init__(self, file=None, other=None, reason=None): msg = str(file) if file else "" super().__init__(msg) class FileSystemLoopException(IOException): pass class FileSystemNotFoundException(IOException): pass class ProviderMismatchException(IllegalArgumentException): pass class ProviderNotFoundException(IllegalArgumentException): pass class NotDirectoryException(IOException): pass class NotLinkException(IOException): pass class AtomicMoveNotSupportedException(IOException): pass class AccessDeniedException(IOException): pass # ============================================================ # Enums / Constants # ============================================================ class FileVisitResult: CONTINUE = 0 TERMINATE = 1 SKIP_SUBTREE = 2 SKIP_SIBLINGS = 3 class StandardCopyOption: REPLACE_EXISTING = 0 COPY_ATTRIBUTES = 1 ATOMIC_MOVE = 2 class StandardOpenOption: READ = 0 WRITE = 1 APPEND = 2 TRUNCATE_EXISTING = 3 CREATE = 4 CREATE_NEW = 5 DELETE_ON_CLOSE = 6 SPARSE = 7 SYNC = 8 DSYNC = 9 class LinkOption: NOFOLLOW_LINKS = 0 class CopyOption(Stub): pass class OpenOption(Stub): pass class AccessMode: READ = 0 WRITE = 1 EXECUTE = 2 # ============================================================ # Path # ============================================================ class Path(Comparable): def __init__(self, path_str, *more): super().__init__() if more: path_str = _os.path.join(path_str, *more) self._path = str(path_str) self._pypath = _PathLibPath(self._path) def getFileName(self): return Path(self._pypath.name) def getParent(self): parent = self._pypath.parent if parent == self._pypath: return None return Path(str(parent)) def getRoot(self): root = self._pypath.anchor return Path(root) if root else None def toAbsolutePath(self): return Path(str(self._pypath.absolute())) def toRealPath(self): return Path(str(self._pypath.resolve())) def getName(self, index): parts = self._pypath.parts if self.getRoot(): parts = parts[1:] if index < 0 or index >= len(parts): raise IllegalArgumentException("Invalid index") return Path(parts[index]) def getNameCount(self): parts = self._pypath.parts if self.getRoot(): parts = parts[1:] return len(parts) def subpath(self, beginIndex, endIndex): parts = self._pypath.parts if self.getRoot(): parts = parts[1:] return Path(_os.path.join(*parts[beginIndex:endIndex])) def startsWith(self, other): if isinstance(other, Path): return str(self._pypath).startswith(str(other._pypath)) return str(self._pypath).startswith(str(other)) def endsWith(self, other): if isinstance(other, Path): return str(self._pypath).endswith(str(other._pypath)) return str(self._pypath).endswith(str(other)) def normalize(self): return Path(_os.path.normpath(self._path)) def resolve(self, other): if isinstance(other, Path): other_path = str(other) else: other_path = str(other) if _os.path.isabs(other_path): return Path(other_path) return Path(_os.path.join(self._path, other_path)) def resolveSibling(self, other): parent = self.getParent() if parent: return parent.resolve(other) return Path(other) def relativize(self, other): if isinstance(other, Path): other_str = str(other._pypath) else: other_str = str(other) try: rel = _os.path.relpath(other_str, self._path) return Path(rel) except ValueError: return other def compareTo(self, other): if isinstance(other, Path): return (self._path > other._path) - (self._path < other._path) return -1 def toString(self): return self._path def toFile(self): from ..io import File return File(self._path) def isAbsolute(self): return _os.path.isabs(self._path) def getFileSystem(self): return FileSystems.getDefault() def iterator(self): parts = self._pypath.parts if self.getRoot(): parts = parts[1:] return iter([Path(p) for p in parts]) def register(self, watcher, events, *modifiers): raise AndroidOnlyError("WatchService nio") def __str__(self): return self._path def __eq__(self, other): if isinstance(other, Path): return self._path == other._path return NotImplemented def __hash__(self): return hash(self._path) # ============================================================ # Paths # ============================================================ class Paths(Stub): @staticmethod def get(first, *more): parts = [str(first)] parts.extend(str(m) for m in more) return Path(_os.path.join(*parts)) # ============================================================ # FileSystem # ============================================================ class FileSystem(Stub): def __init__(self): super().__init__() self._separator = _os.sep self._open = True def getPath(self, first, *more): return Paths.get(first, *more) def getSeparator(self): return self._separator def getRootDirectories(self): if _os.name == "nt": import string return [Path(d + ":\\") for d in string.ascii_uppercase if _os.path.exists(d + ":\\")] return [Path("/")] def getUserPrincipalLookupService(self): raise AndroidOnlyError("UserPrincipalLookupService") def newWatchService(self): raise AndroidOnlyError("WatchService") def getFileStores(self): return [FileStore("/")] def supportedFileAttributeViews(self): return ["basic", "owner", "posix"] def close(self): self._open = False def isOpen(self): return self._open def isReadOnly(self): return False class FileSystems(Stub): _default = None @staticmethod def getDefault(): if FileSystems._default is None: FileSystems._default = FileSystem() return FileSystems._default # ============================================================ # FileStore # ============================================================ class FileStore(Stub): def __init__(self, path): super().__init__() self._path = path def name(self): return _os.path.basename(self._path) or "/" def type(self): return "ext4" def isReadOnly(self): return False def getTotalSpace(self): try: st = _os.statvfs(self._path) return st.f_frsize * st.f_blocks except OSError: return 0 def getUsableSpace(self): try: st = _os.statvfs(self._path) return st.f_frsize * st.f_bavail except OSError: return 0 def getUnallocatedSpace(self): try: st = _os.statvfs(self._path) return st.f_frsize * st.f_bfree except OSError: return 0 def toString(self): return f"{self.name()} ({self.type()})" # ============================================================ # FileVisitor / SimpleFileVisitor # ============================================================ class FileVisitor(Stub): def preVisitDirectory(self, dir, attrs): return FileVisitResult.CONTINUE def postVisitDirectory(self, dir, exc): return FileVisitResult.CONTINUE def visitFile(self, file, attrs): return FileVisitResult.CONTINUE def visitFileFailed(self, file, exc): return FileVisitResult.CONTINUE class SimpleFileVisitor(FileVisitor): pass # ============================================================ # DirectoryStream / Filter # ============================================================ class DirectoryStream(Stub): def __init__(self, path, filter=None): super().__init__() self._path = path self._filter = filter def __iter__(self): try: entries = _os.listdir(str(self._path)) for entry in entries: p = Path(self._path, entry) if self._filter is None or self._filter.accept(p): yield p except OSError: raise IOException(f"Failed to list directory: {self._path}") def close(self): pass class DirectoryStreamFilter(Stub): def accept(self, entry): raise NotImplementedError # ============================================================ # PathMatcher # ============================================================ class PathMatcher(Stub): def __init__(self, pattern): super().__init__() self._pattern = pattern def matches(self, path): import fnmatch return fnmatch.fnmatch(str(path), self._pattern) # ============================================================ # WatchService / WatchKey / WatchEvent / Watchable (stubs) # ============================================================ class Watchable(Stub): def register(self, watcher, events, *modifiers): raise AndroidOnlyError("WatchService") class WatchService(Stub): def close(self): pass def poll(self): return None def take(self): raise AndroidOnlyError("WatchService.take") def poll(self, timeout, unit): return None class WatchKey(Stub): def isValid(self): return True def pollEvents(self): return [] def reset(self): return True def cancel(self): pass def watchable(self): return None class WatchEvent(Stub): class Kind(Stub): def name(self): return "" def type(self): return None def kind(self): return None def count(self): return 0 def context(self): return None # ============================================================ # Files # ============================================================ class Files(Stub): @staticmethod def copy(source, target, *options): src = str(source) dst = str(target) replace = any(isinstance(o, StandardCopyOption) and o == StandardCopyOption.REPLACE_EXISTING for o in options) try: if replace and _os.path.exists(dst): _os.remove(dst) if _os.path.isdir(src): _shutil.copytree(src, dst) else: _shutil.copy2(src, dst) except OSError as e: raise IOException(str(e)) @staticmethod def move(source, target, *options): src = str(source) dst = str(target) replace = any(isinstance(o, StandardCopyOption) and o == StandardCopyOption.REPLACE_EXISTING for o in options) try: if replace and _os.path.exists(dst): _os.remove(dst) _shutil.move(src, dst) except OSError as e: raise IOException(str(e)) @staticmethod def delete(path): try: p = str(path) if _os.path.isdir(p): _os.rmdir(p) else: _os.remove(p) except OSError as e: raise NoSuchFileException(str(path)) @staticmethod def deleteIfExists(path): try: Files.delete(path) return True except NoSuchFileException: return False @staticmethod def createDirectories(path): _os.makedirs(str(path), exist_ok=True) return path @staticmethod def createDirectory(path, attrs=None): _os.makedirs(str(path), exist_ok=True) return path @staticmethod def createFile(path, attrs=None): p = str(path) if _os.path.exists(p): raise FileAlreadyExistsException(str(path)) open(p, "a").close() return path @staticmethod def createTempFile(prefix, suffix, dir=None): f = _tempfile.NamedTemporaryFile(prefix=prefix, suffix=suffix, dir=str(dir) if dir else None, delete=False) p = f.name f.close() return Path(p) @staticmethod def createTempDirectory(prefix, attrs=None): d = _tempfile.mkdtemp(prefix=prefix) return Path(d) @staticmethod def exists(path, *options): return _os.path.exists(str(path)) @staticmethod def notExists(path, *options): return not _os.path.exists(str(path)) @staticmethod def isReadable(path): return _os.access(str(path), _os.R_OK) @staticmethod def isWritable(path): return _os.access(str(path), _os.W_OK) @staticmethod def isExecutable(path): return _os.access(str(path), _os.X_OK) @staticmethod def isDirectory(path, *options): return _os.path.isdir(str(path)) @staticmethod def isRegularFile(path, *options): return _os.path.isfile(str(path)) @staticmethod def isSymbolicLink(path): return _os.path.islink(str(path)) @staticmethod def isHidden(path): return _os.path.basename(str(path)).startswith(".") @staticmethod def getLastModifiedTime(path, *options): return int(_os.path.getmtime(str(path)) * 1000) @staticmethod def setLastModifiedTime(path, time): _os.utime(str(path), (time / 1000, time / 1000)) @staticmethod def getAttribute(path, attribute, *options): return None @staticmethod def setAttribute(path, attribute, value, *options): pass @staticmethod def getPosixFilePermissions(path, *options): return [] @staticmethod def setPosixFilePermissions(path, perms, *options): pass @staticmethod def newInputStream(path, *options): from ..io import FileInputStream return FileInputStream(str(path)) @staticmethod def newOutputStream(path, *options): from ..io import FileOutputStream append = any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options) return FileOutputStream(str(path), append) @staticmethod def newBufferedReader(path, charset=None): from ..io import BufferedReader, InputStreamReader, FileInputStream inp = FileInputStream(str(path)) reader = InputStreamReader(inp, charset or "utf-8") return BufferedReader(reader) @staticmethod def newBufferedWriter(path, charset=None, *options): from ..io import BufferedWriter, OutputStreamWriter, FileOutputStream append = any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options) out = FileOutputStream(str(path), append) writer = OutputStreamWriter(out, charset or "utf-8") return BufferedWriter(writer) @staticmethod def readAllBytes(path): with open(str(path), "rb") as f: return f.read() @staticmethod def readAllLines(path, charset=None): with open(str(path), "r", encoding=charset or "utf-8") as f: return f.read().splitlines() @staticmethod def readString(path, charset=None): with open(str(path), "r", encoding=charset or "utf-8") as f: return f.read() @staticmethod def write(path, content, *options): mode = "wb" if isinstance(content, bytes) else "w" open_mode = mode if any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options): open_mode = "ab" if mode == "wb" else "a" with open(str(path), open_mode) as f: f.write(content) @staticmethod def writeString(path, content, *options): open_mode = "w" if any(isinstance(o, StandardOpenOption) and o == StandardOpenOption.APPEND for o in options): open_mode = "a" with open(str(path), open_mode, encoding="utf-8") as f: f.write(content) @staticmethod def list(path): try: return [Path(_os.path.join(str(path), entry)) for entry in _os.listdir(str(path))] except OSError: raise IOException(f"Failed to list directory: {path}") @staticmethod def walk(path, maxDepth=None): for root, dirs, files in _os.walk(str(path)): yield Path(root) @staticmethod def find(start, maxDepth, matcher, *options): for root, dirs, files in _os.walk(str(start)): for f in files: p = Path(_os.path.join(root, f)) if matcher.matches(p): yield p @staticmethod def lines(path, charset=None): with open(str(path), "r", encoding=charset or "utf-8") as f: for line in f: yield line.rstrip("\n") @staticmethod def getFileStore(path): return FileStore(str(path)) @staticmethod def probeContentType(path): return "application/octet-stream" @staticmethod def getOwner(path, *options): raise AndroidOnlyError("getOwner") @staticmethod def setOwner(path, owner, *options): raise AndroidOnlyError("setOwner")