rdf-ldp 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -33,7 +33,7 @@ module RDF::LDP
33
33
  # resource = RDF::LDP::Resource.new('http://example.org/moomin', repository)
34
34
  # resource.exists? # => false
35
35
  #
36
- # resource.create('', 'text/plain')
36
+ # resource.create(StringIO.new(''), 'text/plain')
37
37
  #
38
38
  # resource.exists? # => true
39
39
  # resource.metagraph.dump :ttl
@@ -56,8 +56,8 @@ module RDF::LDP
56
56
  # resource.exists? # => true
57
57
  # resource.destroyed? # => true
58
58
  #
59
- # Rack (via `RDF::LDP::Rack`) uses the `#request` method to dispatch requests and
60
- # interpret responses. Disallowed HTTP methods result in
59
+ # Rack (via `RDF::LDP::Rack`) uses the `#request` method to dispatch requests
60
+ # and interpret responses. Disallowed HTTP methods result in
61
61
  # `RDF::LDP::MethodNotAllowed`. Individual Resources populate `Link`, `Allow`,
62
62
  # `ETag`, `Last-Modified`, and `Accept-*` headers as required by LDP. All
63
63
  # subclasses (MUST) return `self` as the Body, and respond to `#each`/
@@ -80,10 +80,14 @@ module RDF::LDP
80
80
  #
81
81
  # resource.request(:put, 200, {}, {}) # RDF::LDP::MethodNotAllowed: put
82
82
  #
83
- # @see http://www.w3.org/TR/ldp/ for the Linked Data platform specification
84
- # @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-resource for a
85
- # definition of 'Resource' in LDP
83
+ # @see http://www.w3.org/TR/ldp/ Linked Data platform Specification
84
+ # @see http://www.w3.org/TR/ldp/#dfn-linked-data-platform-resource Definition
85
+ # of 'Resource' in LDP
86
86
  class Resource
87
+ CONTAINS_URI = RDF::Vocab::LDP.contains.freeze
88
+ INVALIDATED_AT_URI = RDF::Vocab::PROV.invalidatedAtTime.freeze
89
+ MODIFIED_URI = RDF::Vocab::DC.modified.freeze
90
+
87
91
  # @!attribute [r] subject_uri
88
92
  # an rdf term identifying the `Resource`
89
93
  attr_reader :subject_uri
@@ -149,18 +153,19 @@ module RDF::LDP
149
153
  # @return [Class] a subclass of {RDF::LDP::Resource} matching the
150
154
  # requested interaction model;
151
155
  def interaction_model(link_header)
152
- models = LinkHeader.parse(link_header)
153
- .links.select { |link| link['rel'].downcase == 'type' }
154
- .map { |link| link.href }
156
+ models =
157
+ LinkHeader.parse(link_header)
158
+ .links.select { |link| link['rel'].casecmp 'type' }
159
+ .map { |link| link.href }
155
160
 
156
161
  return RDFSource if models.empty?
157
162
  match = INTERACTION_MODELS.keys.reverse.find { |u| models.include? u }
158
163
 
159
164
  if match == RDF::LDP::NonRDFSource.to_uri
160
165
  raise NotAcceptable if
161
- models.include?(RDF::LDP::RDFSource.to_uri) ||
162
- models.include?(RDF::LDP::Container.to_uri) ||
163
- models.include?(RDF::LDP::DirectContainer.to_uri) ||
166
+ models.include?(RDF::LDP::RDFSource.to_uri) ||
167
+ models.include?(RDF::LDP::Container.to_uri) ||
168
+ models.include?(RDF::LDP::DirectContainer.to_uri) ||
164
169
  models.include?(RDF::LDP::IndirectContainer.to_uri) ||
165
170
  models.include?(RDF::URI('http://www.w3.org/ns/ldp#BasicContainer'))
166
171
  end
@@ -217,7 +222,7 @@ module RDF::LDP
217
222
  # @raise [RDF::LDP::RequestError] when creation fails. May raise various
218
223
  # subclasses for the appropriate response codes.
219
224
  # @raise [RDF::LDP::Conflict] when the resource exists
