703 lines
19 KiB
Python
703 lines
19 KiB
Python
"""
|
|
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") |