def _handle_upload_request_with_potential_md5_checksum(request, file_path,
                                                       success_status_code):
    """
    Write the HTTP request body to a file and, if the HTTP Content-MD5 header is
    set, make sure the destination file has the expected checkcum

    Returns a response (with a specified HTTP status code) or an error response
    if a checksum has been provided but the destination file's is different
    """
    temp_filepath = helpers.write_request_body_to_temp_file(request)
    if 'HTTP_CONTENT_MD5' in request.META:
        md5sum = helpers.get_file_md5_checksum(temp_filepath)
        if request.META['HTTP_CONTENT_MD5'] != md5sum:
            os.remove(temp_filepath)
            return helpers.sword_error_response(
                request, 400,
                'MD5 checksum of uploaded file ({uploaded_md5sum}) does not match checksum provided in header ({header_md5sum}).'
                .format(uploaded_md5sum=md5sum,
                        header_md5sum=request.META['HTTP_CONTENT_MD5']))
        else:
            shutil.copyfile(temp_filepath, file_path)
            os.remove(temp_filepath)
            return HttpResponse(status=success_status_code)
    else:
        shutil.copyfile(temp_filepath, file_path)
        os.remove(temp_filepath)
        return HttpResponse(status=success_status_code)
def deposit_media(request, deposit):
    """
    Deposit media endpoint: list, create, update or delete files

    Example GET of files list:
      curl -v http://127.0.0.1:8000/api/v1/file/149cc29d-6472-4bcf-bee8-f8223bf60580/sword/media/

    Example PUT of file:
      curl -v -H "Content-Disposition: attachment; filename=joke.jpg" --data-binary "@joke.jpg" --request PUT http://127.0.0.1:8000/api/v1/file/9c8b4ac0-0407-4360-a10d-af6c62a48b69/sword/media/

    Example POST of file:
      curl -v -H "Content-Disposition: attachment; filename=joke.jpg" --data-binary "@joke.jpg" --request POST http://127.0.0.1:8000/api/v1/file/9c8b4ac0-0407-4360-a10d-af6c62a48b69/sword/media/

    Example POST of METS with file info:
      curl -v -H "Packaging: METS" -H "In-Progress: true" --data-binary @mets.xml --request POST http://127.0.0.1:8000/api/v1/file/9c8b4ac0-0407-4360-a10d-af6c62a48b69/sword/media/

    Example DELETE of all files:
      curl -v -XDELETE http://127.0.0.1:8000/api/v1/file/9c8b4ac0-0407-4360-a10d-af6c62a48b69/sword/media/

    Example DELETE of file:
      curl -v -XDELETE http://127.0.0.1:8000/api/v1/file/9c8b4ac0-0407-4360-a10d-af6c62a48b69/sword/media/?filename=joke.jpg
    """

    if isinstance(deposit, basestring):
        try:
            deposit = models.Package.objects.get(uuid=deposit)
        except models.Package.DoesNotExist:
            return helpers.sword_error_response(
                request, 404,
                'Deposit location {uuid} does not exist.'.format(uuid=deposit))

    if deposit.has_been_submitted_for_processing():
        return helpers.sword_error_response(
            request, 400,
            'This deposit has already been submitted for processing.')

    if request.method == 'GET':
        # TODO should this be returned in SWORD XML?
        return HttpResponse(str(os.listdir(deposit.full_path)))
    elif request.method == 'PUT':
        # replace a file in the deposit
        return _handle_adding_to_or_replacing_file_in_deposit(
            request, deposit, replace_file=True)
    elif request.method == 'POST':
        # Allow async batch upload via METS XML body content
        if 'HTTP_PACKAGING' in request.META and request.META[
                'HTTP_PACKAGING'] == 'METS':
            # If METS XML has been sent to indicate a list of files needing downloading, handle it
            if request.body != '':
                temp_filepath = helpers.write_request_body_to_temp_file(
                    request)
                try:
                    mets_data = _parse_name_and_content_urls_from_mets_file(
                        temp_filepath)
                except etree.XMLSyntaxError as e:
                    os.unlink(temp_filepath)
                    mets_data = None

                if mets_data != None:
                    # move METS file to submission documentation directory
                    object_id = mets_data.get('object_id', 'fedora')
                    helpers.store_mets_data(temp_filepath, deposit, object_id)

                    _spawn_batch_download_and_flag_finalization_if_requested(
                        deposit, request, mets_data)
                    return _deposit_receipt_response(request, deposit, 201)
                else:
                    return helpers.sword_error_response(
                        request, 412, 'Error parsing XML.')
            else:
                return helpers.sword_error_response(
                    request, 400, 'No METS body content sent.')
        else:
            # add a file to the deposit
            return _handle_adding_to_or_replacing_file_in_deposit(
                request, deposit)
    elif request.method == 'DELETE':
        filename = request.GET.get('filename', '')
        if filename != '':
            # Delete PackageDownloadTaskFile for this filename
            models.PackageDownloadTaskFile.objects.filter(
                task__package=deposit).filter(filename=filename).delete()
            # Delete empty PackageDownloadTasks for this deposit
            models.PackageDownloadTask.objects.filter(package=deposit).filter(
                download_file_set=None).delete()
            # Delete file
            file_path = os.path.join(deposit.full_path, filename)
            if os.path.exists(file_path):
                os.remove(file_path)
                return HttpResponse(status=204)  # No content
            else:
                return helpers.sword_error_response(
                    request, 404,
                    'The path to this file (%s) does not exist.' % (file_path))
        else:
            # Delete all PackageDownloadTaskFile and PackageDownloadTask for this deposit
            models.PackageDownloadTaskFile.objects.filter(
                task__package=deposit).delete()
            models.PackageDownloadTask.objects.filter(package=deposit).delete()
            # Delete all files
            for filename in os.listdir(deposit.full_path):
                filepath = os.path.join(deposit.full_path, filename)
                if os.path.isfile(filepath):
                    os.remove(filepath)
                elif os.path.isdir(filepath):
                    shutil.rmtree(filepath)

            return HttpResponse(status=204)  # No content
    else:
        return helpers.sword_error_response(
            request, 405,
            'This endpoint only responds to the GET, POST, PUT, and DELETE HTTP methods.'
        )