220
- def create(input, content_type, &block)
225
+ def create(_input, _content_type)
221
226
  raise Conflict if exists?
222
227
 
223
228
  @data.transaction(mutable: true) do |transaction|
@@ -268,13 +273,13 @@ module RDF::LDP
268
273
  #
269
274
  # @todo Use of owl:Nothing is probably problematic. Define an internal
270
275
  # namespace and class represeting deletion status as a stateful property.
271
- def destroy(&block)
276
+ def destroy
272
277
  @data.transaction(mutable: true) do |transaction|
273
278
  containers.each { |c| c.remove(self, transaction) if c.container? }
274
279
  transaction.insert RDF::Statement(subject_uri,
275
- RDF::Vocab::PROV.invalidatedAtTime,
276
- DateTime.now,
277
- graph_name: metagraph_name)
280
+ INVALIDATED_AT_URI,
281
+ DateTime.now,
282
+ graph_name: metagraph_name)
278
283
  yield transaction if block_given?
279
284
  end
280
285
  self
@@ -294,8 +299,8 @@ module RDF::LDP
294
299
  ##
295
300
  # @return [Boolean] true if resource has been destroyed
296
301
  def destroyed?
297
- times = @metagraph.query([subject_uri, RDF::Vocab::PROV.invalidatedAtTime, nil])
298
- !(times.empty?)
302
+ times = @metagraph.query([subject_uri, INVALIDATED_AT_URI, nil])
303
+ !times.empty?
299
304
  end
300
305
 
301
306
  ##
@@ -303,13 +308,15 @@ module RDF::LDP
303
308
  #
304
309
  # @return [String] an HTTP Etag
305
310
  #
306
- # @note these etags are strong if (and only if) all software that updates
307
- # the resource also updates the ETag
311
+ # @note these etags are weak, but we allow clients to use them in
312
+ # `If-Match` headers, and use weak comparison. This is in conflict with
313
+ # https://tools.ietf.org/html/rfc7232#section-3.1. See:
314
+ # https://github.com/ruby-rdf/rdf-ldp/issues/68
308
315
  #
309
316
  # @see http://www.w3.org/TR/ldp#h-ldpr-gen-etags LDP ETag clause for GET
310
317
  # @see http://www.w3.org/TR/ldp#h-ldpr-put-precond LDP ETag clause for PUT
311
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3
312
- # description of strong vs. weak validators
318
+ # @see https://tools.ietf.org/html/rfc7232#section-2.1
319
+ # Weak vs. strong validators
313
320
  def etag
314
321
  return nil unless exists?
315
322
  "W/\"#{last_modified.new_offset(0).iso8601(9)}\""
@@ -382,7 +389,7 @@ module RDF::LDP
382
389
  ##
383
390
  # @return [Array<RDF::LDP::Resource>] the container for this resource
384
391
  def containers
385
- @data.query([:s, RDF::Vocab::LDP.contains, subject_uri]).map do |st|
392
+ @data.query([:s, CONTAINS_URI, subject_uri]).map do |st|
386
393
  RDF::LDP::Resource.find(st.subject, @data)
387
394
  end
388
395
  end
@@ -392,11 +399,11 @@ module RDF::LDP
392
399
  # conforming to the Rack interfare.
393
400
  #
394
401
  # @see http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Body
395
- # for Rack body documentation
402
+ # Rack body documentation
396
403
  def to_response
397
404
  []
398
405
  end
399
- alias_method :each, :to_response
406
+ alias each to_response
400
407
 
401
408
  ##
402
409
  # Build the response for the HTTP `method` given.
@@ -426,7 +433,7 @@ module RDF::LDP
426
433
  raise Gone if destroyed?
427
434
  begin
428
435
  send(method.to_sym.downcase, status, headers, env)
429
- rescue NotImplementedError => e
436
+ rescue NotImplementedError
430
437
  raise MethodNotAllowed, method
431
438
  end
432
439
  end
@@ -436,27 +443,27 @@ module RDF::LDP
436
443
  ##
437
444
  # Generate response for GET requests. Returns existing status and headers,
438
445
  # with `self` as the body.
