require 'uri'
module Ldp
class Response
extend Forwardable
TYPE = 'type'.freeze
attr_reader :response
attr_writer :etag, :last_modified
def initialize(response)
@response = response
end
##
# Extract the Link: headers from the HTTP resource
def links
@links ||= begin
h = {}
Array(headers['Link'.freeze]).map { |x| x.split(','.freeze) }.flatten.inject(h) do |memo, header|
m = header.match(/<(?.*)>;\s?rel="(?[^"]+)"/)
if m
memo[m[:rel]] ||= []
memo[m[:rel]] << m[:link]
end
memo
end
end
end
def applied_preferences
h = {}
Array(headers['Preference-Applied'.freeze]).map { |x| x.split(",") }.flatten.inject(h) do |memo, header|
m = header.match(/(?[^=;]*)(=(?[^;,]*))?(;\s*(?[^,]*))?/)
includes = (m[:params].match(/include="(?[^"]+)"/)[:include] || "").split(" ")
omits = (m[:params].match(/omit="(?[^"]+)"/)[:omit] || "").split(" ")
memo[m[:key]] = { value: m[:value], includes: includes, omits: omits }
end
end
##
# Is the response an LDP resource?
def resource?
Array(links[TYPE]).include? RDF::Vocab::LDP.Resource.to_s
end
##
# Is the response an LDP container?
def container?
[
RDF::Vocab::LDP.BasicContainer,
RDF::Vocab::LDP.DirectContainer,
RDF::Vocab::LDP.IndirectContainer
].any? { |x| Array(links[TYPE]).include? x.to_s }
end
##
# Is the response an LDP RDFSource?
# ldp:Container is a subclass of ldp:RDFSource
def rdf_source?
container? || Array(links[TYPE]).include?(RDF::Vocab::LDP.RDFSource)
end
def dup
super.tap do |new_resp|
unless new_resp.instance_variable_get(:@graph).nil?
new_resp.remove_instance_variable(:@graph)
end
end
end
##
# Get the subject for the response
def subject
@subject ||= if has_page?
graph.first_object [page_subject, RDF::Vocab::LDP.pageOf, nil]
else
page_subject
end
end
##
# Get the URI to the response
def page_subject
@page_subject ||= RDF::URI.new response.env[:url]
end
##
# Is the response paginated?
def has_page?
rdf_source? && graph.has_statement?(RDF::Statement.new(page_subject, RDF.type, RDF::Vocab::LDP.Page))
end
def body
response.body
end
##
# Get the graph for the resource (or a blank graph if there is no metadata for the resource)
def graph
@graph ||= begin
graph = RDF::Graph.new
each_statement { |s| graph << s }
graph
end
end
def reader(&block)
reader_for_content_type.new(body, base_uri: page_subject, &block)
end
def each_statement(&block)
reader do |reader|
reader.each_statement(&block)
end
end
##
# Extract the ETag for the resource
def etag
@etag ||= headers['ETag'.freeze]
end
##
# Extract the last modified header for the resource
def last_modified
@last_modified ||= headers['Last-Modified'.freeze]
end
##
# Extract the Link: rel="type" headers for the resource
def types
Array(links[TYPE])
end
RETURN = 'return'.freeze
def includes? preference
key = Ldp.send("prefer_#{preference}") if Ldp.respond_to("prefer_#{preference}")
key ||= preference
preferences[RETURN][:includes].include?(key) || !preferences["return"][:omits].include?(key)
end
def minimal?
preferences[RETURN][:value] == "minimal"
end
##
# Statements about the page
def page
@page_graph ||= begin
g = RDF::Graph.new
if resource?
res = graph.query RDF::Statement.new(page_subject, nil, nil)
res.each_statement do |s|
g << s
end
end
g
end
end
##
# Is there a next page?
def has_next?
next_page != nil
end
##
# Get the URI for the next page
def next_page
graph.first_object [page_subject, RDF::Vocab::LDP.nextPage, nil]
end
##
# Get the URI to the first page
def first_page
if links['first']
RDF::URI.new links['first']
elsif graph.has_statement? RDf::Statement.new(page_subject, RDF::Vocab::LDP.nextPage, nil)
subject
end
end
def content_type
headers['Content-Type']
end
def content_length
headers['Content-Length'].to_i
end
def content_disposition_filename
filename = content_disposition_attributes['filename']
URI.decode(filename) if filename
end
private
def content_disposition_attributes
parts = headers['Content-Disposition'].split(/;\s*/).collect { |entry| entry.split(/\s*=\s*/) }
entries = parts.collect do |part|
value = part[1].respond_to?(:sub) ? part[1].sub(%r{^"(.*)"$}, '\1') : part[1]
[part[0], value]
end
Hash[entries]
end
def headers
response.headers
end
def reader_for_content_type
content_type = content_type || 'text/turtle'
content_type = Array(content_type).first
RDF::Reader.for(content_type: content_type)
end
end
end