def link_irods_file_to_django(resource, filepath): """ Link a newly created irods file to Django resource model :param filepath: full path to file """ # link the newly created file (**filepath**) to Django resource model b_add_file = False # TODO: folder is an abstract concept... utilize short_path for whole API if resource: folder, base = ResourceFile.resource_path_is_acceptable(resource, filepath, test_exists=False) try: ResourceFile.get(resource=resource, file=base, folder=folder) except ObjectDoesNotExist: # this does not copy the file from anywhere; it must exist already ResourceFile.create(resource=resource, file=base, folder=folder) b_add_file = True if b_add_file: file_format_type = get_file_mime_type(filepath) if file_format_type not in [mime.value for mime in resource.metadata.formats.all()]: resource.metadata.create_element('format', value=file_format_type) # this should assign a logical file object to this new file # if this resource supports logical file resource.set_default_logical_file()
def rename_irods_file_or_folder_in_django(resource, src_name, tgt_name): """ Rename file in Django DB after the file is renamed in Django side :param resource: the BaseResource object representing a HydroShare resource :param src_name: the file or folder full path name to be renamed :param tgt_name: the file or folder full path name to be renamed to :return: Note: the need to copy and recreate the file object was made unnecessary by the ResourceFile.set_storage_path routine, which always sets that correctly. Thus it is possible to move without copying. Thus, logical file relationships are preserved and no longer need adjustment. """ # checks src_name as a side effect. folder, base = ResourceFile.resource_path_is_acceptable(resource, src_name, test_exists=False) try: res_file_obj = ResourceFile.get(resource=resource, file=base, folder=folder) # checks tgt_name as a side effect. ResourceFile.resource_path_is_acceptable(resource, tgt_name, test_exists=True) res_file_obj.set_storage_path(tgt_name) except ObjectDoesNotExist: # src_name and tgt_name are folder names res_file_objs = ResourceFile.list_folder(resource, src_name) for fobj in res_file_objs: src_path = fobj.storage_path # naively replace src_name with tgt_name new_path = src_path.replace(src_name, tgt_name, 1) fobj.set_storage_path(new_path)
def data_store_rename_file_or_folder(request, pk=None): """ Rename one file or folder in a resource file hierarchy. It is invoked by an AJAX call :param request: a REST request :param pk: the short_id of a resource to modify, from REST URL. This is invoked by an AJAX call in the UI. It returns a json object that has the relative path of the target file or folder that has been renamed. The AJAX request must be a POST request with input data for source_path and target_path, where source_path and target_path are the relative paths for the source and target file or folder. This routine is **specifically** targeted at validating requests from the UI. Thus it is much more limiting than a general purpose REST responder. """ pk = request.POST.get('res_id', pk) if pk is None: return HttpResponse('Bad request - resource id is not included', status=status.HTTP_400_BAD_REQUEST) pk = str(pk).strip() try: resource, _, user = authorize( request, pk, needed_permission=ACTION_TO_AUTHORIZE.EDIT_RESOURCE) except NotFound: return HttpResponse('Bad request - resource not found', status=status.HTTP_400_BAD_REQUEST) except PermissionDenied: return HttpResponse('Permission denied', status=status.HTTP_401_UNAUTHORIZED) src_path = resolve_request(request).get('source_path', None) tgt_path = resolve_request(request).get('target_path', None) if src_path is None or tgt_path is None: return HttpResponse('Source or target name is not specified', status=status.HTTP_400_BAD_REQUEST) if not src_path or not tgt_path: return HttpResponse('Source or target name is empty', status=status.HTTP_400_BAD_REQUEST) src_path = str(src_path).strip() tgt_path = str(tgt_path).strip() src_folder, src_base = os.path.split(src_path) tgt_folder, tgt_base = os.path.split(tgt_path) if src_folder != tgt_folder: return HttpResponse( 'Rename: Source and target names must be in same folder', status=status.HTTP_400_BAD_REQUEST) if not src_path.startswith('data/contents/'): return HttpResponse( 'Rename: Source path must start with data/contents/', status=status.HTTP_400_BAD_REQUEST) if src_path.find('/../') >= 0 or src_path.endswith('/..'): return HttpResponse('Rename: Source path cannot contain /../', status=status.HTTP_400_BAD_REQUEST) if not tgt_path.startswith('data/contents/'): return HttpResponse( 'Rename: Target path must start with data/contents/', status=status.HTTP_400_BAD_REQUEST) if tgt_path.find('/../') >= 0 or tgt_path.endswith('/..'): return HttpResponse('Rename: Target path cannot contain /../', status=status.HTTP_400_BAD_REQUEST) istorage = resource.get_irods_storage() # protect against stale data botches: source files should exist src_storage_path = os.path.join(resource.root_path, src_path) try: folder, base = ResourceFile.resource_path_is_acceptable( resource, src_storage_path, test_exists=True) except ValidationError: return HttpResponse('Object to be renamed does not exist', status=status.HTTP_400_BAD_REQUEST) if not irods_path_is_directory(istorage, src_storage_path): try: # Django record should exist for each file ResourceFile.get(resource, base, folder=folder) except ResourceFile.DoesNotExist: return HttpResponse('Object to be renamed does not exist', status=status.HTTP_400_BAD_REQUEST) # check that the target doesn't exist tgt_storage_path = os.path.join(resource.root_path, tgt_path) tgt_short_path = tgt_path[len('data/contents/'):] if istorage.exists(tgt_storage_path): return HttpResponse('Desired name is already in use', status=status.HTTP_400_BAD_REQUEST) try: folder, base = ResourceFile.resource_path_is_acceptable( resource, tgt_storage_path, test_exists=False) except ValidationError: return HttpResponse( 'Poorly structured desired name {}'.format(tgt_short_path), status=status.HTTP_400_BAD_REQUEST) try: ResourceFile.get(resource, base, folder=tgt_short_path) return HttpResponse( 'Desired name {} is already in use'.format(tgt_short_path), status=status.HTTP_400_BAD_REQUEST) except ResourceFile.DoesNotExist: pass # correct response try: rename_file_or_folder(user, pk, src_path, tgt_path) except SessionException as ex: return HttpResponse(ex.stderr, status=status.HTTP_500_INTERNAL_SERVER_ERROR) except DRF_ValidationError as ex: return HttpResponse(ex.detail, status=status.HTTP_400_BAD_REQUEST) return_object = {'target_rel_path': tgt_path} return HttpResponse(json.dumps(return_object), content_type='application/json')
def data_store_move_to_folder(request, pk=None): """ Move a list of files and/or folders to another folder in a resource file hierarchy. :param request: a REST request :param pk: the short_id of a resource to modify, from REST URL. It is invoked by an AJAX call and returns a json object that has the relative paths of the target files or folders to which files have been moved. The AJAX request must be a POST request with input data passed in for source_paths and target_path where source_paths and target_path are the relative paths for the source and target file or folder in the res_id file directory. This routine is **specifically** targeted at validating requests from the UI. Thus it is much more limiting than a general purpose REST responder. """ pk = request.POST.get('res_id', pk) if pk is None: return HttpResponse('Bad request - resource id is not included', status=status.HTTP_400_BAD_REQUEST) # whether to treat request as atomic: skip overwrites for valid request atomic = request.POST.get('atomic', 'false') == 'true' # False by default pk = str(pk).strip() try: resource, _, user = authorize( request, pk, needed_permission=ACTION_TO_AUTHORIZE.EDIT_RESOURCE) except NotFound: return HttpResponse('Bad request - resource not found', status=status.HTTP_400_BAD_REQUEST) except PermissionDenied: return HttpResponse('Permission denied', status=status.HTTP_401_UNAUTHORIZED) tgt_path = resolve_request(request).get('target_path', None) src_paths = resolve_request(request).get('source_paths', None) if src_paths is None or tgt_path is None: return HttpResponse( 'Bad request - src_paths or tgt_path is not included', status=status.HTTP_400_BAD_REQUEST) tgt_path = str(tgt_path).strip() if not tgt_path: return HttpResponse('Target directory not specified', status=status.HTTP_400_BAD_REQUEST) # protect against common hacking attacks if not tgt_path.startswith('data/contents/'): return HttpResponse( 'Target directory path must start with data/contents/', status=status.HTTP_400_BAD_REQUEST) if tgt_path.find('/../') >= 0 or tgt_path.endswith('/..'): return HttpResponse('Bad request - tgt_path cannot contain /../', status=status.HTTP_400_BAD_REQUEST) istorage = resource.get_irods_storage() # strip trailing slashes (if any) tgt_path = tgt_path.rstrip('/') tgt_short_path = tgt_path[len('data/contents/'):] tgt_storage_path = os.path.join(resource.root_path, tgt_path) if not irods_path_is_directory(istorage, tgt_storage_path): return HttpResponse('Target of move is not an existing folder', status=status.HTTP_400_BAD_REQUEST) src_paths = json.loads(src_paths) for i in range(len(src_paths)): src_paths[i] = str(src_paths[i]).strip().rstrip('/') # protect against common hacking attacks for src_path in src_paths: if not src_path.startswith('data/contents/'): return HttpResponse( 'Paths to be moved must start with data/contents/', status=status.HTTP_400_BAD_REQUEST) if src_path.find('/../') >= 0 or src_path.endswith('/..'): return HttpResponse('Paths to be moved cannot contain /../', status=status.HTTP_400_BAD_REQUEST) valid_src_paths = [] skipped_tgt_paths = [] for src_path in src_paths: src_storage_path = os.path.join(resource.root_path, src_path) src_short_path = src_path[len('data/contents/'):] # protect against stale data botches: source files should exist try: folder, file = ResourceFile.resource_path_is_acceptable( resource, src_storage_path, test_exists=True) except ValidationError: return HttpResponse( 'Source file {} does not exist'.format(src_short_path), status=status.HTTP_400_BAD_REQUEST) if not irods_path_is_directory( istorage, src_storage_path): # there is django record try: ResourceFile.get(resource, file, folder=folder) except ResourceFile.DoesNotExist: return HttpResponse( 'Source file {} does not exist'.format(src_short_path), status=status.HTTP_400_BAD_REQUEST) # protect against inadvertent overwrite base = os.path.basename(src_storage_path) tgt_overwrite = os.path.join(tgt_storage_path, base) if not istorage.exists(tgt_overwrite): valid_src_paths.append( src_path) # partly qualified path for operation else: # skip pre-existing objects skipped_tgt_paths.append(os.path.join(tgt_short_path, base)) if skipped_tgt_paths: if atomic: message = 'move would overwrite {}'.format( ', '.join(skipped_tgt_paths)) return HttpResponse(message, status=status.HTTP_400_BAD_REQUEST) # if not atomic, then try to move the files that don't have conflicts # stop immediately on error. try: move_to_folder(user, pk, valid_src_paths, tgt_path) except SessionException as ex: return HttpResponse(ex.stderr, status=status.HTTP_500_INTERNAL_SERVER_ERROR) except DRF_ValidationError as ex: return HttpResponse(ex.detail, status=status.HTTP_400_BAD_REQUEST) return_object = {'target_rel_path': tgt_path} if skipped_tgt_paths: # add information on skipped steps message = '[Warn] skipped move to existing {}'.format( ', '.join(skipped_tgt_paths)) return_object['additional_status'] = message return HttpResponse(json.dumps(return_object), content_type='application/json')
def data_store_rename_file_or_folder(request, pk=None): """ Rename one file or folder in a resource file hierarchy. It is invoked by an AJAX call :param request: a REST request :param pk: the short_id of a resource to modify, from REST URL. This is invoked by an AJAX call in the UI. It returns a json object that has the relative path of the target file or folder that has been renamed. The AJAX request must be a POST request with input data for source_path and target_path, where source_path and target_path are the relative paths (relative to path res_id/data/contents) for the source and target file or folder. This routine is **specifically** targeted at validating requests from the UI. Thus it is much more limiting than a general purpose REST responder. """ pk = request.POST.get('res_id', pk) if pk is None: return HttpResponse('Bad request - resource id is not included', status=status.HTTP_400_BAD_REQUEST) pk = str(pk).strip() try: resource, _, user = authorize(request, pk, needed_permission=ACTION_TO_AUTHORIZE.EDIT_RESOURCE) except NotFound: return HttpResponse('Bad request - resource not found', status=status.HTTP_400_BAD_REQUEST) except PermissionDenied: return HttpResponse('Permission denied', status=status.HTTP_401_UNAUTHORIZED) src_path = resolve_request(request).get('source_path', None) tgt_path = resolve_request(request).get('target_path', None) try: src_path = _validate_path(src_path, 'src_path') tgt_path = _validate_path(tgt_path, 'tgt_path') except ValidationError as ex: return HttpResponse(ex.message, status=status.HTTP_400_BAD_REQUEST) src_folder, src_base = os.path.split(src_path) tgt_folder, tgt_base = os.path.split(tgt_path) if src_folder != tgt_folder: return HttpResponse('Rename: Source and target names must be in same folder', status=status.HTTP_400_BAD_REQUEST) istorage = resource.get_irods_storage() # protect against stale data botches: source files should exist src_storage_path = os.path.join(resource.root_path, src_path) try: folder, base = ResourceFile.resource_path_is_acceptable(resource, src_storage_path, test_exists=True) except ValidationError: return HttpResponse('Object to be renamed does not exist', status=status.HTTP_400_BAD_REQUEST) if not irods_path_is_directory(istorage, src_storage_path): try: # Django record should exist for each file ResourceFile.get(resource, base, folder=folder) except ResourceFile.DoesNotExist: return HttpResponse('Object to be renamed does not exist', status=status.HTTP_400_BAD_REQUEST) # check that the target doesn't exist tgt_storage_path = os.path.join(resource.root_path, tgt_path) tgt_short_path = tgt_path[len('data/contents/'):] if istorage.exists(tgt_storage_path): return HttpResponse('Desired name is already in use', status=status.HTTP_400_BAD_REQUEST) try: folder, base = ResourceFile.resource_path_is_acceptable(resource, tgt_storage_path, test_exists=False) except ValidationError: return HttpResponse('Poorly structured desired name {}' .format(tgt_short_path), status=status.HTTP_400_BAD_REQUEST) try: ResourceFile.get(resource, base, folder=tgt_short_path) return HttpResponse('Desired name {} is already in use' .format(tgt_short_path), status=status.HTTP_400_BAD_REQUEST) except ResourceFile.DoesNotExist: pass # correct response try: rename_file_or_folder(user, pk, src_path, tgt_path) except SessionException as ex: return HttpResponse(ex.stderr, status=status.HTTP_500_INTERNAL_SERVER_ERROR) except DRF_ValidationError as ex: return HttpResponse(ex.detail, status=status.HTTP_400_BAD_REQUEST) return_object = {'target_rel_path': tgt_path} return HttpResponse( json.dumps(return_object), content_type='application/json' )
def data_store_move_to_folder(request, pk=None): """ Move a list of files and/or folders to another folder in a resource file hierarchy. :param request: a REST request :param pk: the short_id of a resource to modify, from REST URL. It is invoked by an AJAX call and returns a json object that has the relative paths of the target files or folders to which files have been moved. The AJAX request must be a POST request with input data passed in for source_paths and target_path where source_paths and target_path are the relative paths (relative to path res_id/data/contents) for the source and target file or folder in the res_id file directory. This routine is **specifically** targeted at validating requests from the UI. Thus it is much more limiting than a general purpose REST responder. """ pk = request.POST.get('res_id', pk) if pk is None: return HttpResponse('Bad request - resource id is not included', status=status.HTTP_400_BAD_REQUEST) # whether to treat request as atomic: skip overwrites for valid request atomic = request.POST.get('atomic', 'false') == 'true' # False by default pk = str(pk).strip() try: resource, _, user = authorize(request, pk, needed_permission=ACTION_TO_AUTHORIZE.EDIT_RESOURCE) except NotFound: return HttpResponse('Bad request - resource not found', status=status.HTTP_400_BAD_REQUEST) except PermissionDenied: return HttpResponse('Permission denied', status=status.HTTP_401_UNAUTHORIZED) tgt_path = resolve_request(request).get('target_path', None) src_paths = resolve_request(request).get('source_paths', None) try: tgt_path = _validate_path(tgt_path, 'tgt_path', check_path_empty=False) except ValidationError as ex: return HttpResponse(ex.message, status=status.HTTP_400_BAD_REQUEST) istorage = resource.get_irods_storage() tgt_short_path = tgt_path[len('data/contents/'):] tgt_storage_path = os.path.join(resource.root_path, tgt_path) if not irods_path_is_directory(istorage, tgt_storage_path): return HttpResponse('Target of move is not an existing folder', status=status.HTTP_400_BAD_REQUEST) src_paths = json.loads(src_paths) # protect against common hacking attacks for index, src_path in enumerate(src_paths): try: src_paths[index] = _validate_path(src_path, 'src_paths') except ValidationError as ex: return HttpResponse(ex.message, status=status.HTTP_400_BAD_REQUEST) valid_src_paths = [] skipped_tgt_paths = [] for src_path in src_paths: src_storage_path = os.path.join(resource.root_path, src_path) src_short_path = src_path[len('data/contents/'):] # protect against stale data botches: source files should exist try: folder, file = ResourceFile.resource_path_is_acceptable(resource, src_storage_path, test_exists=True) except ValidationError: return HttpResponse('Source file {} does not exist'.format(src_short_path), status=status.HTTP_400_BAD_REQUEST) if not irods_path_is_directory(istorage, src_storage_path): # there is django record try: ResourceFile.get(resource, file, folder=folder) except ResourceFile.DoesNotExist: return HttpResponse('Source file {} does not exist'.format(src_short_path), status=status.HTTP_400_BAD_REQUEST) # protect against inadvertent overwrite base = os.path.basename(src_storage_path) tgt_overwrite = os.path.join(tgt_storage_path, base) if not istorage.exists(tgt_overwrite): valid_src_paths.append(src_path) # partly qualified path for operation else: # skip pre-existing objects skipped_tgt_paths.append(os.path.join(tgt_short_path, base)) if skipped_tgt_paths: if atomic: message = 'move would overwrite {}'.format(', '.join(skipped_tgt_paths)) return HttpResponse(message, status=status.HTTP_400_BAD_REQUEST) # if not atomic, then try to move the files that don't have conflicts # stop immediately on error. try: move_to_folder(user, pk, valid_src_paths, tgt_path) except SessionException as ex: return HttpResponse(ex.stderr, status=status.HTTP_500_INTERNAL_SERVER_ERROR) except DRF_ValidationError as ex: return HttpResponse(ex.detail, status=status.HTTP_400_BAD_REQUEST) return_object = {'target_rel_path': tgt_path} if skipped_tgt_paths: # add information on skipped steps message = '[Warn] skipped move to existing {}'.format(', '.join(skipped_tgt_paths)) return_object['additional_status'] = message return HttpResponse( json.dumps(return_object), content_type='application/json' )