1# Copyright (c) 2012-2016 Seafile Ltd. 2import os 3import stat 4import logging 5import posixpath 6 7from rest_framework.authentication import SessionAuthentication 8from rest_framework.permissions import IsAuthenticated 9from rest_framework.response import Response 10from rest_framework.views import APIView 11from rest_framework import status 12from django.utils.http import urlquote 13 14from seahub.api2.throttling import UserRateThrottle 15from seahub.api2.authentication import TokenAuthentication 16from seahub.api2.utils import api_error, to_python_boolean 17from seahub.api2.views import get_dir_file_recursively 18 19from seahub.thumbnail.utils import get_thumbnail_src 20from seahub.views import check_folder_permission 21from seahub.utils import check_filename_with_rename, is_valid_dirent_name, \ 22 normalize_dir_path, is_pro_version, FILEEXT_TYPE_MAP 23from seahub.utils.timeutils import timestamp_to_isoformat_timestr 24from seahub.utils.file_tags import get_files_tags_in_dir 25from seahub.utils.file_types import IMAGE, VIDEO, XMIND 26from seahub.base.models import UserStarredFiles 27from seahub.base.templatetags.seahub_tags import email2nickname, \ 28 email2contact_email 29 30from seahub.settings import ENABLE_VIDEO_THUMBNAIL, \ 31 THUMBNAIL_ROOT 32 33from seaserv import seafile_api 34from pysearpc import SearpcError 35 36logger = logging.getLogger(__name__) 37 38 39def get_dir_file_info_list(username, request_type, repo_obj, parent_dir, 40 with_thumbnail, thumbnail_size): 41 42 repo_id = repo_obj.id 43 dir_info_list = [] 44 file_info_list = [] 45 46 # get dirent(folder and file) list 47 parent_dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir) 48 dir_file_list = seafile_api.list_dir_with_perm(repo_id, 49 parent_dir, parent_dir_id, username, -1, -1) 50 51 try: 52 starred_items = UserStarredFiles.objects.filter(email=username, 53 repo_id=repo_id, path__startswith=parent_dir, org_id=-1) 54 starred_item_path_list = [f.path.rstrip('/') for f in starred_items] 55 except Exception as e: 56 logger.error(e) 57 starred_item_path_list = [] 58 59 # only get dir info list 60 if not request_type or request_type == 'd': 61 dir_list = [dirent for dirent in dir_file_list if stat.S_ISDIR(dirent.mode)] 62 for dirent in dir_list: 63 dir_info = {} 64 dir_info["type"] = "dir" 65 dir_info["id"] = dirent.obj_id 66 dir_info["name"] = dirent.obj_name 67 dir_info["mtime"] = dirent.mtime 68 dir_info["permission"] = dirent.permission 69 dir_info["parent_dir"] = parent_dir 70 dir_info_list.append(dir_info) 71 72 # get star info 73 dir_info['starred'] = False 74 dir_path = posixpath.join(parent_dir, dirent.obj_name) 75 if dir_path.rstrip('/') in starred_item_path_list: 76 dir_info['starred'] = True 77 78 # only get file info list 79 if not request_type or request_type == 'f': 80 81 file_list = [dirent for dirent in dir_file_list if not stat.S_ISDIR(dirent.mode)] 82 83 # Use dict to reduce memcache fetch cost in large for-loop. 84 nickname_dict = {} 85 contact_email_dict = {} 86 modifier_set = {x.modifier for x in file_list} 87 lock_owner_set = {x.lock_owner for x in file_list} 88 for e in modifier_set | lock_owner_set: 89 if e not in nickname_dict: 90 nickname_dict[e] = email2nickname(e) 91 if e not in contact_email_dict: 92 contact_email_dict[e] = email2contact_email(e) 93 94 try: 95 files_tags_in_dir = get_files_tags_in_dir(repo_id, parent_dir) 96 except Exception as e: 97 logger.error(e) 98 files_tags_in_dir = {} 99 100 for dirent in file_list: 101 102 file_name = dirent.obj_name 103 file_path = posixpath.join(parent_dir, file_name) 104 file_obj_id = dirent.obj_id 105 106 file_info = {} 107 file_info["type"] = "file" 108 file_info["id"] = file_obj_id 109 file_info["name"] = file_name 110 file_info["mtime"] = dirent.mtime 111 file_info["permission"] = dirent.permission 112 file_info["parent_dir"] = parent_dir 113 file_info["size"] = dirent.size 114 115 modifier_email = dirent.modifier 116 file_info['modifier_email'] = modifier_email 117 file_info['modifier_name'] = nickname_dict.get(modifier_email, '') 118 file_info['modifier_contact_email'] = contact_email_dict.get(modifier_email, '') 119 120 # get lock info 121 if is_pro_version(): 122 file_info["is_locked"] = dirent.is_locked 123 file_info["lock_time"] = dirent.lock_time 124 125 lock_owner_email = dirent.lock_owner or '' 126 file_info["lock_owner"] = lock_owner_email 127 file_info['lock_owner_name'] = nickname_dict.get(lock_owner_email, '') 128 file_info['lock_owner_contact_email'] = contact_email_dict.get(lock_owner_email, '') 129 130 if username == lock_owner_email: 131 file_info["locked_by_me"] = True 132 else: 133 file_info["locked_by_me"] = False 134 135 # get star info 136 file_info['starred'] = False 137 if file_path.rstrip('/') in starred_item_path_list: 138 file_info['starred'] = True 139 140 # get tag info 141 file_tags = files_tags_in_dir.get(file_name, []) 142 if file_tags: 143 file_info['file_tags'] = [] 144 for file_tag in file_tags: 145 file_info['file_tags'].append(file_tag) 146 147 # get thumbnail info 148 if with_thumbnail and not repo_obj.encrypted: 149 150 # used for providing a way to determine 151 # if send a request to create thumbnail. 152 153 fileExt = os.path.splitext(file_name)[1][1:].lower() 154 file_type = FILEEXT_TYPE_MAP.get(fileExt) 155 156 if file_type in (IMAGE, XMIND) or \ 157 file_type == VIDEO and ENABLE_VIDEO_THUMBNAIL: 158 159 # if thumbnail has already been created, return its src. 160 # Then web browser will use this src to get thumbnail instead of 161 # recreating it. 162 thumbnail_file_path = os.path.join(THUMBNAIL_ROOT, 163 str(thumbnail_size), file_obj_id) 164 if os.path.exists(thumbnail_file_path): 165 src = get_thumbnail_src(repo_id, thumbnail_size, file_path) 166 file_info['encoded_thumbnail_src'] = urlquote(src) 167 168 file_info_list.append(file_info) 169 170 dir_info_list.sort(key=lambda x: x['name'].lower()) 171 file_info_list.sort(key=lambda x: x['name'].lower()) 172 173 return dir_info_list, file_info_list 174 175 176class DirView(APIView): 177 """ 178 Support uniform interface for directory operations, including 179 create/delete/rename/list, etc. 180 """ 181 authentication_classes = (TokenAuthentication, SessionAuthentication) 182 permission_classes = (IsAuthenticated, ) 183 throttle_classes = (UserRateThrottle, ) 184 185 def get_dir_info(self, repo_id, dir_path): 186 187 dir_obj = seafile_api.get_dirent_by_path(repo_id, dir_path) 188 dir_info = { 189 'type': 'dir', 190 'repo_id': repo_id, 191 'parent_dir': os.path.dirname(dir_path.rstrip('/')), 192 'obj_name': dir_obj.obj_name if dir_obj else '', 193 'obj_id': dir_obj.obj_id if dir_obj else '', 194 'mtime': timestamp_to_isoformat_timestr(dir_obj.mtime) if dir_obj else '', 195 } 196 197 return dir_info 198 199 def get(self, request, repo_id, format=None): 200 """ Get sub dirent list info. 201 202 Permission checking: 203 1. user with either 'r' or 'rw' permission. 204 """ 205 206 # argument check 207 recursive = request.GET.get('recursive', '0') 208 if recursive not in ('1', '0'): 209 error_msg = "If you want to get recursive dir entries, you should set 'recursive' argument as '1'." 210 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 211 212 request_type = request.GET.get('t', '') 213 if request_type and request_type not in ('f', 'd'): 214 error_msg = "'t'(type) should be 'f' or 'd'." 215 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 216 217 with_thumbnail = request.GET.get('with_thumbnail', 'false') 218 if with_thumbnail not in ('true', 'false'): 219 error_msg = 'with_thumbnail invalid.' 220 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 221 222 with_thumbnail = to_python_boolean(with_thumbnail) 223 thumbnail_size = request.GET.get('thumbnail_size', 48) 224 try: 225 thumbnail_size = int(thumbnail_size) 226 except ValueError: 227 error_msg = 'thumbnail_size invalid.' 228 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 229 230 with_parents = request.GET.get('with_parents', 'false') 231 if with_parents not in ('true', 'false'): 232 error_msg = 'with_parents invalid.' 233 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 234 235 with_parents = to_python_boolean(with_parents) 236 237 # recource check 238 repo = seafile_api.get_repo(repo_id) 239 if not repo: 240 error_msg = 'Library %s not found.' % repo_id 241 return api_error(status.HTTP_404_NOT_FOUND, error_msg) 242 243 parent_dir = request.GET.get('p', '/') 244 parent_dir = normalize_dir_path(parent_dir) 245 246 dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir) 247 if not dir_id: 248 error_msg = 'Folder %s not found.' % parent_dir 249 return api_error(status.HTTP_404_NOT_FOUND, error_msg) 250 251 # permission check 252 permission = check_folder_permission(request, repo_id, parent_dir) 253 if not permission: 254 error_msg = 'Permission denied.' 255 return api_error(status.HTTP_403_FORBIDDEN, error_msg) 256 257 # get dir/file list recursively 258 username = request.user.username 259 if recursive == '1': 260 261 try: 262 dir_file_info_list = get_dir_file_recursively(username, repo_id, 263 parent_dir, []) 264 except Exception as e: 265 logger.error(e) 266 error_msg = 'Internal Server Error' 267 return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) 268 269 response_dict = {} 270 response_dict['dirent_list'] = [] 271 272 if request_type == 'f': 273 for item in dir_file_info_list: 274 if item['type'] == 'file': 275 response_dict['dirent_list'].append(item) 276 elif request_type == 'd': 277 for item in dir_file_info_list: 278 if item['type'] == 'dir': 279 response_dict['dirent_list'].append(item) 280 else: 281 response_dict['dirent_list'] = dir_file_info_list 282 283 return Response(response_dict) 284 285 parent_dir_list = [] 286 if not with_parents: 287 # only return dirent list in current parent folder 288 parent_dir_list.append(parent_dir) 289 else: 290 # if value of 'p' parameter is '/a/b/c' add with_parents's is 'true' 291 # then return dirent list in '/', '/a', '/a/b' and '/a/b/c'. 292 if parent_dir == '/': 293 parent_dir_list.append(parent_dir) 294 else: 295 tmp_parent_dir = '/' 296 parent_dir_list.append(tmp_parent_dir) 297 for folder_name in parent_dir.strip('/').split('/'): 298 tmp_parent_dir = posixpath.join(tmp_parent_dir, folder_name) 299 tmp_parent_dir = normalize_dir_path(tmp_parent_dir) 300 parent_dir_list.append(tmp_parent_dir) 301 302 all_dir_info_list = [] 303 all_file_info_list = [] 304 305 try: 306 for parent_dir in parent_dir_list: 307 # get dir file info list 308 dir_info_list, file_info_list = get_dir_file_info_list(username, 309 request_type, repo, parent_dir, with_thumbnail, thumbnail_size) 310 all_dir_info_list.extend(dir_info_list) 311 all_file_info_list.extend(file_info_list) 312 except Exception as e: 313 logger.error(e) 314 error_msg = 'Internal Server Error' 315 return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) 316 317 response_dict = {} 318 response_dict["user_perm"] = permission 319 response_dict["dir_id"] = dir_id 320 321 if request_type == 'f': 322 response_dict['dirent_list'] = all_file_info_list 323 elif request_type == 'd': 324 response_dict['dirent_list'] = all_dir_info_list 325 else: 326 response_dict['dirent_list'] = all_dir_info_list + all_file_info_list 327 328 return Response(response_dict) 329 330 def post(self, request, repo_id, format=None): 331 """ Create, rename, revert dir. 332 333 Permission checking: 334 1. create: user with 'rw' permission for current dir's parent dir; 335 2. rename: user with 'rw' permission for current dir; 336 3. revert: user with 'rw' permission for current dir's parent dir; 337 """ 338 339 # argument check 340 path = request.GET.get('p', None) 341 if not path or path[0] != '/': 342 error_msg = 'p invalid.' 343 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 344 345 if path == '/': 346 error_msg = 'Can not operate root dir.' 347 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 348 349 operation = request.data.get('operation', None) 350 if not operation: 351 error_msg = 'operation invalid.' 352 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 353 354 operation = operation.lower() 355 if operation not in ('mkdir', 'rename', 'revert'): 356 error_msg = "operation can only be 'mkdir', 'rename' or 'revert'." 357 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 358 359 # resource check 360 repo = seafile_api.get_repo(repo_id) 361 if not repo: 362 error_msg = 'Library %s not found.' % repo_id 363 return api_error(status.HTTP_404_NOT_FOUND, error_msg) 364 365 path = path.rstrip('/') 366 username = request.user.username 367 parent_dir = os.path.dirname(path) 368 if operation == 'mkdir': 369 # resource check 370 parent_dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir) 371 if not parent_dir_id: 372 error_msg = 'Folder %s not found.' % parent_dir 373 return api_error(status.HTTP_404_NOT_FOUND, error_msg) 374 375 # permission check 376 if check_folder_permission(request, repo_id, parent_dir) != 'rw': 377 error_msg = 'Permission denied.' 378 return api_error(status.HTTP_403_FORBIDDEN, error_msg) 379 380 new_dir_name = os.path.basename(path) 381 382 if not is_valid_dirent_name(new_dir_name): 383 return api_error(status.HTTP_400_BAD_REQUEST, 384 'name invalid.') 385 386 retry_count = 0 387 while retry_count < 10: 388 new_dir_name = check_filename_with_rename(repo_id, 389 parent_dir, new_dir_name) 390 try: 391 seafile_api.post_dir(repo_id, 392 parent_dir, new_dir_name, username) 393 break 394 except SearpcError as e: 395 if str(e) == 'file already exists': 396 retry_count += 1 397 else: 398 logger.error(e) 399 error_msg = 'Internal Server Error' 400 return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 401 error_msg) 402 403 new_dir_path = posixpath.join(parent_dir, new_dir_name) 404 dir_info = self.get_dir_info(repo_id, new_dir_path) 405 resp = Response(dir_info) 406 407 return resp 408 409 if operation == 'rename': 410 # resource check 411 dir_id = seafile_api.get_dir_id_by_path(repo_id, path) 412 if not dir_id: 413 error_msg = 'Folder %s not found.' % path 414 return api_error(status.HTTP_404_NOT_FOUND, error_msg) 415 416 # permission check 417 if check_folder_permission(request, repo_id, path) != 'rw': 418 error_msg = 'Permission denied.' 419 return api_error(status.HTTP_403_FORBIDDEN, error_msg) 420 421 old_dir_name = os.path.basename(path) 422 new_dir_name = request.data.get('newname', None) 423 424 if not new_dir_name: 425 error_msg = 'newname invalid.' 426 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 427 428 if not is_valid_dirent_name(new_dir_name): 429 return api_error(status.HTTP_400_BAD_REQUEST, 430 'name invalid.') 431 432 if new_dir_name == old_dir_name: 433 dir_info = self.get_dir_info(repo_id, path) 434 resp = Response(dir_info) 435 return resp 436 437 try: 438 # rename duplicate name 439 new_dir_name = check_filename_with_rename(repo_id, parent_dir, new_dir_name) 440 # rename dir 441 seafile_api.rename_file(repo_id, parent_dir, old_dir_name, 442 new_dir_name, username) 443 444 new_dir_path = posixpath.join(parent_dir, new_dir_name) 445 dir_info = self.get_dir_info(repo_id, new_dir_path) 446 resp = Response(dir_info) 447 return resp 448 except SearpcError as e: 449 logger.error(e) 450 error_msg = 'Internal Server Error' 451 return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) 452 453 if operation == 'revert': 454 commit_id = request.data.get('commit_id', None) 455 if not commit_id: 456 error_msg = 'commit_id invalid.' 457 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 458 459 if seafile_api.get_dir_id_by_path(repo_id, path): 460 # dir exists in repo 461 if check_folder_permission(request, repo_id, path) != 'rw': 462 error_msg = 'Permission denied.' 463 return api_error(status.HTTP_403_FORBIDDEN, error_msg) 464 else: 465 # dir NOT exists in repo 466 if check_folder_permission(request, repo_id, '/') != 'rw': 467 error_msg = 'Permission denied.' 468 return api_error(status.HTTP_403_FORBIDDEN, error_msg) 469 470 try: 471 seafile_api.revert_dir(repo_id, commit_id, path, username) 472 except Exception as e: 473 logger.error(e) 474 error_msg = 'Internal Server Error' 475 return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) 476 477 return Response({'success': True}) 478 479 def delete(self, request, repo_id, format=None): 480 """ Delete dir. 481 482 Permission checking: 483 1. user with 'rw' permission. 484 """ 485 486 # argument check 487 path = request.GET.get('p', None) 488 if not path: 489 error_msg = 'p invalid.' 490 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 491 492 if path == '/': 493 error_msg = 'Can not delete root path.' 494 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 495 496 # resource check 497 dir_id = seafile_api.get_dir_id_by_path(repo_id, path) 498 if not dir_id: 499 error_msg = 'Folder %s not found.' % path 500 return api_error(status.HTTP_404_NOT_FOUND, error_msg) 501 502 repo = seafile_api.get_repo(repo_id) 503 if not repo: 504 error_msg = 'Library %s not found.' % repo_id 505 return api_error(status.HTTP_404_NOT_FOUND, error_msg) 506 507 # permission check 508 if check_folder_permission(request, repo_id, path) != 'rw': 509 error_msg = 'Permission denied.' 510 return api_error(status.HTTP_403_FORBIDDEN, error_msg) 511 512 if path[-1] == '/': 513 path = path[:-1] 514 515 path = path.rstrip('/') 516 username = request.user.username 517 parent_dir = os.path.dirname(path) 518 dir_name = os.path.basename(path) 519 try: 520 seafile_api.del_file(repo_id, parent_dir, dir_name, username) 521 except SearpcError as e: 522 logger.error(e) 523 error_msg = 'Internal Server Error' 524 return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) 525 526 return Response({'success': True}) 527 528 529class DirDetailView(APIView): 530 """ Get detailed info of a folder. 531 """ 532 authentication_classes = (TokenAuthentication, SessionAuthentication) 533 permission_classes = (IsAuthenticated, ) 534 throttle_classes = (UserRateThrottle, ) 535 536 def get(self, request, repo_id): 537 """ Get dir info. 538 539 Permission checking: 540 1. user with either 'r' or 'rw' permission. 541 """ 542 543 # parameter check 544 path = request.GET.get('path', None) 545 if not path: 546 error_msg = 'path invalid.' 547 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 548 549 path = normalize_dir_path(path) 550 if path == '/': 551 error_msg = 'path invalid.' 552 return api_error(status.HTTP_400_BAD_REQUEST, error_msg) 553 554 # resource check 555 repo = seafile_api.get_repo(repo_id) 556 if not repo: 557 error_msg = 'Library %s not found.' % repo_id 558 return api_error(status.HTTP_404_NOT_FOUND, error_msg) 559 560 dir_id = seafile_api.get_dir_id_by_path(repo_id, path) 561 if not dir_id: 562 error_msg = 'Folder %s not found.' % path 563 return api_error(status.HTTP_404_NOT_FOUND, error_msg) 564 565 # permission check 566 permission = check_folder_permission(request, repo_id, path) 567 if not permission: 568 error_msg = 'Permission denied.' 569 return api_error(status.HTTP_403_FORBIDDEN, error_msg) 570 571 dir_obj = seafile_api.get_dirent_by_path(repo_id, path) 572 dir_info = { 573 'repo_id': repo_id, 574 'path': path, 575 'name': dir_obj.obj_name if dir_obj else '', 576 'mtime': timestamp_to_isoformat_timestr(dir_obj.mtime) if dir_obj else '', 577 'permission': permission, 578 } 579 580 return Response(dir_info) 581