feat: Android-pyApi v1.0 - 全面翻译 Android SDK API 为 Python

52 个模块覆盖 Android + Java + org.json 标准库
This commit is contained in:
shuiyu1123 committed 2026-06-24 11:16:16 +00:00
commit bfe27fdc08
104 files changed
+58750

No files matched your search

+703
View File
@@ -0,0 +1,703 @@
"""
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")