diff --git a/wsgidav/seafile_dav_provider.py b/wsgidav/seafile_dav_provider.py index 0b36b99..9cae67c 100644 --- a/wsgidav/seafile_dav_provider.py +++ b/wsgidav/seafile_dav_provider.py @@ -12,7 +12,7 @@ import tempfile -from seaserv import seafile_api, CALC_SHARE_USAGE +from seaserv import seafile_api from pysearpc import SearpcError from seafobj import commit_mgr, fs_mgr from seafobj.fs import SeafFile, SeafDir @@ -28,14 +28,17 @@ INFINITE_QUOTA = -2 + def sort_repo_list(repos): - return sorted(repos, key = lambda r: r.id) + 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): @@ -66,7 +69,7 @@ def read(self, size): self.block = None self.block_offset = 0 else: - ret += self.block[self.block_offset:self.block_offset+remain] + ret += self.block[self.block_offset:self.block_offset + remain] self.block_offset += remain remain = 0 @@ -84,7 +87,7 @@ def seek(self, pos): self.block_offset = 0 current_pos = pos - if current_pos == 0: + if current_pos == 0: return with self.block_map_lock: @@ -94,9 +97,9 @@ def seek(self, pos): 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 = 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 @@ -109,10 +112,9 @@ def seek(self, pos): self.block_offset = current_pos current_pos = 0 -#=============================================================================== -# SeafileResource -#=============================================================================== + 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 @@ -130,19 +132,24 @@ def __init__(self, path, repo, rel_path, obj, environ, block_map={}, block_map_l # 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 + # (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 int(time.time()) + return None + def get_display_name(self): return self.name + def get_etag(self): return self.obj.obj_id @@ -172,8 +179,9 @@ def get_last_modified(self): def support_etag(self): return True + def support_ranges(self): - return True + return True def get_content(self): """Open content as a stream for reading. @@ -239,7 +247,7 @@ def end_write(self, with_errors, isnewfile=True): 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) + self.username, None) except SearpcError as e: raise DAVError(HTTP_INTERNAL_ERROR, e.msg) finally: @@ -262,7 +270,7 @@ def handle_delete(self): return True parent, filename = os.path.split(self.rel_path) - seafile_api.del_file(self.repo.id, parent, '[\"'+filename+'\"]', self.username) + seafile_api.del_file(self.repo.id, parent, '[\"' + filename + '\"]', self.username) except SearpcError as e: raise DAVError(HTTP_INTERNAL_ERROR, e.msg) @@ -294,11 +302,13 @@ def handle_move(self, dest_path): # 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 != None: - seafile_api.del_file(dest_repo.id, dest_dir, '[\"'+dest_file+'\"]', self.username) + 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) + 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) @@ -330,17 +340,17 @@ def handle_copy(self, dest_path, depth_infinity): 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) + 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 -#=============================================================================== -# SeafDirResource -#=============================================================================== + class SeafDirResource(DAVCollection): + def __init__(self, path, repo, rel_path, obj, environ): super(SeafDirResource, self).__init__(path, environ) self.repo = repo @@ -353,17 +363,27 @@ def __init__(self, path, repo, rel_path, obj, environ): # Getter methods for standard live properties def get_creation_date(self): -# return int(time.time()) + # 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): -# return int(time.time()) - return None + 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) @@ -385,9 +405,11 @@ def get_member(self, name): raise DAVError(HTTP_NOT_FOUND) if isinstance(member, SeafFile): - return SeafileResource(member_path, self.repo, member_rel_path, member, self.environ) + 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) + return SeafDirResource(member_path, self.repo, member_rel_path, + member, self.environ) def get_member_list(self): member_list = [] @@ -431,7 +453,7 @@ def create_empty_resource(self, name): See DAVResource.createEmptyResource() """ - assert not "/" in name + assert "/" not in name if self.provider.readonly: raise DAVError(HTTP_FORBIDDEN) @@ -475,7 +497,7 @@ def create_collection(self, name): See DAVResource.createCollection() """ - assert not "/" in name + assert "/" not in name if self.provider.readonly: raise DAVError(HTTP_FORBIDDEN) @@ -504,7 +526,7 @@ def handle_delete(self): if not filename: raise DAVError(HTTP_BAD_REQUEST) - seafile_api.del_file(self.repo.id, parent, '[\"'+filename+'\"]', self.username) + seafile_api.del_file(self.repo.id, parent, '[\"' + filename + '\"]', self.username) except SearpcError as e: raise DAVError(HTTP_INTERNAL_ERROR, e.msg) @@ -537,8 +559,9 @@ def handle_move(self, dest_path): 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) + 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) @@ -571,14 +594,17 @@ def handle_copy(self, dest_path, depth_infinity): 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) + 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 @@ -588,16 +614,20 @@ def __init__(self, username, environ, show_repo_id): # Getter methods for standard live properties def get_creation_date(self): -# return int(time.time()) + # 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 int(time.time()) return None def get_member_names(self): @@ -662,7 +692,7 @@ def get_member_list(self): def _createRootRes(self, repo, name): obj = get_repo_root_seafdir(repo) - return SeafDirResource("/"+name, repo, "", obj, self.environ) + return SeafDirResource("/" + name, repo, "", obj, self.environ) # --- Read / write --------------------------------------------------------- @@ -682,9 +712,6 @@ def handle_copy(self, dest_path, depth_infinity): raise DAVError(HTTP_FORBIDDEN) -#=============================================================================== -# SeafileProvider -#=============================================================================== class SeafileProvider(DAVProvider): def __init__(self, show_repo_id, readonly=False): @@ -702,7 +729,7 @@ 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: + 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]) @@ -715,7 +742,6 @@ def __repr__(self): rw = "Read-Only" return "%s for Seafile (%s)" % (self.__class__.__name__, rw) - def get_resource_inst(self, path, environ): """Return info dictionary for path. @@ -749,6 +775,7 @@ def get_resource_inst(self, path, environ): 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("/") @@ -768,7 +795,7 @@ def resolvePath(path, username, org_id, is_guest): parent = obj obj = parent.lookup(segment) - if not obj or (isinstance(obj, SeafFile) and i != n_segs-1): + if not obj or (isinstance(obj, SeafFile) and i != n_segs - 1): raise DAVError(HTTP_NOT_FOUND) rel_path += "/" + segment @@ -779,6 +806,7 @@ def resolvePath(path, username, org_id, is_guest): return (repo, rel_path, obj) + def resolveRepoPath(repo, path): path = unicodedata.normalize('NFC', path) segments = path.strip("/").split("/") @@ -790,17 +818,19 @@ def resolveRepoPath(repo, path): for segment in segments: obj = obj.lookup(segment) - if not obj or (isinstance(obj, SeafFile) and i != n_segs-1): + 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) @@ -820,6 +850,7 @@ def getRepoByName(repo_name, username, org_id, is_guest): return ret_repo + def getAccessibleRepos(username, org_id, is_guest): all_repos = {} @@ -853,10 +884,10 @@ def addRepo(repo): try: repos = get_group_repos(username, org_id) - except SearpcError as e: + except SearpcError: util.warn("Failed to get groups for %s" % username) for grepo in repos: - if grepo: + if grepo: addRepo(grepo) for prepo in list_inner_pub_repos(username, org_id, is_guest): @@ -865,18 +896,21 @@ def addRepo(repo): return all_repos.values() + def get_group_repos(username, org_id): if org_id: return seafile_api.get_org_group_repos_by_user(username, org_id) else: return seafile_api.get_group_repos_by_user(username) + def get_owned_repos(username, org_id): if org_id: return seafile_api.get_org_owned_repo_list(org_id, username) else: return seafile_api.get_owned_repo_list(username) + def get_share_in_repo_list(username, org_id): """List share in repos. """ @@ -888,6 +922,7 @@ def get_share_in_repo_list(username, org_id): return repo_list + def list_inner_pub_repos(username, org_id, is_guest): if is_guest: return []