from wsgidav.dav_error import DAVError, HTTP_BAD_REQUEST, HTTP_FORBIDDEN, \
    HTTP_NOT_FOUND, HTTP_INTERNAL_ERROR, HTTP_TOO_MANY_FILES_IN_LIBRARY

from wsgidav.dav_provider import DAVProvider, DAVCollection, DAVNonCollection
from threading import Timer, Lock

import wsgidav.util as util
import os
import time
import posixpath
import unicodedata

import tempfile

from seaserv import seafile_api
from pysearpc import SearpcError
from seafobj import commit_mgr, fs_mgr
from seafobj.fs import SeafFile, SeafDir
from seafobj.blocks import block_mgr
from wsgidav.dc.seaf_utils import SEAFILE_CONF_DIR

__docformat__ = "reStructuredText"

_logger = util.get_module_logger(__name__)

NEED_PROGRESS = 0
SYNCHRONOUS = 1

INFINITE_QUOTA = -2


def sort_repo_list(repos):
    return sorted(repos, key=lambda r: r.id)


class BlockMap(object):
    def __init__(self):
        self.block_sizes = []
        self.timestamp = time.time()


class SeafileStream(object):
    '''Implements basic file-like interface'''
    def __init__(self, file_obj, block_map, block_map_lock):
        self.file_obj = file_obj
        self.block = None
        self.block_idx = 0
        self.block_offset = 0
        self.block_map = block_map
        self.block_map_lock = block_map_lock

    def read(self, size):
        remain = size
        blocks = self.file_obj.blocks
        ret = b''

        while True:
            if not self.block:
                if self.block_idx == len(blocks):
                    break
                self.block = block_mgr.load_block(self.file_obj.store_id,
                                                  self.file_obj.version,
                                                  blocks[self.block_idx])

            if self.block_offset + remain >= len(self.block):
                self.block_idx += 1
                ret += self.block[self.block_offset:]
                remain -= (len(self.block) - self.block_offset)
                self.block = None
                self.block_offset = 0
            else:
                ret += self.block[self.block_offset:self.block_offset + remain]
                self.block_offset += remain
                remain = 0

            if remain == 0:
                break

        return ret

    def close(self):
        pass

    def seek(self, pos):
        self.block = None
        self.block_idx = 0
        self.block_offset = 0

        current_pos = pos
        if current_pos == 0:
            return

        with self.block_map_lock:
            if self.file_obj.obj_id not in self.block_map:
                block_map = BlockMap()
                for i in range(len(self.file_obj.blocks)):
                    block_size = block_mgr.stat_block(self.file_obj.store_id, self.file_obj.version, self.file_obj.blocks[i])
                    block_map.block_sizes.append(block_size)
                self.block_map[self.file_obj.obj_id] = block_map
            block_map = self.block_map[self.file_obj.obj_id]
            block_map.timestamp = time.time()

        while current_pos > 0:
            if self.block_idx == len(self.file_obj.blocks):
                break
            block_size = block_map.block_sizes[self.block_idx]
            if current_pos >= block_size:
                self.block_idx += 1
                current_pos -= block_size
                self.block_offset = 0
            else:
                self.block_offset = current_pos
                current_pos = 0