def collection(request, location):
    """
    Collection endpoint: accepts deposits, and returns current deposits.

    Example GET of collection deposit list:
      curl -v http://localhost:8000/api/v1/location/96606387-cc70-4b09-b422-a7220606488d/sword/collection/

    Example POST creation of deposit, allowing asynchronous downloading of object content URLs:
      curl -v -H "In-Progress: true" --data-binary @mets.xml --request POST http://localhost:8000/api/v1/location/96606387-cc70-4b09-b422-a7220606488d/sword/collection/

    Example POST creation of deposit, finalizing the deposit and auto-approving it:
      curl -v -H "In-Progress: false" --data-binary @mets.xml --request POST http://localhost:8000/api/v1/location/c0bee7c8-3e9b-41e3-8600-ee9b2c475da2/sword/collection/

    Example POST creation of deposit from another location:
      curl -v -H "In-Progress: true" --request POST http://localhost:8000/api/v1/location/96606387-cc70-4b09-b422-a7220606488d/sword/collection/?source_location=aab142a9-018f-4452-8f93-67c1bf7fd486&relative_path_to_files=archivematica-sampledata/SampleTransfers/Images
    """
    if isinstance(location, basestring):
        try:
            location = models.Location.active.get(uuid=location)
        except models.Location.DoesNotExist:
            return helpers.sword_error_response(
                request, 404,
                'Collection {uuid} does not exist.'.format(uuid=location))

    if request.method == 'GET':
        # return list of deposits as ATOM feed
        col_iri = request.build_absolute_uri(
            reverse('sword_collection',
                    kwargs={
                        'api_name': 'v1',
                        'resource_name': 'location',
                        'uuid': location.uuid
                    }))
        feed = {'title': 'Deposits', 'url': col_iri}

        entries = []
        for deposit in helpers.deposit_list(location.uuid):
            edit_iri = request.build_absolute_uri(
                reverse('sword_deposit',
                        kwargs={
                            'api_name': 'v1',
                            'resource_name': 'file',
                            'uuid': deposit.uuid
                        }))

            entries.append({
                'title':
                deposit.description or 'Deposit ' + deposit.uuid,
                'url':
                edit_iri,
            })

        # feed and entries passed via locals() to the template
        collection_xml = render_to_string('locations/api/sword/collection.xml',
                                          locals())
        response = HttpResponse(collection_xml)
        response['Content-Type'] = 'application/atom+xml;type=feed'
        return response

    elif request.method == 'POST':
        # has the In-Progress header been set?
        if 'HTTP_IN_PROGRESS' in request.META:
            # process creation request, if criteria met
            source_location = request.GET.get('source_location', '')
            relative_path_to_files = request.GET.get('relative_path_to_files',
                                                     '')

            if request.body != '':
                try:
                    temp_filepath = helpers.write_request_body_to_temp_file(
                        request)

                    # parse name and content URLs out of XML
                    try:
                        mets_data = _parse_name_and_content_urls_from_mets_file(
                            temp_filepath)
                    except etree.XMLSyntaxError as e:
                        os.unlink(temp_filepath)
                        mets_data = None

                    if mets_data != None:
                        if mets_data['deposit_name'] == None:
                            return helpers.sword_error_response(
                                request, 400, 'No deposit name found in XML.')
                        if not os.path.isdir(location.full_path):
                            return helpers.sword_error_response(
                                request, 500,
                                'Collection path (%s) does not exist: contact an administrator.'
                                % (location.full_path))

                        # TODO: should get this from author header or provided XML metadata
                        sourceofacquisition = request.META[
                            'HTTP_ON_BEHALF_OF'] if 'HTTP_ON_BEHALF_OF' in request.META else None
                        deposit = _create_deposit_directory_and_db_entry(
                            location=location,
                            deposit_name=mets_data['deposit_name'],
                            sourceofacquisition=sourceofacquisition)
                        if deposit is None:
                            return helpers.sword_error_response(
                                request, 500,
                                'Could not create deposit: contact an administrator.'
                            )

                        # move METS file to submission documentation directory
                        object_id = mets_data.get('object_id', 'fedora')
                        helpers.store_mets_data(temp_filepath, deposit,
                                                object_id)

                        _spawn_batch_download_and_flag_finalization_if_requested(
                            deposit, request, mets_data)

                        if request.META['HTTP_IN_PROGRESS'] == 'true':
                            return _deposit_receipt_response(
                                request, deposit, 201)
                        else:
                            return _deposit_receipt_response(
                                request, deposit, 200)
                    else:
                        return helpers.sword_error_response(
                            request, 412, 'Error parsing XML')
                except Exception as e:
                    return helpers.sword_error_response(
                        request, 400, traceback.format_exc())
            elif source_location or relative_path_to_files:
                if not source_location or not relative_path_to_files:
                    if not source_location:
                        return helpers.sword_error_response(
                            request, 400,
                            'relative_path_to_files is set, but source_location is not.'
                        )
                    else:
                        return helpers.sword_error_response(
                            request, 400,
                            'source_location is set, but relative_path_to_files is not.'
                        )
                else:
                    result = deposit_from_location_relative_path(
                        source_location, relative_path_to_files, location)
                    if result.get('error', False):
                        return helpers.sword_error_response(
                            request, 500, result['message'])
                    else:
                        return _deposit_receipt_response(
                            request, result['deposit_uuid'], 200)
            else:
                return helpers.sword_error_response(
                    request, 412,
                    'A request body must be sent when creating a deposit.')
        else:
            return helpers.sword_error_response(
                request, 412,
                'The In-Progress header must be set to either true or false when creating a deposit.'
            )
    else:
        return helpers.sword_error_response(
            request, 405,
            'This endpoint only responds to the GET and POST HTTP methods.')
