1from wsgidav.dav_error import DAVError, HTTP_BAD_REQUEST, HTTP_FORBIDDEN, \
2    HTTP_NOT_FOUND, HTTP_INTERNAL_ERROR
3from wsgidav.dav_provider import DAVProvider, DAVCollection, DAVNonCollection
4from threading import Timer, Lock
5
6import wsgidav.util as util
7import os
8import time
9import posixpath
10
11import tempfile
12
13from seaserv import seafile_api, CALC_SHARE_USAGE
14from pysearpc import SearpcError
15from seafobj import commit_mgr, fs_mgr
16from seafobj.fs import SeafFile, SeafDir
17from seafobj.blocks import block_mgr
18from wsgidav.dc.seaf_utils import SEAFILE_CONF_DIR
19
20__docformat__ = "reStructuredText"
21
22_logger = util.get_module_logger(__name__)
23
24NEED_PROGRESS = 0
25SYNCHRONOUS = 1
26
27INFINITE_QUOTA = -2
28
29def sort_repo_list(repos):
30    return sorted(repos, key = lambda r: r.id)
31
32class BlockMap(object):
33    def __init__(self):
34        self.block_sizes = []
35        self.timestamp = time.time()
36
37class SeafileStream(object):
38    '''Implements basic file-like interface'''
39    def __init__(self, file_obj, block_map, block_map_lock):
40        self.file_obj = file_obj
41        self.block = None
42        self.block_idx = 0
43        self.block_offset = 0
44        self.block_map = block_map
45        self.block_map_lock = block_map_lock
46
47    def read(self, size):
48        remain = size
49        blocks = self.file_obj.blocks
50        ret = b''
51
52        while True:
53            if not self.block:
54                if self.block_idx == len(blocks):
55                    break
56                self.block = block_mgr.load_block(self.file_obj.store_id,
57                                                  self.file_obj.version,
58                                                  blocks[self.block_idx])
59
60            if self.block_offset + remain >= len(self.block):
61                self.block_idx += 1
62                ret += self.block[self.block_offset:]
63                remain -= (len(self.block) - self.block_offset)
64                self.block = None
65                self.block_offset = 0
66            else:
67                ret += self.block[self.block_offset:self.block_offset+remain]
68                self.block_offset += remain
69                remain = 0
70
71            if remain == 0:
72                break
73
74        return ret
75
76    def close(self):
77        pass
78
79    def seek(self, pos):
80        self.block = None
81        self.block_idx = 0
82        self.block_offset = 0
83
84        current_pos = pos
85        if current_pos == 0:
86            return
87
88        with self.block_map_lock:
89            if self.file_obj.obj_id not in self.block_map:
90                block_map = BlockMap()
91                for i in range(len(self.file_obj.blocks)):
92                    block_size = block_mgr.stat_block(self.file_obj.store_id, self.file_obj.version, self.file_obj.blocks[i])
93                    block_map.block_sizes.append(block_size)
94                self.block_map[self.file_obj.obj_id] = block_map
95            block_map = self.block_map[self.file_obj.obj_id]
96            block_map.timestamp = time.time()
97
98        while current_pos > 0:
99            if self.block_idx == len(self.file_obj.blocks):
100                break
101            block_size = block_map.block_sizes[self.block_idx]
102            if current_pos >= block_size:
103                self.block_idx += 1
104                current_pos -= block_size
105                self.block_offset = 0
106            else:
107                self.block_offset = current_pos
108                current_pos = 0
109
110#===============================================================================
111# SeafileResource
112#===============================================================================
113class SeafileResource(DAVNonCollection):
114    def __init__(self, path, repo, rel_path, obj, environ, block_map={}, block_map_lock=None):
115        super(SeafileResource, self).__init__(path, environ)
116        self.repo = repo
117        self.rel_path = rel_path
118        self.obj = obj
119        self.username = environ.get("http_authenticator.username", "")
120        self.org_id = environ.get("seafile.org_id", "")
121        self.is_guest = environ.get("seafile.is_guest", False)
122        self.tmpfile_path = None
123        self.owner = None
124        self.block_map = block_map
125        self.block_map_lock = block_map_lock
126
127    # Getter methods for standard live properties
128    def get_content_length(self):
129        return self.obj.size
130    def get_content_type(self):
131#        (mimetype, _mimeencoding) = mimetypes.guess_type(self.path)
132#        print "mimetype(%s): %r, %r" % (self.path, mimetype, _mimeencoding)
133#        if not mimetype:
134#            mimetype = "application/octet-stream"
135#        print "mimetype(%s): return %r" % (self.path, mimetype)
136#        return mimetype
137        return util.guess_mime_type(self.path)
138    def get_creation_date(self):
139#        return int(time.time())
140        return None
141    def get_display_name(self):
142        return self.name
143    def get_etag(self):
144        return self.obj.obj_id
145
146    def get_last_modified(self):
147        cached_mtime = getattr(self.obj, 'last_modified', None)
148        if cached_mtime:
149            return cached_mtime
150
151        if self.obj.mtime > 0:
152            return self.obj.mtime
153
154        # XXX: What about not return last modified for files in v0 repos,
155        # since they can be too expensive sometimes?
156        parent, filename = os.path.split(self.rel_path)
157        try:
158            mtimes = seafile_api.get_files_last_modified(self.repo.id, parent, -1)
159        except SearpcError as e:
160            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
161        for mtime in mtimes:
162            if (mtime.file_name == filename):
163                return mtime.last_modified
164
165        return None
166
167    def support_etag(self):
168        return True
169    def support_ranges(self):
170        return True
171
172    def get_content(self):
173        """Open content as a stream for reading.
174
175        See DAVResource.getContent()
176        """
177        assert not self.is_collection
178        return SeafileStream(self.obj, self.block_map, self.block_map_lock)
179
180    def check_repo_owner_quota(self, isnewfile=True, contentlength=-1):
181        """Check if the upload would cause the user quota be exceeded
182
183        `contentlength` is only positive when the client does not use "transfer-encode: chunking"
184
185        Return True if the quota would not be exceeded, otherwise return False.
186        """
187        try:
188            if contentlength <= 0:
189                # When client use "transfer-encode: chunking", the content length
190                # is not included in the request headers
191                if isnewfile:
192                    return seafile_api.check_quota(self.repo.id) >= 0
193                else:
194                    return True
195            else:
196                delta = contentlength - self.obj.size
197                return seafile_api.check_quota(self.repo.id, delta) >= 0
198        except SearpcError as e:
199            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
200
201    def begin_write(self, content_type=None, isnewfile=True, contentlength=-1):
202        """Open content as a stream for writing.
203
204        See DAVResource.beginWrite()
205        """
206        assert not self.is_collection
207        if self.provider.readonly:
208            raise DAVError(HTTP_FORBIDDEN)
209
210        try:
211            if seafile_api.check_permission_by_path(self.repo.id, self.rel_path, self.username) != "rw":
212                raise DAVError(HTTP_FORBIDDEN)
213        except SearpcError as e:
214            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
215
216        if not self.check_repo_owner_quota(isnewfile, contentlength):
217            raise DAVError(HTTP_FORBIDDEN, "The quota of the repo owner is exceeded")
218
219        fd, path = tempfile.mkstemp(dir=self.provider.tmpdir)
220        self.tmpfile_path = path
221        return os.fdopen(fd, "wb")
222
223    def end_write(self, with_errors, isnewfile=True):
224        try:
225            if not with_errors:
226                parent, filename = os.path.split(self.rel_path)
227                contentlength = os.stat(self.tmpfile_path).st_size
228                if not self.check_repo_owner_quota(isnewfile=isnewfile, contentlength=contentlength):
229                    if self.tmpfile_path:
230                        try:
231                            os.unlink(self.tmpfile_path)
232                        finally:
233                            self.tmpfile_path = None
234                    raise DAVError(HTTP_FORBIDDEN, "The quota of the repo owner is exceeded")
235                seafile_api.put_file(self.repo.id, self.tmpfile_path, parent, filename,
236                                    self.username, None)
237        except SearpcError as e:
238            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
239        finally:
240            if self.tmpfile_path:
241                try:
242                    os.unlink(self.tmpfile_path)
243                finally:
244                    self.tmpfile_path = None
245
246    def handle_delete(self):
247        if self.provider.readonly:
248            raise DAVError(HTTP_FORBIDDEN)
249
250        try:
251            if seafile_api.check_permission_by_path(self.repo.id, self.rel_path, self.username) != "rw":
252                raise DAVError(HTTP_FORBIDDEN)
253
254            file_id = seafile_api.get_file_id_by_path(self.repo.id, self.rel_path)
255            if file_id is None:
256                return True
257
258            parent, filename = os.path.split(self.rel_path)
259            seafile_api.del_file(self.repo.id, parent, filename, self.username)
260        except SearpcError as e:
261            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
262
263        return True
264
265    def handle_move(self, dest_path):
266        if self.provider.readonly:
267            raise DAVError(HTTP_FORBIDDEN)
268
269        parts = dest_path.strip("/").split("/", 1)
270        if len(parts) <= 1:
271            raise DAVError(HTTP_BAD_REQUEST)
272        repo_name = parts[0]
273        rel_path = parts[1]
274
275        dest_dir, dest_file = os.path.split(rel_path)
276        dest_repo = getRepoByName(repo_name, self.username, self.org_id, self.is_guest)
277        if dest_repo.id is None:
278            raise DAVError(HTTP_BAD_REQUEST)
279
280        try:
281            if seafile_api.check_permission_by_path(dest_repo.id, self.rel_path, self.username) != "rw":
282                raise DAVError(HTTP_FORBIDDEN)
283
284            src_dir, src_file = os.path.split(self.rel_path)
285
286            if not seafile_api.is_valid_filename(dest_repo.id, dest_file):
287                raise DAVError(HTTP_BAD_REQUEST)
288
289            # some clients such as GoodReader requires "overwrite" semantics
290            file_id_dest = seafile_api.get_file_id_by_path(dest_repo.id, rel_path)
291            if file_id_dest != None:
292                seafile_api.del_file(dest_repo.id, dest_dir, dest_file, self.username)
293
294            seafile_api.move_file(self.repo.id, src_dir, src_file,
295                                dest_repo.id, dest_dir, dest_file, 1, self.username, NEED_PROGRESS, SYNCHRONOUS)
296        except SearpcError as e:
297            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
298
299        return True
300
301    def handle_copy(self, dest_path, depth_infinity):
302        if self.provider.readonly:
303            raise DAVError(HTTP_FORBIDDEN)
304
305        parts = dest_path.strip("/").split("/", 1)
306        if len(parts) <= 1:
307            raise DAVError(HTTP_BAD_REQUEST)
308        repo_name = parts[0]
309        rel_path = parts[1]
310
311        dest_dir, dest_file = os.path.split(rel_path)
312        dest_repo = getRepoByName(repo_name, self.username, self.org_id, self.is_guest)
313        if dest_repo.id is None:
314            raise DAVError(HTTP_BAD_REQUEST)
315
316        try:
317            if seafile_api.check_permission_by_path(dest_repo.id, self.rel_path, self.username) != "rw":
318                raise DAVError(HTTP_FORBIDDEN)
319
320            src_dir, src_file = os.path.split(self.rel_path)
321            if not src_file:
322                raise DAVError(HTTP_BAD_REQUEST)
323
324            if not seafile_api.is_valid_filename(dest_repo.id, dest_file):
325                raise DAVError(HTTP_BAD_REQUEST)
326
327            seafile_api.copy_file(self.repo.id, src_dir, src_file,
328                                dest_repo.id, dest_dir, dest_file, self.username, NEED_PROGRESS, SYNCHRONOUS)
329        except SearpcError as e:
330            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
331
332        return True
333
334#===============================================================================
335# SeafDirResource
336#===============================================================================
337class SeafDirResource(DAVCollection):
338    def __init__(self, path, repo, rel_path, obj, environ):
339        super(SeafDirResource, self).__init__(path, environ)
340        self.repo = repo
341        self.rel_path = rel_path
342        self.obj = obj
343        self.username = environ.get("http_authenticator.username", "")
344        self.org_id = environ.get("seafile.org_id", "")
345        self.is_guest = environ.get("seafile.is_guest", False)
346
347    # Getter methods for standard live properties
348    def get_creation_date(self):
349#        return int(time.time())
350        return None
351    def get_display_name(self):
352        return self.name
353    def get_directory_info(self):
354        return None
355    def get_etag(self):
356        return self.obj.obj_id
357    def get_last_modified(self):
358#        return int(time.time())
359        return None
360
361    def get_member_names(self):
362        namelist = []
363        for e in self.obj.dirs:
364            namelist.append(e[0])
365        for e in self.obj.files:
366            namelist.append(e[0])
367        return namelist
368
369    def get_member(self, name):
370        member_rel_path = "/".join([self.rel_path, name])
371        member_path = "/".join([self.path, name])
372        member = self.obj.lookup(name)
373
374        if not member:
375            raise DAVError(HTTP_NOT_FOUND)
376
377        if isinstance(member, SeafFile):
378            return SeafileResource(member_path, self.repo, member_rel_path, member, self.environ)
379        else:
380            return SeafDirResource(member_path, self.repo, member_rel_path, member, self.environ)
381
382    def get_member_list(self):
383        member_list = []
384        d = self.obj
385
386        if d.version == 0:
387            file_mtimes = []
388            try:
389                file_mtimes = seafile_api.get_files_last_modified(self.repo.id, self.rel_path, -1)
390            except SearpcError as e:
391                raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
392
393            mtimes = {}
394            for entry in file_mtimes:
395                mtimes[entry.file_name] = entry.last_modified
396        for name, dent in d.dirents.items():
397            member_path = posixpath.join(self.path, name)
398            member_rel_path = posixpath.join(self.rel_path, name)
399
400            if dent.is_dir():
401                obj = fs_mgr.load_seafdir(d.store_id, d.version, dent.id)
402                res = SeafDirResource(member_path, self.repo, member_rel_path, obj, self.environ)
403            elif dent.is_file():
404                obj = fs_mgr.load_seafile(d.store_id, d.version, dent.id)
405                res = SeafileResource(member_path, self.repo, member_rel_path, obj, self.environ)
406            else:
407                continue
408
409            if d.version == 1:
410                obj.last_modified = dent.mtime
411            else:
412                obj.last_modified = mtimes[name]
413
414            member_list.append(res)
415
416        return member_list
417
418    # --- Read / write ---------------------------------------------------------
419    def create_empty_resource(self, name):
420        """Create an empty (length-0) resource.
421
422        See DAVResource.createEmptyResource()
423        """
424        assert not "/" in name
425        if self.provider.readonly:
426            raise DAVError(HTTP_FORBIDDEN)
427
428        try:
429            if seafile_api.check_permission_by_path(self.repo.id, self.rel_path, self.username) != "rw":
430                raise DAVError(HTTP_FORBIDDEN)
431
432            if seafile_api.check_quota(self.repo.id) < 0:
433                raise DAVError(HTTP_FORBIDDEN, "The quota of the repo owner is exceeded")
434        except SearpcError as e:
435            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
436
437        try:
438            seafile_api.post_empty_file(self.repo.id, self.rel_path, name, self.username)
439        except SearpcError as e:
440            if e.msg == 'Invalid file name':
441                raise DAVError(HTTP_BAD_REQUEST, e.msg)
442            if e.msg != 'file already exists':
443                raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
444
445        try:
446            # Repo was updated, can't use self.repo
447            repo = seafile_api.get_repo(self.repo.id)
448        except SearpcError as e:
449            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
450        if not repo:
451            raise DAVError(HTTP_INTERNAL_ERROR)
452
453        member_rel_path = "/".join([self.rel_path, name])
454        member_path = "/".join([self.path, name])
455        obj = resolveRepoPath(repo, member_rel_path)
456        if not obj or not isinstance(obj, SeafFile):
457            raise DAVError(HTTP_INTERNAL_ERROR)
458
459        return SeafileResource(member_path, repo, member_rel_path, obj, self.environ)
460
461    def create_collection(self, name):
462        """Create a new collection as member of self.
463
464        See DAVResource.createCollection()
465        """
466        assert not "/" in name
467        if self.provider.readonly:
468            raise DAVError(HTTP_FORBIDDEN)
469
470        try:
471            if seafile_api.check_permission_by_path(self.repo.id, self.rel_path, self.username) != "rw":
472                raise DAVError(HTTP_FORBIDDEN)
473
474            if not seafile_api.is_valid_filename(self.repo.id, name):
475                raise DAVError(HTTP_BAD_REQUEST)
476
477            seafile_api.post_dir(self.repo.id, self.rel_path, name, self.username)
478        except SearpcError as e:
479            if e.msg != 'file already exists':
480                raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
481
482    def handle_delete(self):
483        if self.provider.readonly:
484            raise DAVError(HTTP_FORBIDDEN)
485
486        try:
487            if seafile_api.check_permission_by_path(self.repo.id, self.rel_path, self.username) != "rw":
488                raise DAVError(HTTP_FORBIDDEN)
489
490            parent, filename = os.path.split(self.rel_path)
491            # Can't delete repo root
492            if not filename:
493                raise DAVError(HTTP_BAD_REQUEST)
494
495            seafile_api.del_file(self.repo.id, parent, filename, self.username)
496        except SearpcError as e:
497            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
498
499        return True
500
501    def handle_move(self, dest_path):
502        if self.provider.readonly:
503            raise DAVError(HTTP_FORBIDDEN)
504
505        parts = dest_path.strip("/").split("/", 1)
506        if len(parts) <= 1:
507            raise DAVError(HTTP_BAD_REQUEST)
508        repo_name = parts[0]
509        rel_path = parts[1]
510
511        dest_dir, dest_file = os.path.split(rel_path)
512        dest_repo = getRepoByName(repo_name, self.username, self.org_id, self.is_guest)
513
514        if dest_repo.id is None or self.rel_path is None or self.username is None:
515            raise DAVError(HTTP_BAD_REQUEST)
516
517        try:
518            if seafile_api.check_permission_by_path(dest_repo.id, self.rel_path, self.username) != "rw":
519                raise DAVError(HTTP_FORBIDDEN)
520
521            src_dir, src_file = os.path.split(self.rel_path)
522            if not src_file:
523                raise DAVError(HTTP_BAD_REQUEST)
524
525            if not seafile_api.is_valid_filename(dest_repo.id, dest_file):
526                raise DAVError(HTTP_BAD_REQUEST)
527
528            seafile_api.move_file(self.repo.id, src_dir, src_file,
529                                dest_repo.id, dest_dir, dest_file, 0, self.username, NEED_PROGRESS, SYNCHRONOUS)
530        except SearpcError as e:
531            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
532
533        return True
534
535    def handle_copy(self, dest_path, depth_infinity):
536        if self.provider.readonly:
537            raise DAVError(HTTP_FORBIDDEN)
538
539        parts = dest_path.strip("/").split("/", 1)
540        if len(parts) <= 1:
541            raise DAVError(HTTP_BAD_REQUEST)
542        repo_name = parts[0]
543        rel_path = parts[1]
544
545        dest_dir, dest_file = os.path.split(rel_path)
546        dest_repo = getRepoByName(repo_name, self.username, self.org_id, self.is_guest)
547
548        if dest_repo.id is None or self.rel_path is None or self.username is None:
549            raise DAVError(HTTP_BAD_REQUEST)
550
551        try:
552            if seafile_api.check_permission_by_path(dest_repo.id, self.rel_path, self.username) != "rw":
553                raise DAVError(HTTP_FORBIDDEN)
554
555            src_dir, src_file = os.path.split(self.rel_path)
556            if not src_file:
557                raise DAVError(HTTP_BAD_REQUEST)
558
559            if not seafile_api.is_valid_filename(dest_repo.id, dest_file):
560                raise DAVError(HTTP_BAD_REQUEST)
561
562            seafile_api.copy_file(self.repo.id, src_dir, src_file,
563                                dest_repo.id, dest_dir, dest_file, self.username, NEED_PROGRESS, SYNCHRONOUS)
564        except SearpcError as e:
565            raise DAVError(HTTP_INTERNAL_ERROR, e.msg)
566
567        return True
568
569class RootResource(DAVCollection):
570    def __init__(self, username, environ, show_repo_id):
571        super(RootResource, self).__init__("/", environ)
572        self.username = username
573        self.show_repo_id = show_repo_id
574        self.org_id = environ.get('seafile.org_id', '')
575        self.is_guest = environ.get('seafile.is_guest', False)
576
577    # Getter methods for standard live properties
578    def get_creation_date(self):
579#        return int(time.time())
580        return None
581    def get_display_name(self):
582        return ""
583    def get_directory_info(self):
584        return None
585    def get_etag(self):
586        return None
587    def getLastModified(self):
588#        return int(time.time())
589        return None
590
591    def get_member_names(self):
592        all_repos = getAccessibleRepos(self.username, self.org_id, self.is_guest)
593
594        name_hash = {}
595        for r in all_repos:
596            r_list = name_hash[r.name]
597            if not r_list:
598                name_hash[r.name] = [r]
599            else:
600                r_list.append(r)
601
602        namelist = []
603        for r_list in name_hash.values():
604            if len(r_list) == 1:
605                repo = r_list[0]
606                namelist.append(repo.name)
607            else:
608                for repo in sort_repo_list(r_list):
609                    unique_name = repo.name + "-" + repo.id[:6]
610                    namelist.append(unique_name)
611
612        return namelist
613
614    def get_member(self, name):
615        repo = getRepoByName(name, self.username, self.org_id, self.is_guest)
616        return self._createRootRes(repo, name)
617
618    def get_member_list(self):
619        """
620        Overwrite this method for better performance.
621        The default implementation call getMemberNames() then call getMember()
622        for each name. This calls getAccessibleRepos() for too many times.
623        """
624        all_repos = getAccessibleRepos(self.username, self.org_id, self.is_guest)
625
626        name_hash = {}
627        for r in all_repos:
628            r_list = name_hash.get(r.name, [])
629            if not r_list:
630                name_hash[r.name] = [r]
631            else:
632                r_list.append(r)
633
634        member_list = []
635        for r_list in name_hash.values():
636            if len(r_list) == 1:
637                repo = r_list[0]
638                unique_name = repo.name
639                if self.show_repo_id:
640                    unique_name = repo.name + "-" + repo.id[:6]
641                res = self._createRootRes(repo, unique_name)
642                member_list.append(res)
643            else:
644                for repo in sort_repo_list(r_list):
645                    unique_name = repo.name + "-" + repo.id[:6]
646                    res = self._createRootRes(repo, unique_name)
647                    member_list.append(res)
648
649        return member_list
650
651    def _createRootRes(self, repo, name):
652        obj = get_repo_root_seafdir(repo)
653        return SeafDirResource("/"+name, repo, "", obj, self.environ)
654
655    # --- Read / write ---------------------------------------------------------
656
657    def create_empty_resource(self, name):
658        raise DAVError(HTTP_FORBIDDEN)
659
660    def create_collection(self, name):
661        raise DAVError(HTTP_FORBIDDEN)
662
663    def handle_delete(self):
664        raise DAVError(HTTP_FORBIDDEN)
665
666    def handle_move(self, dest_path):
667        raise DAVError(HTTP_FORBIDDEN)
668
669    def handle_copy(self, dest_path, depth_infinity):
670        raise DAVError(HTTP_FORBIDDEN)
671
672
673#===============================================================================
674# SeafileProvider
675#===============================================================================
676class SeafileProvider(DAVProvider):
677
678    def __init__(self, show_repo_id, readonly=False):
679        super(SeafileProvider, self).__init__()
680        self.readonly = readonly
681        self.show_repo_id = show_repo_id
682        self.tmpdir = os.path.join(SEAFILE_CONF_DIR, "webdavtmp")
683        self.block_map = {}
684        self.block_map_lock = Lock()
685        self.clean_block_map_task_started = False
686        if not os.access(self.tmpdir, os.F_OK):
687            os.mkdir(self.tmpdir)
688
689    def clean_block_map_per_hour(self):
690        delete_items = []
691        with self.block_map_lock:
692            for obj_id, block in self.block_map.items():
693                if time.time() - block.timestamp >= 3600*24:
694                    delete_items.append(obj_id)
695            for i in range(len(delete_items)):
696                self.block_map.pop(delete_items[i])
697        t = Timer(3600, self.clean_block_map_per_hour)
698        t.start()
699
700    def __repr__(self):
701        rw = "Read-Write"
702        if self.readonly:
703            rw = "Read-Only"
704        return "%s for Seafile (%s)" % (self.__class__.__name__, rw)
705
706
707    def get_resource_inst(self, path, environ):
708        """Return info dictionary for path.
709
710        See DAVProvider.getResourceInst()
711        """
712
713        # start the scheduled task of cleaning up the block map here,
714        # because __init__ runs in a separate process.
715        if not self.clean_block_map_task_started:
716            self.clean_block_map_task_started = True
717            self.clean_block_map_per_hour()
718
719        self._count_get_resource_inst += 1
720
721        username = environ.get("http_authenticator.username", "")
722        org_id = environ.get("seafile.org_id", "")
723        is_guest = environ.get("seafile.is_guest", False)
724
725        if path == "/" or path == "":
726            return RootResource(username, environ, self.show_repo_id)
727
728        path = path.rstrip("/")
729        try:
730            repo, rel_path, obj = resolvePath(path, username, org_id, is_guest)
731        except DAVError as e:
732            if e.value == HTTP_NOT_FOUND:
733                return None
734            raise
735
736        if isinstance(obj, SeafDir):
737            return SeafDirResource(path, repo, rel_path, obj, environ)
738        return SeafileResource(path, repo, rel_path, obj, environ, self.block_map, self.block_map_lock)
739
740def resolvePath(path, username, org_id, is_guest):
741    segments = path.strip("/").split("/")
742    if len(segments) == 0:
743        raise DAVError(HTTP_BAD_REQUEST)
744    repo_name = segments.pop(0)
745
746    repo = getRepoByName(repo_name, username, org_id, is_guest)
747
748    rel_path = ""
749    obj = get_repo_root_seafdir(repo)
750
751    n_segs = len(segments)
752    i = 0
753    parent = None
754    for segment in segments:
755        parent = obj
756        obj = parent.lookup(segment)
757
758        if not obj or (isinstance(obj, SeafFile) and i != n_segs-1):
759            raise DAVError(HTTP_NOT_FOUND)
760
761        rel_path += "/" + segment
762        i += 1
763
764    if parent:
765        obj.mtime = parent.lookup_dent(segment).mtime
766
767    return (repo, rel_path, obj)
768
769def resolveRepoPath(repo, path):
770    segments = path.strip("/").split("/")
771
772    obj = get_repo_root_seafdir(repo)
773
774    n_segs = len(segments)
775    i = 0
776    for segment in segments:
777        obj = obj.lookup(segment)
778
779        if not obj or (isinstance(obj, SeafFile) and i != n_segs-1):
780            return None
781
782        i += 1
783
784    return obj
785
786def get_repo_root_seafdir(repo):
787    root_id = commit_mgr.get_commit_root_id(repo.id, repo.version, repo.head_cmmt_id)
788    return fs_mgr.load_seafdir(repo.store_id, repo.version, root_id)
789
790def getRepoByName(repo_name, username, org_id, is_guest):
791    repos = getAccessibleRepos(username, org_id, is_guest)
792
793    ret_repo = None
794    for repo in repos:
795        if repo.name == repo_name:
796            ret_repo = repo
797            break
798
799    if not ret_repo:
800        for repo in repos:
801            if repo.name + "-" + repo.id[:6] == repo_name:
802                ret_repo = repo
803                break
804        if not ret_repo:
805            raise DAVError(HTTP_NOT_FOUND)
806
807    return ret_repo
808
809def getAccessibleRepos(username, org_id, is_guest):
810    all_repos = {}
811
812    def addRepo(repo):
813        if all_repos.get(repo.repo_id):
814            return
815        if not repo.encrypted:
816            all_repos[repo.repo_id] = repo
817
818    try:
819        owned_repos = get_owned_repos(username, org_id)
820    except SearpcError as e:
821        util.warn("Failed to list owned repos: %s" % e.msg)
822
823    for orepo in owned_repos:
824        if orepo:
825            # store_id is used by seafobj to access fs object.
826            # repo's store_id is equal to repo_id except virtual_repo.
827            orepo.store_id = orepo.repo_id
828            addRepo(orepo)
829
830    try:
831        shared_repos = get_share_in_repo_list(username, org_id)
832    except SearpcError as e:
833        util.warn("Failed to list shared repos: %s" % e.msg)
834
835    for srepo in shared_repos:
836        if srepo:
837            addRepo(srepo)
838            pass
839
840    try:
841        repos = get_group_repos(username, org_id)
842    except SearpcError as e:
843        util.warn("Failed to get groups for %s" % username)
844    for grepo in repos:
845        if grepo:
846            addRepo(grepo)
847
848    for prepo in list_inner_pub_repos(username, org_id, is_guest):
849        if prepo:
850            addRepo(prepo)
851
852    return all_repos.values()
853
854def get_group_repos(username, org_id):
855    if org_id:
856        return seafile_api.get_org_group_repos_by_user(username, org_id)
857    else:
858        return seafile_api.get_group_repos_by_user(username)
859
860def get_owned_repos(username, org_id):
861    if org_id:
862        return seafile_api.get_org_owned_repo_list(org_id, username)
863    else:
864        return seafile_api.get_owned_repo_list(username)
865
866def get_share_in_repo_list(username, org_id):
867    """List share in repos.
868    """
869    if org_id:
870        repo_list = seafile_api.get_org_share_in_repo_list(org_id, username,
871                                                           -1, -1)
872    else:
873        repo_list = seafile_api.get_share_in_repo_list(username, -1, -1)
874
875    return repo_list
876
877def list_inner_pub_repos(username, org_id, is_guest):
878    if is_guest:
879        return []
880
881    if org_id:
882        return seafile_api.list_org_inner_pub_repos(org_id)
883
884    return seafile_api.get_inner_pub_repo_list()
885