class SeafileResource(DAVNonCollection):

    def __init__(self, path, repo, rel_path, obj, environ, block_map={}, block_map_lock=None):
        super(SeafileResource, self).__init__(path, environ)
        self.repo = repo
        self.rel_path = rel_path
        self._file_path: str = rel_path
        self.obj = obj
        self.username = environ.get("http_authenticator.username", "")
        self.org_id = environ.get("seafile.org_id", "")
        self.is_guest = environ.get("seafile.is_guest", False)
        self.tmpfile_path = None
        self.owner = None
        self.block_map = block_map
        self.block_map_lock = block_map_lock

    # Getter methods for standard live properties
    def get_content_length(self):
        return self.obj.size

    def get_content_type(self):
        # (mimetype, _mimeencoding) = mimetypes.guess_type(self.path)
        # print "mimetype(%s): %r, %r" % (self.path, mimetype, _mimeencoding)
        # if not mimetype:
        #     mimetype = "application/octet-stream"
        # print "mimetype(%s): return %r" % (self.path, mimetype)
        # return mimetype
        return util.guess_mime_type(self.path)

    def get_creation_date(self):
        # return int(time.time())

        return None

    def get_display_name(self):
        return self.name

    def get_etag(self):
        return self.obj.obj_id

    def is_link(self):
        return os.path.islink(self._file_path)

    def get_last_modified(self):
        cached_mtime = getattr(self.obj, 'last_modified', None)
        if cached_mtime:
            return cached_mtime

        if self.obj.mtime > 0:
            return self.obj.mtime

        # XXX: What about not return last modified for files in v0 repos,
        # since they can be too expensive sometimes?
        parent, filename = os.path.split(self.rel_path)
        try:
            mtimes = seafile_api.get_files_last_modified(self.repo.id, parent, -1)
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
        for mtime in mtimes:
            if (mtime.file_name == filename):
                return mtime.last_modified

        return None

    def support_etag(self):
        return True

    def support_ranges(self):
        return True

    def get_content(self):
        """Open content as a stream for reading.

        See DAVResource.getContent()
        """
        assert not self.is_collection
        return SeafileStream(self.obj, self.block_map, self.block_map_lock)

    def check_repo_owner_quota(self, isnewfile=True, contentlength=-1):
        """Check if the upload would cause the user quota be exceeded

        `contentlength` is only positive when the client does not use "transfer-encode: chunking"

        Return True if the quota would not be exceeded, otherwise return False.
        """
        try:
            if contentlength <= 0:
                # When client use "transfer-encode: chunking", the content length
                # is not included in the request headers
                if isnewfile:
                    return seafile_api.check_quota(self.repo.id) >= 0
                else:
                    return True
            else:
                delta = contentlength - self.obj.size
                return seafile_api.check_quota(self.repo.id, delta) >= 0
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

    def begin_write(self, content_type=None, isnewfile=True, contentlength=-1):
        """Open content as a stream for writing.

        See DAVResource.beginWrite()
        """
        assert not self.is_collection
        if self.provider.readonly:
            raise DAVError(HTTP_FORBIDDEN)

        try:
            if seafile_api.check_permission_by_path(self.repo.id, self.rel_path, self.username) != "rw":
                raise DAVError(HTTP_FORBIDDEN)
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

        if not self.check_repo_owner_quota(isnewfile, contentlength):
            raise DAVError(HTTP_FORBIDDEN, "The quota of the repo owner is exceeded")

        fd, path = tempfile.mkstemp(dir=self.provider.tmpdir)
        self.tmpfile_path = path
        return os.fdopen(fd, "wb")

    def end_write(self, with_errors, isnewfile=True):
        try:
            if not with_errors:
                parent, filename = os.path.split(self.rel_path)
                contentlength = os.stat(self.tmpfile_path).st_size
                if not self.check_repo_owner_quota(isnewfile=isnewfile, contentlength=contentlength):
                    if self.tmpfile_path:
                        try:
                            os.unlink(self.tmpfile_path)
                        finally:
                            self.tmpfile_path = None
                    raise DAVError(HTTP_FORBIDDEN, "The quota of the repo owner is exceeded")
                seafile_api.put_file(self.repo.id, self.tmpfile_path, parent, filename,
                                     self.username, None)
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
        finally:
            if self.tmpfile_path:
                try:
                    os.unlink(self.tmpfile_path)
                finally:
                    self.tmpfile_path = None

    def handle_delete(self):
        if self.provider.readonly:
            raise DAVError(HTTP_FORBIDDEN)

        try:
            if seafile_api.check_permission_by_path(self.repo.id, self.rel_path, self.username) != "rw":
                raise DAVError(HTTP_FORBIDDEN)

            file_id = seafile_api.get_file_id_by_path(self.repo.id, self.rel_path)
            if file_id is None:
                return True

            parent, filename = os.path.split(self.rel_path)
            seafile_api.del_file(self.repo.id, parent, '[\"' + filename + '\"]', self.username)
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

        return True

    def handle_move(self, dest_path):
        if self.provider.readonly:
            raise DAVError(HTTP_FORBIDDEN)

        parts = dest_path.strip("/").split("/", 1)
        if len(parts) <= 1:
            raise DAVError(HTTP_BAD_REQUEST)
        repo_name = parts[0]
        rel_path = parts[1]

        dest_dir, dest_file = os.path.split(rel_path)
        dest_repo = getRepoByName(repo_name, self.username, self.org_id, self.is_guest)
        if dest_repo.id is None:
            raise DAVError(HTTP_BAD_REQUEST)

        try:
            if seafile_api.check_permission_by_path(dest_repo.id, self.rel_path, self.username) != "rw":
                raise DAVError(HTTP_FORBIDDEN)

            src_dir, src_file = os.path.split(self.rel_path)

            if not seafile_api.is_valid_filename(dest_repo.id, dest_file):
                raise DAVError(HTTP_BAD_REQUEST)

            # some clients such as GoodReader requires "overwrite" semantics
            file_id_dest = seafile_api.get_file_id_by_path(dest_repo.id, rel_path)
            if file_id_dest is not None:
                seafile_api.del_file(dest_repo.id, dest_dir, '[\"' + dest_file + '\"]',
                                     self.username)

            seafile_api.move_file(self.repo.id, src_dir, '[\"' + src_file + '\"]',
                                  dest_repo.id, dest_dir, '[\"' + dest_file + '\"]',
                                  1, self.username, NEED_PROGRESS, SYNCHRONOUS)
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

        return True

    def handle_copy(self, dest_path, depth_infinity):
        if self.provider.readonly:
            raise DAVError(HTTP_FORBIDDEN)

        parts = dest_path.strip("/").split("/", 1)
        if len(parts) <= 1:
            raise DAVError(HTTP_BAD_REQUEST)
        repo_name = parts[0]
        rel_path = parts[1]

        dest_dir, dest_file = os.path.split(rel_path)
        dest_repo = getRepoByName(repo_name, self.username, self.org_id, self.is_guest)
        if dest_repo.id is None:
            raise DAVError(HTTP_BAD_REQUEST)

        try:
            if seafile_api.check_permission_by_path(dest_repo.id, self.rel_path, self.username) != "rw":
                raise DAVError(HTTP_FORBIDDEN)

            src_dir, src_file = os.path.split(self.rel_path)
            if not src_file:
                raise DAVError(HTTP_BAD_REQUEST)

            if not seafile_api.is_valid_filename(dest_repo.id, dest_file):
                raise DAVError(HTTP_BAD_REQUEST)

            seafile_api.copy_file(self.repo.id, src_dir, '[\"' + src_file + '\"]',
                                  dest_repo.id, dest_dir, '[\"' + dest_file + '\"]',
                                  self.username, NEED_PROGRESS, SYNCHRONOUS)
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

        return True