439
- def get(status, headers, env)
446
+ def get(status, headers, _env)
440
447
  [status, update_headers(headers), self]
441
448
  end
442
449
 
443
450
  ##
444
451
  # Generate response for HEAD requsets. Adds appropriate headers and returns
445
452
  # an empty body.
446
- def head(status, headers, env)
453
+ def head(status, headers, _env)
447
454
  [status, update_headers(headers), []]
448
455
  end
449
456
 
450
457
  ##
451
458
  # Generate response for OPTIONS requsets. Adds appropriate headers and
452
459
  # returns an empty body.
453
- def options(status, headers, env)
460
+ def options(status, headers, _env)
454
461
  [status, update_headers(headers), []]
455
462
  end
456
463
 
457
464
  ##
458
465
  # Process & generate response for DELETE requests.
459
- def delete(status, headers, env)
466
+ def delete(_status, headers, _env)
460
467
  destroy
461
468
  headers.delete('Content-Type')
462
469
  [204, headers, []]
@@ -503,7 +510,7 @@ module RDF::LDP
503
510
  # @return [Hash<String, String>] the updated headers
504
511
  def update_headers(headers)
505
512
  headers['Link'] =
506
- ([headers['Link']] + link_headers).compact.join(",")
513
+ ([headers['Link']] + link_headers).compact.join(',')
507
514
 
508
515
  headers['Allow'] = allowed_methods.join(', ')
509
516
  headers['Accept-Post'] = accept_post if respond_to?(:post, true)
@@ -561,18 +568,15 @@ module RDF::LDP
561
568
  # transactions do not support updates or pattern deletes, so we must
562
569
  # ask the Repository for the current last_modified to delete the statement
563
570
  # transactionally
564
- modified = last_modified
565
- transaction.delete RDF::Statement(subject_uri,
566
- RDF::Vocab::DC.modified,
567
- modified,
568
- graph_name: metagraph_name) if modified
571
+ transaction
572
+ .delete RDF::Statement(subject_uri, MODIFIED_URI, last_modified,
573
+ graph_name: metagraph_name) if last_modified
569
574
 
570
- transaction.insert RDF::Statement(subject_uri,
571
- RDF::Vocab::DC.modified,
572
- DateTime.now,
573
- graph_name: metagraph_name)
575
+ transaction
576
+ .insert RDF::Statement(subject_uri, MODIFIED_URI, DateTime.now,
577
+ graph_name: metagraph_name)
574
578
  else
575
- metagraph.update([subject_uri, RDF::Vocab::DC.modified, DateTime.now])
579
+ metagraph.update([subject_uri, MODIFIED_URI, DateTime.now])
576
580
  end
577
581
  end
578
582
 
