# Source code for openpaperwork_core.fs.memory

    """
    Provides support for URIs "memory://". Those files are actually stored in
    memory. It is only useful for temporary files. It has been made as a plugin
    so it can easily be disabled on low-memory systems (will fall back on
    gio.fs --> real on-disk files).
    """

    import io
    import itertools
    import logging
    import time

    from . import CommonFsPluginBase

    LOGGER = logging.getLogger(__name__)

    class _MemoryFileAdapter(io.RawIOBase):
        def __init__(self, plugin, key, mode='r'):
            super().__init__()
            self.plugin = plugin
            self.mode = mode
            self.key = key

            self.io = None
            self.io_cls = io.BytesIO if 'b' in mode else io.StringIO

            if 'r' in mode or 'a' in mode:
                if key not in self.plugin.fs:
                    raise FileNotFoundError(key)

                data = self.plugin.fs[key][1]

                if 'b' in mode and isinstance(data, str):
                    data = data.encode("utf-8")
                elif 'b' not in mode and isinstance(data, bytes):
                    data = data.decode("utf-8")

                self.io = self.io_cls(data)
            elif 'w' in mode:
                self.io = self.io_cls()
                self.plugin.fs[self.key] = (
                    time.time(), b"" if 'b' in mode else ""
                )

            self.get_content = (
                self.io.getbuffer if 'b' in mode else self.io.getvalue
            )
            self.read = self.io.read if hasattr(self.io, 'read') else None
            self.readall = self.io.readall if hasattr(self.io, 'readall') else None
            self.readinto = (
                self.io.readinto if hasattr(self.io, 'readinto') else None
            )
            self.readline = (
                self.io.readline if hasattr(self.io, 'readline') else None
            )
            self.readlines = (
                self.io.readlines if hasattr(self.io, 'readlines') else None
            )
            self.seek = self.io.seek if hasattr(self.io, 'seek') else None
            self.tell = self.io.tell if hasattr(self.io, 'tell') else None
            self.truncate = (
                self.io.truncate if hasattr(self.io, 'truncate') else None
            )
            self.write = self.io.write if hasattr(self.io, 'write') else None
            self.writelines = (
                self.io.writelines if hasattr(self.io, 'writelines') else None
            )

        def readable(self):
            return 'r' in self.mode or '+' in self.mode

        def writable(self):
            return 'w' in self.mode or 'a' in self.mode

        def seekable(self):
            return True

        def fileno(self):
            raise io.UnsupportedOperation("fileno() called on memory object")

        def isatty(self):
            return False

        def writelines(self, lines):
            if 'b' in self.mode:
                self.write(b"".join(lines))
            else:
                self.write("".join(lines))

        def flush(self):
            super().flush()
            if hasattr(self.io, 'flush'):
                self.io.flush()
            if 'w' not in self.mode and 'a' not in self.mode:
                return
            if 'b' in self.mode:
                self.plugin.fs[self.key] = (time.time(), bytes(self.get_content()))
            else:
                self.plugin.fs[self.key] = (time.time(), str(self.get_content()))

        def close(self):
            self.flush()
            super().close()

        def __enter__(self):
            return self

        def __exit__(self, type, value, traceback):
            self.close()

    class Plugin(CommonFsPluginBase):
        PRIORITY = 100  # to be called first for fs_mktemp

        def __init__(self):
            super().__init__()
            # self.fs = {id: (mtime, content), id: (mtime, content), ...}
            self.fs = {}
            self.id_gen = itertools.count()

        @staticmethod
        def _get_memory_id(uri):
            if not uri.startswith("memory://"):
                return None
            return uri[len("memory://"):]

        def fs_open(self, uri, mode='r', needs_fileno=False, **kwargs):
            if needs_fileno:
                return None
            mem_id = self._get_memory_id(uri)
            if mem_id is None:
                return None
            return _MemoryFileAdapter(self, mem_id, mode)

        def fs_exists(self, url):
            mem_id = self._get_memory_id(url)
            if mem_id is None:
                return None
            return mem_id in self.fs

        def fs_listdir(self, url):
            mem_id = self._get_memory_id(url)
            if mem_id is None:
                return

            mem_id += '/'
            out = []

            for k in self.fs.keys():
                if k.startswith(mem_id) and '/' not in k[len(mem_id):]:
                    out.append(k)

            return out

        def fs_rename(self, old_url, new_url):
            old_mem_id = self._get_memory_id(old_url)
            new_mem_id = self._get_memory_id(old_url)

            if old_mem_id is None or new_mem_id is None:
                return

            f = self.fs.pop(old_mem_id)
            self.fs[new_mem_id] = f
            return True

        def fs_unlink(self, url, **kwargs):
            mem_id = self._get_memory_id(url)
            if mem_id is None:
                return
            self.fs.pop(mem_id)
            return True

        def fs_rm_rf(self, url, **kwargs):
            mem_id = self._get_memory_id(url)
            if mem_id is None:
                return

            if mem_id in self.fs:
                self.fs.pop(mem_id)

            mem_id += '/'
            for k in list(self.fs.keys()):
                if k.startswith(mem_id):
                    self.fs.pop(k)

            return True

        def fs_get_mtime(self, url):
            mem_id = self._get_memory_id(url)
            if mem_id is None:
                return
            return self.fs[mem_id][0]

        def fs_getsize(self, url):
            mem_id = self._get_memory_id(url)
            if mem_id is None:
                return
            return len(self.fs[mem_id][1])

        def fs_isdir(self, url):
            mem_id = self._get_memory_id(url)
            if mem_id is None:
                return None

            mem_id += '/'
            for k in list(self.fs.keys()):
                if k.startswith(mem_id):
                    return True
            return None

        def fs_copy(self, old_url, new_url):
            old_mem_id = self._get_memory_id(old_url)
            new_mem_id = self._get_memory_id(old_url)

            if old_mem_id is None and new_mem_id is None:
                return None

            if old_mem_id is None or new_mem_id is None:
                # One memory url, one local filesystem url
                # use the more generic and cross-FS method
                return super().fs_copy(old_url, new_url)

            self.fs[new_mem_id] = self.fs[old_mem_id]
            return new_url

        def fs_mkdir_p(self, url):
            mem_id = self._get_memory_id(url)
            if mem_id is None:
                return None
            # nothing to do actually
            return True

        def fs_recurse(self, parent_url, dir_included=False):
            mem_id = self._get_memory_id(parent_url)
            if mem_id is None:
                return

            mem_id += '/'
            out = []

            for k in self.fs.keys():
                if k.startswith(mem_id):
                    out.append(k)

            return out

        def fs_hide(self, url):
            return None

        def fs_get_mime(self, url):
            return None

        def fs_iswritable(self, url):
            mem_id = self._get_memory_id(url)
            if mem_id is None:
                return None
            return True

        def fs_mktemp(
                    self, prefix=None, suffix=None, mode='w+b', on_disk=False,
                    **kwargs
                ):
            assert prefix is None or '/' not in prefix
            assert suffix is None or '/' not in suffix
            if on_disk:
                return
            name = "{}{}{}".format(
                prefix if prefix is not None else "",
                next(self.id_gen),
                suffix if suffix is not None else ""
            )
            return ("memory://" + name, _MemoryFileAdapter(self, name, mode))