class SeafDirResource(DAVCollection):

    def __init__(self, path, repo, rel_path, obj, environ):
        super(SeafDirResource, self).__init__(path, environ)
        self.repo = repo
        self.rel_path = rel_path
        self._file_path: str = rel_path
        self.obj = obj
        self.username = environ.get("http_authenticator.username", "")
        self.org_id = environ.get("seafile.org_id", "")
        self.is_guest = environ.get("seafile.is_guest", False)

    # Getter methods for standard live properties
    def get_creation_date(self):
        # return int(time.time())
        return None

    def get_display_name(self):
        return self.name

    def get_directory_info(self):
        return None

    def get_etag(self):
        return self.obj.obj_id

    def get_last_modified(self):
        if not self.rel_path:
            # is repo
            return self.repo.last_modified
        else:
            # is folder
            dir_obj = seafile_api.get_dirent_by_path(self.repo.id,
                                                     self.rel_path)
            return dir_obj.mtime

    def is_link(self):
        return os.path.islink(self._file_path)

    def get_member_names(self):
        namelist = []
        for e in self.obj.dirs:
            namelist.append(e[0])
        for e in self.obj.files:
            namelist.append(e[0])
        return namelist

    def get_member(self, name):
        member_rel_path = "/".join([self.rel_path, name])
        member_path = "/".join([self.path, name])
        member = self.obj.lookup(name)

        if not member:
            raise DAVError(HTTP_NOT_FOUND)

        if isinstance(member, SeafFile):
            return SeafileResource(member_path, self.repo, member_rel_path,
                                   member, self.environ)
        else:
            return SeafDirResource(member_path, self.repo, member_rel_path,
                                   member, self.environ)

    def get_member_list(self):
        member_list = []
        d = self.obj

        if d.version == 0:
            file_mtimes = []
            try:
                file_mtimes = seafile_api.get_files_last_modified(self.repo.id, self.rel_path, -1)
            except SearpcError as e:
                raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

            mtimes = {}
            for entry in file_mtimes:
                mtimes[entry.file_name] = entry.last_modified
        for name, dent in d.dirents.items():
            member_path = posixpath.join(self.path, name)
            member_rel_path = posixpath.join(self.rel_path, name)

            if dent.is_dir():
                obj = fs_mgr.load_seafdir(d.store_id, d.version, dent.id)
                res = SeafDirResource(member_path, self.repo, member_rel_path, obj, self.environ)
            elif dent.is_file():
                obj = fs_mgr.load_seafile(d.store_id, d.version, dent.id)
                res = SeafileResource(member_path, self.repo, member_rel_path, obj, self.environ)
            else:
                continue

            if d.version == 1:
                obj.last_modified = dent.mtime
            else:
                obj.last_modified = mtimes[name]

            member_list.append(res)

        return member_list

    # --- Read / write ---------------------------------------------------------
    def create_empty_resource(self, name):
        """Create an empty (length-0) resource.

        See DAVResource.createEmptyResource()
        """
        assert "/" not in name
        if self.provider.readonly:
            raise DAVError(HTTP_FORBIDDEN)

        try:
            if seafile_api.check_permission_by_path(self.repo.id, self.rel_path, self.username) != "rw":
                raise DAVError(HTTP_FORBIDDEN)

            if seafile_api.check_quota(self.repo.id) < 0:
                raise DAVError(HTTP_FORBIDDEN, "The quota of the repo owner is exceeded")
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

        try:
            seafile_api.post_empty_file(self.repo.id, self.rel_path, name, self.username)
        except Exception as e:
            if e.msg == 'Invalid file name':
                raise DAVError(HTTP_BAD_REQUEST, e.msg)
            if e.msg == 'Too many files in library.':
                raise DAVError(HTTP_TOO_MANY_FILES_IN_LIBRARY, e.msg)
            if e.msg != 'file already exists':
                raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

        try:
            # Repo was updated, can't use self.repo
            repo = seafile_api.get_repo(self.repo.id)
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
        if not repo:
            raise DAVError(HTTP_INTERNAL_ERROR)

        member_rel_path = "/".join([self.rel_path, name])
        member_path = "/".join([self.path, name])
        obj = resolveRepoPath(repo, member_rel_path)
        if not obj or not isinstance(obj, SeafFile):
            raise DAVError(HTTP_INTERNAL_ERROR)

        return SeafileResource(member_path, repo, member_rel_path, obj, self.environ)

    def create_collection(self, name):
        """Create a new collection as member of self.

        See DAVResource.createCollection()
        """
        assert "/" not in name
        if self.provider.readonly:
            raise DAVError(HTTP_FORBIDDEN)

        try:
            if seafile_api.check_permission_by_path(self.repo.id, self.rel_path, self.username) != "rw":
                raise DAVError(HTTP_FORBIDDEN)

            if not seafile_api.is_valid_filename(self.repo.id, name):
                raise DAVError(HTTP_BAD_REQUEST)

            seafile_api.post_dir(self.repo.id, self.rel_path, name, self.username)
        except SearpcError as e:
            if e.msg != 'file already exists':
                raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

    def handle_delete(self):
        if self.provider.readonly:
            raise DAVError(HTTP_FORBIDDEN)

        try:
            if seafile_api.check_permission_by_path(self.repo.id, self.rel_path, self.username) != "rw":
                raise DAVError(HTTP_FORBIDDEN)

            parent, filename = os.path.split(self.rel_path)
            # Can't delete repo root
            if not filename:
                raise DAVError(HTTP_BAD_REQUEST)

            seafile_api.del_file(self.repo.id, parent, '[\"' + filename + '\"]', self.username)
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

        return True

    def handle_move(self, dest_path):
        if self.provider.readonly:
            raise DAVError(HTTP_FORBIDDEN)

        parts = dest_path.strip("/").split("/", 1)
        if len(parts) <= 1:
            raise DAVError(HTTP_BAD_REQUEST)
        repo_name = parts[0]
        rel_path = parts[1]

        dest_dir, dest_file = os.path.split(rel_path)
        dest_repo = getRepoByName(repo_name, self.username, self.org_id, self.is_guest)

        if dest_repo.id is None or self.rel_path is None or self.username is None:
            raise DAVError(HTTP_BAD_REQUEST)

        try:
            if seafile_api.check_permission_by_path(dest_repo.id, self.rel_path, self.username) != "rw":
                raise DAVError(HTTP_FORBIDDEN)

            src_dir, src_file = os.path.split(self.rel_path)
            if not src_file:
                raise DAVError(HTTP_BAD_REQUEST)

            if not seafile_api.is_valid_filename(dest_repo.id, dest_file):
                raise DAVError(HTTP_BAD_REQUEST)

            seafile_api.move_file(self.repo.id, src_dir, '[\"' + src_file + '\"]',
                                  dest_repo.id, dest_dir, '[\"' + dest_file + '\"]',
                                  0, self.username, NEED_PROGRESS, SYNCHRONOUS)
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

        return True

    def handle_copy(self, dest_path, depth_infinity):
        if self.provider.readonly:
            raise DAVError(HTTP_FORBIDDEN)

        parts = dest_path.strip("/").split("/", 1)
        if len(parts) <= 1:
            raise DAVError(HTTP_BAD_REQUEST)
        repo_name = parts[0]
        rel_path = parts[1]

        dest_dir, dest_file = os.path.split(rel_path)
        dest_repo = getRepoByName(repo_name, self.username, self.org_id, self.is_guest)

        if dest_repo.id is None or self.rel_path is None or self.username is None:
            raise DAVError(HTTP_BAD_REQUEST)

        try:
            if seafile_api.check_permission_by_path(dest_repo.id, self.rel_path, self.username) != "rw":
                raise DAVError(HTTP_FORBIDDEN)

            src_dir, src_file = os.path.split(self.rel_path)
            if not src_file:
                raise DAVError(HTTP_BAD_REQUEST)

            if not seafile_api.is_valid_filename(dest_repo.id, dest_file):
                raise DAVError(HTTP_BAD_REQUEST)

            seafile_api.copy_file(self.repo.id, src_dir, '[\"' + src_file + '\"]',
                                  dest_repo.id, dest_dir, '[\"' + dest_file + '\"]',
                                  self.username, NEED_PROGRESS, SYNCHRONOUS)
        except SearpcError as e:
            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)

        return True


