feat: Android-pyApi v1.0 - 全面翻译 Android SDK API 为 Python
52 个模块覆盖 Android + Java + org.json 标准库
This commit is contained in:
commit
bfe27fdc08
104 files changed
+58750
No files matched your search
@@ -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")
|
||||
Reference in new issue
Block a user