You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-lib/pym/calculate/lib/utils/vfs.py

543 lines
14 KiB

# -*- coding: utf-8 -*-
# Copyright 2016 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from .files import readFile
3 years ago
from collections import deque
from collections.abc import Mapping
from os import path
import json
from functools import wraps
import fnmatch
3 years ago
from . import tools
class VFSError(Exception):
pass
class VFSErrorNotFound(VFSError):
pass
class VFSErrorExists(VFSError):
pass
class VFSErrorBadsymlink(VFSError):
pass
class VFSErrorCycliclink(VFSErrorBadsymlink):
pass
class VFSErrorNotImplemented(VFSError):
pass
class VFSErrorRestoreFailed(VFSError):
pass
class VFS(tools.GenericFs):
"""
Виртуальная файловая система
"""
def __init__(self, vfs=None):
if isinstance(vfs, VFS):
self.root = vfs.root
else:
self.root = VFSRoot()
def get(self, fn):
r = self.root
for dn in (x for x in fn.split('/') if x):
r = r[dn]
return r
def walk(self, sort=False):
deq = deque((self.root,))
if sort:
sortfunc = lambda z: sorted(z, key=lambda y: y.name, reverse=True)
else:
sortfunc = iter
while deq:
d = deq.pop()
if isinstance(d, VFSDirectory):
for x in sortfunc(d):
deq.append(x)
yield d
def read(self, fn):
return self.get(fn).read()
def write(self, fn, data):
return self.get(fn).write(data)
def listdir(self, fn, fullpath=False):
return self.get(fn).listdir(fullpath=fullpath)
def lexists(self, fn):
try:
self.get(fn)
return True
except VFSErrorNotFound:
return False
def dereflink(self, obj):
objs = [obj]
while isinstance(obj, VFSLink):
obj = obj.deref()
if obj in objs:
raise VFSErrorCycliclink(obj.fullname())
return obj
def _glob(self, pathname):
paths = deque(x for x in pathname.split("/") if x)
pat = paths.popleft()
SEPARATOR = (None, None)
deq = deque([("/", self.root), SEPARATOR])
while deq:
dirname, d = deq.popleft()
if d is None:
if paths:
pat = paths.popleft()
deq.append(SEPARATOR)
continue
else:
break
if d.isdir():
dirname = path.join(dirname, d.name)
for x in d:
if fnmatch.fnmatch(x.name, pat):
if not paths:
yield (path.join(dirname, x.name), x)
else:
deq.append((dirname, x))
def glob(self, pathname):
return (x for x, y in self._glob(pathname))
def realglob(self, pathname):
return [y.fullname() for x, y in self._glob(pathname)]
def exists(self, fn):
try:
obj = self.get(fn)
if isinstance(obj, VFSLink):
obj = self.dereflink(obj)
return True
except (VFSErrorNotFound, VFSErrorBadsymlink):
return False
def realpath(self, fn):
bestpath = fn
obj = None
try:
obj = self.get(fn)
if isinstance(obj, VFSLink):
obj = self.dereflink(obj)
return obj.fullname()
except VFSErrorBadsymlink:
if isinstance(obj, VFSLink):
bestpath = obj.symlink()
except VFSErrorNotFound:
pass
return path.normpath(bestpath)
# serialization
def dump(self):
return self.root.dump()
def restore(self, dumpdata):
self.root = self.root.restore(dumpdata)
class SafeVFS(VFS):
"""
Виртуатльная файловая система, не возвращающая ошибок при обращении к
отсутстующему файлу
"""
def exists(self, fn):
return self.lexists(fn)
def read(self, fn):
try:
return super().read(fn)
except VFSError:
return ""
def listdir(self, fn, fullpath=False):
try:
return super().listdir(fn, fullpath)
except VFSError:
return []
class VFSObject():
def __init__(self, name):
self.parent = None
self.name = name
def dump(self):
raise NotImplemented()
def fullname(self):
if self.parent:
dirname = self.parent.fullname()
dirname = "" if dirname == "/" else dirname
return "%s/%s" % (dirname, self.name)
else:
return self.name
def read(self):
raise NotImplemented()
def write(self, data):
raise NotImplemented()
def listdir(self, fullpath=False):
raise NotImplemented()
def isdir(self):
return False
@classmethod
def restore(cls, data):
for clsobj in (VFSFile, VFSLink, VFSDirectory, VFSRoot):
obj = clsobj.restore(data)
if obj is not None:
return obj
raise VFSErrorRestoreFailed(data)
class VFSDirectory(VFSObject, Mapping):
def __init__(self, name, *args):
super().__init__(name)
self.children = {}
for o in args:
self.append(o)
def dump(self):
return ['d', self.name, [x.dump() for x in self]]
def isdir(self):
return True
@classmethod
def restore(cls, data):
if data[0] == "d":
objtype, name, objdata = data
d = cls(name)
for x in objdata:
d.append(VFSObject.restore(x))
return d
def __contains__(self, item):
return item.name in self.children
def append(self, obj):
if obj in self:
raise VFSErrorExists("{dirn}/{name}".format(name=obj.name,
dirn=self.fullname()))
self.children[obj.name] = obj
obj.parent = self
def __iter__(self):
return iter(self.children.values())
def __len__(self):
return len(self.children)
def __getitem__(self, item):
if item == ".":
return self
if item == "..":
return self.parent
try:
return self.children[item]
except KeyError:
raise VFSErrorNotFound(item)
def listdir(self, fullpath=False):
if fullpath:
dirname = self.fullname()
else:
dirname = ""
return [path.join(dirname, x.name) for x in self.children.values()]
def read(self):
raise VFSErrorNotImplemented("Directory does not support reading")
def write(self, data):
raise VFSErrorNotImplemented("Directory does not support writing")
def __eq__(self, obj):
return self is obj
def __ne__(self, obj):
return self is not obj
class VFSRoot(VFSDirectory):
def __init__(self, *args):
super().__init__("/", *args)
def __getitem__(self, item):
if item == "..":
return self
return super().__getitem__(item)
def dump(self):
return ['/', self.name, [x.dump() for x in self]]
@classmethod
def restore(cls, data):
if data[0] == "/":
objtype, name, objdata = data
d = cls()
for x in objdata:
d.append(VFSObject.restore(x))
return d
class VFSFile(VFSObject):
def __init__(self, name, data=""):
super().__init__(name)
self.data = data
def dump(self):
return ['r', self.name, self.read()]
@classmethod
def restore(cls, data):
if data[0] == "r":
objtype, name, objdata = data
return cls(name, objdata)
def read(self):
return self.data
def write(self, data):
self.data = data
def listdir(self, fullpath=False):
raise VFSErrorNotImplemented("File does not support listing")
def __getitem__(self, item):
raise VFSErrorNotImplemented("File does not support listing")
def detect_cyclic(f):
@wraps(f)
def wrapper(self, *args, **kw):
if self.deref_start:
raise VFSErrorCycliclink(self.fullname())
self.deref_start = True
try:
return f(self, *args, **kw)
finally:
self.deref_start = False
return wrapper
class VFSLink(VFSObject):
def __init__(self, name, target):
super().__init__(name)
self.name = name
self._target = target
self.deref_start = False
@classmethod
def restore(cls, data):
if data[0] == "l":
objtype, name, objdata = data
return cls(name, objdata)
def dump(self):
return ['l', self.name, self.symlink()]
def symlink(self):
return self._target
def get_root(self):
r = self.parent
while r.parent:
r = r.parent
return r
def deref(self):
r = self.parent
if r is None:
raise VFSErrorBadsymlink(self._target)
if self._target.startswith("/"):
r = self.get_root()
for dn in (x for x in self._target.split('/') if x):
try:
r = r[dn]
except VFSErrorNotFound:
raise VFSErrorBadsymlink(self._target)
return r
@detect_cyclic
def _isdir(self):
return self.deref().isdir()
def isdir(self):
try:
return self._isdir()
except VFSErrorBadsymlink:
return False
def __iter__(self):
if self.isdir():
return iter(self.deref())
else:
raise VFSErrorNotImplemented("Symlink is not directory")
@detect_cyclic
def read(self):
return self.deref().read()
@detect_cyclic
def write(self, data):
return self.deref().write(data)
@detect_cyclic
def listdir(self, fullpath=False):
return self.deref().listdir()
@detect_cyclic
def __getitem__(self, item):
return self.deref().__getitem__(item)
class VFSImporter():
"""
Имортёр файловой системы
"""
def __init__(self, dn, prefix="/"):
self.importdn = dn
self.prefix = prefix
def importfs(self):
vfs = VFS()
dn = self.importdn
r = vfs.root
for dns in (x for x in path.relpath(dn, self.prefix).split('/') if x):
d = VFSDirectory(dns)
r.append(d)
r = d
for root, dnames, fnames in os.walk(dn):
vroot = path.join("/", path.relpath(root, self.prefix))
r = vfs.get(vroot)
remove_dirs = deque()
remove_files = deque()
for dn in dnames:
fdn = path.join(root, dn)
if path.islink(fdn):
self.import_link(r, dn, fdn)
else:
self.import_directory(r, dn, fdn)
if not self.filter_dirs(root, dn):
remove_dirs.append(dn)
for fn in fnames:
ffn = path.join(root, fn)
if path.islink(ffn):
self.import_link(r, fn, ffn)
else:
self.import_file(r, fn, ffn)
if not self.filter_dirs(root, dn):
remove_dirs.append(dn)
while remove_dirs:
dnames.remove(remove_dirs.popleft())
while remove_files:
fnames.remove(remove_files.popleft())
return vfs
def filter_dirs(self, root, dn):
return True
def filter_files(self, root, fn):
return True
def import_link(self, dirobj, fn, ffn):
target = os.readlink(ffn)
dirobj.append(VFSLink(fn, target))
def import_file(self, dirobj, fn, ffn):
dirobj.append(VFSFile(fn))
def import_directory(self, dirobj, dn, fdn):
dirobj.append(VFSDirectory(dn))
class VFSDevfsImporter(VFSImporter):
"""
Импортер /dev
"""
def __init__(self, dn="/dev", prefix="/"):
super().__init__(dn, prefix)
def filter_dirs(self, root, dn):
if dn == "shm":
return False
return True
class VFSSysfsImporter(VFSImporter):
"""
Импортёр /sys
"""
def __init__(self, dn="/sys", prefix="/"):
super().__init__(dn, prefix)
def import_file(self, dirobj, fn, ffn):
if fn in ("version", "vendor", "model", "board_name",
"board_vendor", "refcnt", "boardname", "size",
"removable",
"type", "operstate"):
dirobj.append(VFSFile(fn, readFile(ffn)))
else:
dirobj.append(VFSFile(fn))
class VFSJsonKeeper():
"""
Сохранение и загрузыка виртуальной файловой системы
"""
@staticmethod
def save(f, vfs):
json.dump(vfs.dump(), f)
@staticmethod
def load(f):
vfs = VFS()
vfs.restore(json.load(f))
return vfs
VFSKeeper = VFSJsonKeeper