class RootResource(DAVCollection):

    def __init__(self, username, environ, show_repo_id):
        super(RootResource, self).__init__("/", environ)
        self.username = username
        self.show_repo_id = show_repo_id
        self.org_id = environ.get('seafile.org_id', '')
        self.is_guest = environ.get('seafile.is_guest', False)

    # Getter methods for standard live properties
    def get_creation_date(self):
        # return int(time.time())
        return None

    def get_display_name(self):
        return ""

    def get_directory_info(self):
        return None

    def get_etag(self):
        return None

    def getLastModified(self):
        # return int(time.time())
        return None

    def get_member_names(self):
        all_repos = getAccessibleRepos(self.username, self.org_id, self.is_guest)

        name_hash = {}
        for r in all_repos:
            r_list = name_hash[r.name]
            if not r_list:
                name_hash[r.name] = [r]
            else:
                r_list.append(r)

        namelist = []
        for r_list in name_hash.values():
            if len(r_list) == 1:
                repo = r_list[0]
                namelist.append(repo.name)
            else:
                for repo in sort_repo_list(r_list):
                    unique_name = repo.name + "-" + repo.id[:6]
                    namelist.append(unique_name)

        return namelist

    def get_member(self, name):
        repo = getRepoByName(name, self.username, self.org_id, self.is_guest)
        return self._createRootRes(repo, name)

    def get_member_list(self):
        """
        Overwrite this method for better performance.
        The default implementation call getMemberNames() then call getMember()
        for each name. This calls getAccessibleRepos() for too many times.
        """
        all_repos = getAccessibleRepos(self.username, self.org_id, self.is_guest)

        name_hash = {}
        for r in all_repos:
            r_list = name_hash.get(r.name, [])
            if not r_list:
                name_hash[r.name] = [r]
            else:
                r_list.append(r)

        member_list = []
        for r_list in name_hash.values():
            if len(r_list) == 1:
                repo = r_list[0]
                unique_name = repo.name
                if self.show_repo_id:
                    unique_name = repo.name + "-" + repo.id[:6]
                res = self._createRootRes(repo, unique_name)
                member_list.append(res)
            else:
                for repo in sort_repo_list(r_list):
                    unique_name = repo.name + "-" + repo.id[:6]
                    res = self._createRootRes(repo, unique_name)
                    member_list.append(res)

        return member_list

    def _createRootRes(self, repo, name):
        obj = get_repo_root_seafdir(repo)
        return SeafDirResource("/" + name, repo, "", obj, self.environ)

    # --- Read / write ---------------------------------------------------------

    def create_empty_resource(self, name):
        raise DAVError(HTTP_FORBIDDEN)

    def create_collection(self, name):
        raise DAVError(HTTP_FORBIDDEN)

    def handle_delete(self):
        raise DAVError(HTTP_FORBIDDEN)

    def handle_move(self, dest_path):
        raise DAVError(HTTP_FORBIDDEN)

    def handle_copy(self, dest_path, depth_infinity):
        raise DAVError(HTTP_FORBIDDEN)


