class Tilia::Dav::PartialUpdate::Plugin

Partial update plugin (Patch method)

This plugin provides a way to modify only part of a target resource It may bu used to update a file chunk, upload big a file into smaller chunks or resume an upload.

patch_plugin = new SabreDAVPartialUpdatePlugin server.add_plugin(patch_plugin)

Constants

RANGE_APPEND
RANGE_END
RANGE_START

Public Instance Methods

features() click to toggle source

Returns a list of features for the HTTP OPTIONS Dav: header.

@return array

# File lib/tilia/dav/partial_update/plugin.rb, line 68
def features
  ['sabredav-partialupdate']
end
http_methods(uri) click to toggle source

Use this method to tell the server this plugin defines additional HTTP methods.

This method is passed a uri. It should only return HTTP methods that are available for the specified uri.

We claim to support PATCH method (partirl update) if and only if

- the node exist
- the node implements our partial update interface

@param string uri @return array

# File lib/tilia/dav/partial_update/plugin.rb, line 55
def http_methods(uri)
  tree = @server.tree

  if tree.node_exists(uri)
    node = tree.node_for_path(uri)
    return ['PATCH'] if node.is_a?(IPatchSupport)
  end
  []
end
http_patch(request, response) click to toggle source

Patch an uri

The WebDAV patch request can be used to modify only a part of an existing resource. If the resource does not exist yet and the first offset is not 0, the request fails

@param RequestInterface request @param ResponseInterface response @return void

# File lib/tilia/dav/partial_update/plugin.rb, line 81
def http_patch(request, response)
  path = request.path

  # Get the node. Will throw a 404 if not found
  node = @server.tree.node_for_path(path)
  unless node.is_a?(IPatchSupport)
    fail Exception::MethodNotAllowed, 'The target resource does not support the PATCH method.'
  end

  range = http_update_range(request)

  unless range
    fail Exception::BadRequest, 'No valid "X-Update-Range" found in the headers'
  end

  content_type = request.header('Content-Type').to_s.downcase

  unless content_type == 'application/x-sabredav-partialupdate'
    fail Exception::UnsupportedMediaType, "Unknown Content-Type header \"#{content_type}\""
  end

  len = @server.http_request.header('Content-Length').to_i
  if len == 0
    fail Exception::LengthRequired, 'A Content-Length header is required'
  end

  case range[0]
  when RANGE_START
    # Calculate the end-range if it doesn't exist.
    if range[2].blank?
      range[2] = range[1] + len - 1
    else
      if range[2] < range[1]
        fail Exception::RequestedRangeNotSatisfiable, "The end offset (#{range[2]}) is lower than the start offset (#{range[1]})"
      end
      if range[2] - range[1] + 1 != len
        fail Exception::RequestedRangeNotSatisfiable, "Actual data length (#{len}) is not consistent with begin (#{range[1]}) and end (#{range[2]}) offsets"
      end
    end
  end

  unless @server.emit('beforeWriteContent', [path, node, nil, nil])
    return nil
  end

  body = @server.http_request.body

  etag = node.patch(body, range[0], range[1])

  @server.emit('afterWriteContent', [path, node])

  response.update_header('Content-Length', '0')
  response.update_header('ETag', etag) if etag

  response.status = 204

  # Breaks the event chain
  false
end
http_update_range(request) click to toggle source

Returns the HTTP custom range update header

This method returns null if there is no well-formed HTTP range request header. It returns array(1) if it was an append request, array(2, start, end) if it's a start and end range, lastly it's array(3, endoffset) if the offset was negative, and should be calculated from the end of the file.

Examples:

null - invalid

1
  • append

2,10,15
  • update bytes 10, 11, 12, 13, 14, 15

2,10,null
  • update bytes 10 until the end of the patch body

3,-5
  • update from 5 bytes from the end of the file.

@param RequestInterface request @return array|null

# File lib/tilia/dav/partial_update/plugin.rb, line 159
def http_update_range(request)
  range = request.header('X-Update-Range')
  return nil unless range

  # Matching "Range: bytes=1234-5678: both numbers are optional

  matches = /^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i.match(range)
  return nil unless matches

  if matches[1] == 'append'
    return [RANGE_APPEND]
  elsif matches[2].to_s.size > 0
    return [RANGE_START, matches[2].to_i, matches[3].blank? ? nil : matches[3].to_i]
  else
    return [RANGE_END, matches[4].to_i]
  end
end
plugin_name() click to toggle source

Returns a plugin name.

Using this name other plugins will be able to access other plugins using DAVServer::getPlugin

@return string

# File lib/tilia/dav/partial_update/plugin.rb, line 39
def plugin_name
  'partialupdate'
end
setup(server) click to toggle source

Initializes the plugin

This method is automatically called by the Server class after addPlugin.

@param DAVServer server @return void

# File lib/tilia/dav/partial_update/plugin.rb, line 28
def setup(server)
  @server = server
  server.on('method:PATCH', method(:http_patch))
end