lib/rdf/ldp/non_rdf_source.rb in rdf-ldp-0.7.0 vs lib/rdf/ldp/non_rdf_source.rb in rdf-ldp-0.8.0

- old
+ new

@@ -1,44 +1,60 @@ +require 'rdf/ldp/storage_adapters/file_storage_adapter' + module RDF::LDP ## # A NonRDFSource describes a `Resource` whose response body is a format other - # than an RDF serialization. The persistent state of the resource, as - # represented by the body, is persisted to an IO stream provided by a + # than an RDF serialization. The persistent state of the resource, as + # represented by the body, is persisted to an IO stream provided by a # `RDF::LDP::NonRDFSource::StorageAdapter` given by `#storage`. # # In addition to the properties stored by the `RDF::LDP::Resource#metagraph`, - # `NonRDFSource`s also store a content type (format). + # `NonRDFSource`s also store a content type (format). # - # When a `NonRDFSource` is created, it also creates an `RDFSource` which + # When a `NonRDFSource` is created, it also creates an `RDFSource` which # describes it. This resource is created at the URI in `#description_uri`, - # the resource itself is returned by `#description`. + # the resource itself is returned by `#description`. # # @see RDF::LDP::Resource # @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-non-rdf-source for # a definition of NonRDFSource in LDP class NonRDFSource < Resource + attr_reader :storage + + # Use the default filesystem-based storage adapter + DEFAULT_ADAPTER = RDF::LDP::NonRDFSource::FileStorageAdapter + # Use DC elements format - FORMAT_TERM = RDF::Vocab::DC11.format - DESCRIBED_BY_TERM = RDF::URI('http://www.w3.org/2007/05/powder-s#describedby') - + FORMAT_TERM = RDF::Vocab::DC11.format.freeze + ## - # @return [RDF::URI] uri with lexical representation + # @param [storage_adapter] a class implementing the StorageAdapter interface + # + # @see RDF::LDP::Resource#initialize + def initialize(subject_uri, data = RDF::Repository.new, storage_adapter = DEFAULT_ADAPTER) + data ||= RDF::Repository.new # allows explict `nil` pass + @storage = storage_adapter.new(self) + super(subject_uri, data) + end + + ## + # @return [RDF::URI] uri with lexical representation # 'http://www.w3.org/ns/ldp#NonRDFSource' # # @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-non-rdf-source - def self.to_uri + def self.to_uri RDF::Vocab::LDP.NonRDFSource end ## # @return [Boolean] whether this is an ldp:NonRDFSource def non_rdf_source? true end ## - # @param [IO, File] input input (usually from a Rack env's + # @param [IO, File] input input (usually from a Rack env's # `rack.input` key) that will be read into the NonRDFSource # @param [#to_s] c_type a MIME content_type used as a content type # for the created NonRDFSource # # @raise [RDF::LDP::RequestError] when saving the NonRDFSource @@ -50,11 +66,11 @@ storage.io { |io| IO.copy_stream(input, io) } super self.content_type = c_type RDFSource.new(description_uri, @data) - .create(StringIO.new, 'application/n-triples') + .create(StringIO.new, 'application/n-triples') self end ## @@ -65,17 +81,17 @@ self.content_type = c_type self end ## - # Deletes the LDP-NR contents from the storage medium and marks the + # Deletes the LDP-NR contents from the storage medium and marks the # resource as destroyed. # # @see RDF::LDP::Resource#destroy def destroy - storage.delete super + storage.delete end ## # @raise [RDF::LDP::NotFound] if the describedby resource doesn't exist # @@ -89,52 +105,46 @@ def description_uri subject_uri / '.well-known' / 'desc' end ## - # @return [StorageAdapter] the storage adapter for this LDP-NR - def storage - @storage_adapter ||= StorageAdapter.new(self) - end - - ## # Sets the MIME type for the resource in `metagraph`. # # @param [String] a string representing the content type for this LDP-NR. # This SHOULD be a regisered MIME type. # - # @return [StorageAdapter] the content type + # @return [StorageAdapter] the content type def content_type=(content_type) metagraph.delete([subject_uri, FORMAT_TERM]) - metagraph << - RDF::Statement(subject_uri, RDF::Vocab::DC11.format, content_type) + metagraph << + RDF::Statement(subject_uri, FORMAT_TERM, content_type) end - + ## - # @return [StorageAdapter] this resource's content type + # @return [StorageAdapter] this resource's content type def content_type format_triple = metagraph.first([subject_uri, FORMAT_TERM, :format]) format_triple.nil? ? nil : format_triple.object.object end ## - # @return [#each] the response body. This is normally the StorageAdapter's + # @return [#each] the response body. This is normally the StorageAdapter's # IO object in read and binary mode. - # + # # @raise [RDF::LDP::RequestError] when the request fails def to_response (exists? && !destroyed?) ? storage.io : [] end - private + private ## # Process & generate response for PUT requsets. - def put(status, headers, env) - raise PreconditionFailed.new('Etag invalid') if - env.has_key?('HTTP_IF_MATCH') && !match?(env['HTTP_IF_MATCH']) - + def put(_status, headers, env) + raise(PreconditionFailed, 'Etag invalid') if + env.key?('HTTP_IF_MATCH') && !match?(env['HTTP_IF_MATCH']) + if exists? update(env['rack.input'], env['CONTENT_TYPE']) headers = update_headers(headers) [200, headers, self] else @@ -150,107 +160,8 @@ super end def link_headers super << "<#{description_uri}>;rel=\"describedBy\"" - end - - ## - # StorageAdapters bundle the logic for mapping a `NonRDFSource` to a - # specific IO stream. Implementations must conform to a minimal interface: - # - # - `#initailize` must accept a `resource` parameter. The input should be - # a `NonRDFSource` (LDP-NR). - # - `#io` must yield and return a IO object in binary mode that represents - # the current state of the LDP-NR. - # - If a block is passed to `#io`, the implementation MUST allow return a - # writable IO object and that anything written to the stream while - # yielding is synced with the source in a thread-safe manner. - # - Clients not passing a block to `#io` SHOULD call `#close` on the - # object after reading it. - # - If the `#io` object responds to `#to_path` it MUST give the location - # of a file whose contents are identical the IO object's. This supports - # Rack's response body interface. - # - `#delete` remove the contents from the corresponding storage. This MAY - # be a no-op if is undesirable or impossible to delete the contents - # from the storage medium. - # - # @see http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Body - # for details about `#to_path` in Rack response bodies. - # - # @example reading from a `StorageAdapter` - # storage = StorageAdapter.new(an_nr_source) - # storage.io.read # => [string contents of `an_nr_source`] - # - # @example writing to a `StorageAdapter` - # storage = StorageAdapter.new(an_nr_source) - # storage.io { |io| io.write('moomin') } - # - # Beyond this interface, implementations are permitted to behave as desired. - # They may, for instance, reject undesirable content or alter the graph (or - # metagraph) of the resource. They should throw appropriate `RDF::LDP` - # errors when failing to allow the middleware to handle response codes and - # messages. - # - # The base storage adapter class provides a simple File storage - # implementation. - # - # @todo check thread saftey on write for base implementation - class StorageAdapter - STORAGE_PATH = '.storage'.freeze - - ## - # Initializes the storage adapter. - # - # @param [NonRDFSource] resource - def initialize(resource) - @resource = resource - end - - ## - # Gives an IO object which represents the current state of @resource. - # Opens the file for read-write (mode: r+), if it already exists; - # otherwise, creates the file and opens it for read-write (mode: w+). - # - # @yield [IO] yields a read-writable object conforming to the Ruby IO - # interface for storage. The IO object will be closed when the block - # ends. - # - # @return [IO] an object conforming to the Ruby IO interface - def io(&block) - FileUtils.mkdir_p(path_dir) unless Dir.exists?(path_dir) - FileUtils.touch(path) unless file_exists? - - File.open(path, 'r+b', &block) - end - - ## - # @return [Boolean] 1 if the file has been deleted, otherwise false - def delete - return false unless File.exists?(path) - File.delete(path) - end - - private - - ## - # @return [Boolean] - def file_exists? - File.exists?(path) - end - - ## - # Build the path to the file on disk. - # @return [String] - def path - File.join(STORAGE_PATH, @resource.subject_uri.path) - end - - ## - # Build the path to the file's directory on disk - # @return [String] - def path_dir - File.split(path).first - end end end end