class SeafileProvider(DAVProvider):

    def __init__(self, show_repo_id, readonly=False):
        super(SeafileProvider, self).__init__()
        self.readonly = readonly
        self.show_repo_id = show_repo_id
        self.tmpdir = os.path.join(SEAFILE_CONF_DIR, "webdavtmp")
        self.block_map = {}
        self.block_map_lock = Lock()
        self.clean_block_map_task_started = False
        if not os.access(self.tmpdir, os.F_OK):
            os.mkdir(self.tmpdir)

    def clean_block_map_per_hour(self):
        delete_items = []
        with self.block_map_lock:
            for obj_id, block in self.block_map.items():
                if time.time() - block.timestamp >= 3600 * 24:
                    delete_items.append(obj_id)
            for i in range(len(delete_items)):
                self.block_map.pop(delete_items[i])
        t = Timer(3600, self.clean_block_map_per_hour)
        t.start()

    def __repr__(self):
        rw = "Read-Write"
        if self.readonly:
            rw = "Read-Only"
        return "%s for Seafile (%s)" % (self.__class__.__name__, rw)

    def get_resource_inst(self, path, environ):
        """Return info dictionary for path.

        See DAVProvider.getResourceInst()
        """

        # start the scheduled task of cleaning up the block map here,
        # because __init__ runs in a separate process.
        if not self.clean_block_map_task_started:
            self.clean_block_map_task_started = True
            self.clean_block_map_per_hour()

        self._count_get_resource_inst += 1

        username = environ.get("http_authenticator.username", "")
        org_id = environ.get("seafile.org_id", "")
        is_guest = environ.get("seafile.is_guest", False)

        if path == "/" or path == "":
            return RootResource(username, environ, self.show_repo_id)

        path = path.rstrip("/")
        try:
            repo, rel_path, obj = resolvePath(path, username, org_id, is_guest)
        except DAVError as e:
            if e.value == HTTP_NOT_FOUND:
                return None
            raise

        if isinstance(obj, SeafDir):
            return SeafDirResource(path, repo, rel_path, obj, environ)
        return SeafileResource(path, repo, rel_path, obj, environ, self.block_map, self.block_map_lock)