def deposit_edit(request, deposit):
    """
    Deposit endpoint: list info, accept new files, finalize or delete deposit.

    Example POST adding files to the deposit:
      curl -v -H "In-Progress: true" --data-binary @mets.xml --request POST http://127.0.0.1:8000/api/v1/file/149cc29d-6472-4bcf-bee8-f8223bf60580/sword/

    Example POST finalization of deposit:
      curl -v -H "In-Progress: false" --request POST http://127.0.0.1:8000/api/v1/file/149cc29d-6472-4bcf-bee8-f8223bf60580/sword/

    Example DELETE of deposit:
      curl -v -XDELETE http://127.0.0.1:8000/api/v1/file/149cc29d-6472-4bcf-bee8-f8223bf60580/sword/
    """
    if isinstance(deposit, basestring):
        try:
            deposit = models.Package.objects.get(uuid=deposit)
        except models.Package.DoesNotExist:
            return helpers.sword_error_response(
                request, 404,
                'Deposit location {uuid} does not exist.'.format(uuid=deposit))

    if deposit.has_been_submitted_for_processing():
        return helpers.sword_error_response(
            request, 400,
            'This deposit has already been submitted for processing.')

    if request.method == 'GET':
        edit_iri = request.build_absolute_uri(
            reverse('sword_deposit',
                    kwargs={
                        'api_name': 'v1',
                        'resource_name': 'file',
                        'uuid': deposit.uuid
                    }))
        entry = {'title': deposit.description, 'url': edit_iri}
        response = HttpResponse(
            render_to_string('locations/api/sword/entry.xml', locals()))
        response['Content-Type'] = 'application/atom+xml'
        return response
    elif request.method == 'POST':
        # If METS XML has been sent to indicate a list of files needing downloading, handle it
        if request.body != '':
            temp_filepath = helpers.write_request_body_to_temp_file(request)
            try:
                mets_data = _parse_name_and_content_urls_from_mets_file(
                    temp_filepath)
            except etree.XMLSyntaxError as e:
                os.unlink(temp_filepath)
                mets_data = None

            if mets_data is not None:
                # move METS file to submission documentation directory
                object_id = mets_data.get('object_id', 'fedora')
                helpers.store_mets_data(temp_filepath, deposit, object_id)

                _spawn_batch_download_and_flag_finalization_if_requested(
                    deposit, request, mets_data)
                return _deposit_receipt_response(request, deposit, 200)
            else:
                return helpers.sword_error_response(request, 412,
                                                    'Error parsing XML.')
        else:
            # Attempt to finalize (if requested), otherwise just return deposit receipt
            if 'HTTP_IN_PROGRESS' in request.META and request.META[
                    'HTTP_IN_PROGRESS'] == 'false':
                return _finalize_or_mark_for_finalization(request, deposit)
            else:
                return _deposit_receipt_response(request, deposit, 200)
    elif request.method == 'PUT':
        # TODO: implement update deposit
        return HttpResponse(status=204)  # No content
    elif request.method == 'DELETE':
        # Delete files
        shutil.rmtree(deposit.full_path)
        # Delete all PackageDownloadTaskFile and PackageDownloadTask for this deposit
        models.PackageDownloadTaskFile.objects.filter(
            task__package=deposit).delete()
        models.PackageDownloadTask.objects.filter(package=deposit).delete()
        # TODO should this actually delete the Package entry?
        deposit.status = models.Package.DELETED
        deposit.save()
        return HttpResponse(status=204)  # No content
    else:
        return helpers.sword_error_response(
            request, 405,
            'This endpoint only responds to the GET, POST, PUT, and DELETE HTTP methods.'
        )