@@ -0,0 +1,102 @@
1
+ module RDF::LDP
2
+ class NonRDFSource < Resource
3
+ ##
4
+ # StorageAdapters bundle the logic for mapping a `NonRDFSource` to a
5
+ # specific IO stream. Implementations must conform to a minimal interface:
6
+ #
7
+ # - `#initailize` must accept a `resource` parameter. The input should be
8
+ # a `NonRDFSource` (LDP-NR).
9
+ # - `#io` must yield and return a IO object in binary mode that represents
10
+ # the current state of the LDP-NR.
11
+ # - If a block is passed to `#io`, the implementation MUST allow return a
12
+ # writable IO object and that anything written to the stream while
13
+ # yielding is synced with the source in a thread-safe manner.
14
+ # - Clients not passing a block to `#io` SHOULD call `#close` on the
15
+ # object after reading it.
16
+ # - If the `#io` object responds to `#to_path` it MUST give the location
17
+ # of a file whose contents are identical the IO object's. This supports
18
+ # Rack's response body interface.
19
+ # - `#delete` remove the contents from the corresponding storage. This MAY
20
+ # be a no-op if is undesirable or impossible to delete the contents
21
+ # from the storage medium.
22
+ #
23
+ # @see http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Body
24
+ # for details about `#to_path` in Rack response bodies.
25
+ #
26
+ # @example reading from a `StorageAdapter`
27
+ # storage = StorageAdapter.new(an_nr_source)
28
+ # storage.io.read # => [string contents of `an_nr_source`]
29
+ #
30
+ # @example writing to a `StorageAdapter`
31
+ # storage = StorageAdapter.new(an_nr_source)
32
+ # storage.io { |io| io.write('moomin') }
33
+ #
34
+ # Beyond this interface, implementations are permitted to behave as desired.
35
+ # They may, for instance, reject undesirable content or alter the graph (or
36
+ # metagraph) of the resource. They should throw appropriate `RDF::LDP`
37
+ # errors when failing to allow the middleware to handle response codes and
38
+ # messages.
39
+ #
40
+ # The base storage adapter class provides a simple File storage
41
+ # implementation.
42
+ #
43
+ # @todo check thread saftey on write for base implementation
44
+ class FileStorageAdapter
45
+ STORAGE_PATH = '.storage'.freeze
46
+
47
+ ##
48
+ # Initializes the storage adapter.
49
+ #
50
+ # @param [NonRDFSource] resource
51
+ def initialize(resource)
52
+ @resource = resource
53
+ end
54
+
55
+ ##
56
+ # Gives an IO object which represents the current state of @resource.
57
+ # Opens the file for read-write (mode: r+), if it already exists;
58
+ # otherwise, creates the file and opens it for read-write (mode: w+).
59
+ #
60
+ # @yield [IO] yields a read-writable object conforming to the Ruby IO
61
+ # interface for storage. The IO object will be closed when the block
62
+ # ends.
63
+ #
64
+ # @return [IO] an object conforming to the Ruby IO interface
65
+ def io(&block)
66
+ FileUtils.mkdir_p(path_dir) unless Dir.exists?(path_dir)
67
+ FileUtils.touch(path) unless file_exists?
68
+
69
+ File.open(path, 'r+b', &block)
70
+ end
71
+
72
+ ##
73
+ # @return [Boolean] 1 if the file has been deleted, otherwise false
74
+ def delete
75
+ return false unless File.exists?(path)
76
+ File.delete(path)
77
+ end
78
+
79
+ private
80
+
81
+ ##
82
+ # @return [Boolean]
83
+ def file_exists?
84
+ File.exists?(path)
85
+ end
86
+
87
+ ##
88
+ # Build the path to the file on disk.
89
+ # @return [String]
90
+ def path
91
+ File.join(STORAGE_PATH, @resource.subject_uri.path)
92
+ end
93
+
94
+ ##
95
+ # Build the path to the file's directory on disk
96
+ # @return [String]
97
+ def path_dir
98
+ File.split(path).first
99
+ end
100
+ end
101
+ end
102
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-ldp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Johnson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-26 00:00:00.000000000 Z
11
+ date: 2016-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 2.0.0
33
+ version: '2.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 2.0.0
40
+ version: '2.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rdf-turtle
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '2.0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '2.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: ld-patch
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '1.1'
89
+ version: '2.0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '1.1'
96
+ version: '2.0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: json-ld
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -363,10 +363,11 @@ files:
363
363
  - lib/rdf/ldp/non_rdf_source.rb
364
364
  - lib/rdf/ldp/rdf_source.rb
365
365
  - lib/rdf/ldp/resource.rb
366
+ - lib/rdf/ldp/storage_adapters/file_storage_adapter.rb
366
367
  - lib/rdf/ldp/version.rb
367
368
  homepage: http://ruby-rdf.github.com/
368
369
  licenses:
369
- - Public Domain
370
+ - Unlicense
370
371
  metadata: {}
371
372
  post_install_message:
372
373
  rdoc_options: []
@@ -377,7 +378,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
377
378
  requirements:
378
379
  - - ">="
379
380
  - !ruby/object:Gem::Version
380
- version: 2.0.0
381
+ version: 2.2.2
381
382
  required_rubygems_version: !ruby/object:Gem::Requirement
382
383
  requirements:
383
384
  - - ">="
@@ -390,4 +391,3 @@ signing_key:
390
391
  specification_version: 4
391
392
  summary: A suite of LDP software and middleware for RDF.rb.
392
393
  test_files: []
393
- has_rdoc: false