def resolvePath(path, username, org_id, is_guest):
    path = unicodedata.normalize('NFC', path)
    segments = path.strip("/").split("/")
    if len(segments) == 0:
        raise DAVError(HTTP_BAD_REQUEST)
    repo_name = segments.pop(0)

    repo = getRepoByName(repo_name, username, org_id, is_guest)

    rel_path = ""
    obj = get_repo_root_seafdir(repo)

    n_segs = len(segments)
    i = 0
    parent = None
    for segment in segments:
        parent = obj
        obj = parent.lookup(segment)

        if not obj or (isinstance(obj, SeafFile) and i != n_segs - 1):
            raise DAVError(HTTP_NOT_FOUND)

        rel_path += "/" + segment
        i += 1

    if parent:
        obj.mtime = parent.lookup_dent(segment).mtime

    return (repo, rel_path, obj)


def resolveRepoPath(repo, path):
    path = unicodedata.normalize('NFC', path)
    segments = path.strip("/").split("/")

    obj = get_repo_root_seafdir(repo)

    n_segs = len(segments)
    i = 0
    for segment in segments:
        obj = obj.lookup(segment)

        if not obj or (isinstance(obj, SeafFile) and i != n_segs - 1):
            return None

        i += 1

    return obj


def get_repo_root_seafdir(repo):
    root_id = commit_mgr.get_commit_root_id(repo.id, repo.version, repo.head_cmmt_id)
    return fs_mgr.load_seafdir(repo.store_id, repo.version, root_id)


def getRepoByName(repo_name, username, org_id, is_guest):
    repos = getAccessibleRepos(username, org_id, is_guest)

    ret_repo = None
    for repo in repos:
        if repo.name == repo_name:
            ret_repo = repo
            break

    if not ret_repo:
        for repo in repos:
            if repo.name + "-" + repo.id[:6] == repo_name:
                ret_repo = repo
                break
        if not ret_repo:
            raise DAVError(HTTP_NOT_FOUND)

    return ret_repo


def getAccessibleRepos(username, org_id, is_guest):
    all_repos = {}

    def addRepo(repo):
        if all_repos.get(repo.repo_id):
            return
        if not repo.encrypted:
            all_repos[repo.repo_id] = repo

    try:
        owned_repos = get_owned_repos(username, org_id)
    except SearpcError as e:
        util.warn("Failed to list owned repos: %s" % e.msg)

    for orepo in owned_repos:
        if orepo:
            # store_id is used by seafobj to access fs object.
            # repo's store_id is equal to repo_id except virtual_repo.
            orepo.store_id = orepo.repo_id
            addRepo(orepo)

    try:
        shared_repos = get_share_in_repo_list(username, org_id)
    except SearpcError as e:
        util.warn("Failed to list shared repos: %s" % e.msg)

    for srepo in shared_repos:
        if srepo:
            addRepo(srepo)
            pass

    try:
        repos = get_group_repos(username, org_id)
    except SearpcError:
        util.warn("Failed to get groups for %s" % username)
    for grepo in repos:
        if grepo:
            addRepo(grepo)

    for prepo in list_inner_pub_repos(username, org_id, is_guest):
        if prepo:
            addRepo(prepo)

    return all_repos.values()


def get_group_repos(username, org_id):
    if org_id:
        group_repos = seafile_api.get_org_group_repos_by_user(username, org_id)
    else:
        group_repos = seafile_api.get_group_repos_by_user(username)

    return [repo for repo in group_repos if repo.repo_type != 'wiki']

def get_owned_repos(username, org_id):
    if org_id:
        owned_repos = seafile_api.get_org_owned_repo_list(org_id, username)
    else:
        owned_repos = seafile_api.get_owned_repo_list(username)

    return [repo for repo in owned_repos if repo.repo_type != 'wiki']


def get_share_in_repo_list(username, org_id):
    """List share in repos.
    """
    if org_id:
        repo_list = seafile_api.get_org_share_in_repo_list(org_id, username,
                                                           -1, -1)
    else:
        repo_list = seafile_api.get_share_in_repo_list(username, -1, -1)

    return [repo for repo in repo_list if repo.repo_type != 'wiki']


def list_inner_pub_repos(username, org_id, is_guest):
    if is_guest:
        return []

    if org_id:
        return seafile_api.list_org_inner_pub_repos(org_id)

    return seafile_api.get_inner_pub_repo_list()
