duracloud-client 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,116 +1,10 @@
1
- require 'hashie'
1
+ require "duracloud/properties"
2
2
 
3
3
  module Duracloud
4
- #
5
- # [See https://groups.google.com/d/msg/duracloud-users/67ONTkAqyCM/SGVOOHTvAAAJ]
6
- #
7
- # These are used as part of the REST call to perform a copy
8
- #
9
- # x-dura-meta-copy-source
10
- # x-dura-meta-copy-source-store
11
- #
12
- # These are used to provide details about a space.
13
- # You could probably set them on a content item, but that would be a little odd:
14
- #
15
- # x-dura-meta-space-count
16
- # x-dura-meta-space-created
17
- #
18
- # These are use used as part of the REST call to set space access control
19
- # (the * is replaced by the user or group name)
20
- #
21
- # x-dura-meta-acl-*
22
- # x-dura-meta-acl-group-*
23
- #
24
- # These are used for some internal DuraCloud data wrangling,
25
- # I'd recommend that you not use them in your code:
26
- #
27
- # x-dura-meta-content-mimetype
28
- # x-dura-meta-content-size
29
- # x-dura-meta-content-checksum
30
- # x-dura-meta-content-modified
31
- #
32
- # That's it for the exceptions.
33
- #
34
- # One other set I'll call out is the values which are captured by the DuraCloud SyncTool.
35
- # These are added automatically by the SyncTool, so you may have an interest in reading
36
- # or updating them. Either way, it's good to be aware of them so you don't accidentally
37
- # step on the values being captured for you.
38
- #
39
- # x-dura-meta-creator
40
- # x-dura-meta-content-file-created
41
- # x-dura-meta-content-file-modified
42
- # x-dura-meta-content-file-last-accessed
43
- # x-dura-meta-content-file-path
44
- #
45
- # A few other notes:
46
- #
47
- # Stick with US-ASCII characters for property names and values
48
- # There is a 2 KB total size limit on all user-metadata (this includes the metadata
49
- # we create for you and that you add yourself)
50
- #
51
- # Both of these restrictions are put in place by Amazon S3, so we don't have much say
52
- # in the matter.
53
- #
54
- class ContentProperties < Hashie::Mash
55
- include Hashie::Extensions::Coercion
4
+ class ContentProperties < Properties
56
5
 
57
- PREFIX = "x-dura-meta-".freeze
58
-
59
- ENCODING = Encoding::US_ASCII
60
-
61
- RESERVED = [
62
- /\Ax-dura-meta-acl-/,
63
- /\Ax-dura-meta-space-(count|created)\z/,
64
- /\Ax-dura-meta-copy-source(-store)\z/,
65
- /\Ax-dura-meta-content-(mimetype|size|checksum|modified)\z/,
66
- ]
67
-
68
- coerce_value Array, ->(v) { v.first }
69
-
70
- class << self
71
- attr_accessor :ignore_reserved
72
-
73
- def property?(prop)
74
- prop.start_with?(PREFIX)
75
- end
76
- end
77
-
78
- self.ignore_reserved = true
79
-
80
- def ignore_reserved?
81
- self.class.ignore_reserved
82
- end
83
-
84
- def regular_writer(key, value)
85
- if reserved?(key)
86
- if ignore_reserved?
87
- warn "#{self.class}: Ignoring reserved content property \"#{key}\"."
88
- else
89
- raise Error, "#{self.class}: The content property \"#{key}\" is reserved."
90
- end
91
- else
92
- super
93
- end
94
- end
95
-
96
- def convert_key(key)
97
- converted = super.dup
98
- converted.gsub!(/_/, '-')
99
- converted.downcase!
100
- converted.prepend(PREFIX) unless self.class.property?(converted)
101
- converted.force_encoding(ENCODING)
102
- end
103
-
104
- def reserved?(key)
105
- RESERVED.any? { |k| k.match(key) }
106
- end
107
-
108
- def convert_value(value, _ = nil)
109
- if value.is_a?(Array)
110
- value.map { |v| convert_value(v) }
111
- else
112
- value.force_encoding(ENCODING)
113
- end
6
+ def self.property?(prop)
7
+ super && !( space_property?(prop) || space_acl?(prop) || copy_content_property?(prop) )
114
8
  end
115
9
 
116
10
  end
@@ -0,0 +1,9 @@
1
+ require "duracloud/request"
2
+
3
+ module Duracloud
4
+ class DurastoreRequest < Request
5
+ def base_path
6
+ '/durastore/'
7
+ end
8
+ end
9
+ end
@@ -45,14 +45,4 @@ module Duracloud
45
45
  response.plain_text? && response.has_body?
46
46
  end
47
47
  end
48
-
49
- # class StoreContentErrorHandler < ErrorHandler
50
- # def handle_400
51
- # InvalidContentIDError
52
- # end
53
-
54
- # def handle_409
55
- # ChecksumError
56
- # end
57
- # end
58
48
  end
@@ -0,0 +1,45 @@
1
+ module Duracloud
2
+ module HasProperties
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ include Persistence
7
+
8
+ before_delete :reset_properties
9
+ after_save :reset_properties
10
+ end
11
+ end
12
+
13
+ def properties
14
+ load_properties if persisted? && @properties.nil?
15
+ @properties ||= properties_class.new
16
+ end
17
+
18
+ def load_properties
19
+ response = get_properties_response
20
+ self.properties = response.headers
21
+ yield response if block_given?
22
+ persisted!
23
+ end
24
+
25
+ private
26
+
27
+ def properties=(props)
28
+ filtered = props ? properties_class.filter(props) : props
29
+ @properties = properties_class.new(filtered)
30
+ end
31
+
32
+ def reset_properties
33
+ @properties = nil
34
+ end
35
+
36
+ def properties_class
37
+ Properties
38
+ end
39
+
40
+ def get_properties_response
41
+ raise NotImplementedError, "Class must implement `get_properties_response`."
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,59 @@
1
+ require "active_model/callbacks"
2
+
3
+ module Duracloud
4
+ module Persistence
5
+
6
+ def self.included(base)
7
+ base.class_eval do
8
+ extend ActiveModel::Callbacks
9
+
10
+ define_model_callbacks :save, :delete
11
+ end
12
+ end
13
+
14
+ def save
15
+ raise Error, "Cannot save deleted #{self.class}." if deleted?
16
+ run_callbacks :save do
17
+ do_save
18
+ persisted!
19
+ end
20
+ end
21
+
22
+ def delete
23
+ raise Error, "Cannot delete, already deleted." if deleted?
24
+ run_callbacks :delete do
25
+ do_delete
26
+ deleted!
27
+ freeze
28
+ end
29
+ end
30
+
31
+ def persisted?
32
+ !!@persisted
33
+ end
34
+
35
+ def deleted?
36
+ !!@deleted
37
+ end
38
+
39
+ private
40
+
41
+ def persisted!
42
+ @persisted = true
43
+ end
44
+
45
+ def deleted!
46
+ @deleted = true
47
+ @persisted = false
48
+ end
49
+
50
+ def do_delete
51
+ raise NotImplementedError, "Classes must implement `do_delete`."
52
+ end
53
+
54
+ def do_save
55
+ raise NotImplementedError, "Classes must implement `do_save`."
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,108 @@
1
+ require 'hashie'
2
+ require 'time'
3
+ require 'date'
4
+
5
+ module Duracloud
6
+ # @abstract
7
+ class Properties < Hashie::Mash
8
+
9
+ PREFIX = "x-dura-meta-".freeze
10
+
11
+ # Space properties
12
+ SPACE = /\A#{PREFIX}space-(count|created)\z/
13
+
14
+ # Space ACL headers
15
+ SPACE_ACLS = /\A#{PREFIX}acl-/
16
+
17
+ # Copy Content headers
18
+ COPY_CONTENT = /\A#{PREFIX}copy-source(-store)\z/
19
+
20
+ # DuraCloud internal content properties
21
+ INTERNAL = /\A#{PREFIX}content-(mimetype|size|checksum|modified)\z/
22
+
23
+ # Properties set by the DuraCloud SyncTool
24
+ SYNCTOOL = /\A#{PREFIX}(creator|(content-file-(created|modified|last-accessed-path)))\z/
25
+
26
+ def self.property?(prop)
27
+ duraspace_property?(prop) && !internal_property?(prop)
28
+ end
29
+
30
+ def self.filter(hsh)
31
+ hsh.select { |k, v| property?(k) }
32
+ end
33
+
34
+ def self.duraspace_property?(prop)
35
+ prop.start_with?(PREFIX)
36
+ end
37
+
38
+ def self.internal_property?(prop)
39
+ INTERNAL =~ prop
40
+ end
41
+
42
+ def self.space_property?(prop)
43
+ SPACE =~ prop
44
+ end
45
+
46
+ def self.space_acl?(prop)
47
+ SPACE_ACLS =~ prop
48
+ end
49
+
50
+ def self.copy_content_property?(prop)
51
+ COPY_CONTENT =~ prop
52
+ end
53
+
54
+ def property?(prop)
55
+ self.class.property?(prop)
56
+ end
57
+
58
+ def regular_writer(key, value)
59
+ if property?(key)
60
+ super
61
+ else
62
+ raise Error, "#{self.class}: Unrecognized or restricted property \"#{key}\"."
63
+ end
64
+ end
65
+
66
+ def convert_key(key)
67
+ force_ascii(duraspace_property!(super))
68
+ end
69
+
70
+ def convert_value(value, _ = nil)
71
+ case value
72
+ when Array
73
+ convert_array(value)
74
+ when Time
75
+ value.utc.iso8601
76
+ when DateTime
77
+ convert_value(value.to_time)
78
+ else
79
+ force_ascii(value.to_s)
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ # coerce to a Duraspace property
86
+ def duraspace_property!(prop)
87
+ prop.dup.tap do |p|
88
+ p.gsub!(/_/, '-')
89
+ p.downcase!
90
+ p.prepend(PREFIX) unless self.class.duraspace_property?(p)
91
+ end
92
+ end
93
+
94
+ def convert_array(value)
95
+ value.uniq!
96
+ if value.length > 1
97
+ value.map { |v| convert_value(v) }
98
+ else
99
+ convert_value(value.first)
100
+ end
101
+ end
102
+
103
+ def force_ascii(str)
104
+ str.ascii_only? ? str : str.force_encoding("US-ASCII")
105
+ end
106
+
107
+ end
108
+ end
@@ -1,25 +1,34 @@
1
- require_relative "connection"
2
- require_relative "response"
3
- require_relative "request_options"
1
+ require "duracloud/connection"
2
+ require "duracloud/response"
4
3
 
5
4
  module Duracloud
6
5
  class Request
7
- attr_reader :client, :url, :http_method, :options
6
+ attr_reader :client, :url, :http_method, :body, :headers, :query
8
7
 
9
- def initialize(client, http_method, url, **options)
8
+ # @param client [Duracloud::Client] the client
9
+ # @param http_method [Symbol] the lower-case symbol corresponding to HTTP method
10
+ # @param url [String] relative or absolute URL
11
+ # @param body [String] the body of the request
12
+ # @param headers [Hash] HTTP headers
13
+ # @param query [Hash] Query string parameters
14
+ def initialize(client, http_method, url, body: nil, headers: nil, query: nil)
10
15
  @client = client
11
16
  @http_method = http_method
12
17
  @url = url
13
- @options = RequestOptions.new(**options)
18
+ @body = body
19
+ @headers = headers
20
+ @query = query
14
21
  end
15
22
 
16
23
  def execute
17
- original_response = connection.send(http_method,
18
- url,
19
- body: options.payload,
20
- query: options.query,
21
- header: options.headers)
22
- response_class.new(original_response)
24
+ begin
25
+ original_response = connection.send(http_method,
26
+ url,
27
+ body: body,
28
+ query: query,
29
+ header: headers)
30
+ Response.new(original_response)
31
+ end
23
32
  end
24
33
 
25
34
  private
@@ -28,10 +37,6 @@ module Duracloud
28
37
  '/'
29
38
  end
30
39
 
31
- def response_class
32
- Response
33
- end
34
-
35
40
  def connection
36
41
  @connection ||= Connection.new(client, base_path)
37
42
  end
@@ -1,22 +1,9 @@
1
1
  require "forwardable"
2
- require_relative "error_handler"
3
2
 
4
3
  module Duracloud
5
4
  class Response
6
5
  extend Forwardable
7
6
 
8
- # class << self
9
- # def error_handler
10
- # @error_handler ||=
11
- # begin
12
- # class_name = self.name.split(/::/).last.sub(/Response\z/, "ErrorHandler")
13
- # Duracloud.const_get(class_name)
14
- # rescue NameError
15
- # superclass.error_handler
16
- # end
17
- # end
18
- # end
19
-
20
7
  attr_reader :original_response
21
8
 
22
9
  delegate [:header, :body, :code, :ok?, :redirect?, :status, :reason] => :original_response,
@@ -24,11 +11,10 @@ module Duracloud
24
11
  [:size, :empty?] => :body
25
12
 
26
13
  def_delegator :header, :request_uri, :url
14
+ def_delegator :header, :request_method
27
15
 
28
16
  def initialize(original_response)
29
17
  @original_response = original_response
30
- #self.class.error_handler.call(self) if error?
31
- ErrorHandler.call(self) if error?
32
18
  end
33
19
 
34
20
  def error?
@@ -0,0 +1,89 @@
1
+ require "duracloud/durastore_request"
2
+
3
+ module Duracloud
4
+ module RestMethods
5
+
6
+ def get_stores
7
+ durastore :get, "stores"
8
+ end
9
+
10
+ def get_spaces
11
+ durastore :get, "spaces"
12
+ end
13
+
14
+ def get_space(space_id, query: nil)
15
+ durastore :get, space_id, query: query
16
+ end
17
+
18
+ def get_space_properties(space_id)
19
+ durastore :head, space_id
20
+ end
21
+
22
+ def get_space_acls(space_id)
23
+ durastore :head, "acl/#{space_id}"
24
+ end
25
+
26
+ def set_space_acls(space_id, acls)
27
+ durastore :post, space_id, properties: acls
28
+ end
29
+
30
+ def create_space(space_id)
31
+ durastore :put, space_id
32
+ end
33
+
34
+ def delete_space(space_id)
35
+ durastore :delete, space_id
36
+ end
37
+
38
+ def get_content(url, **options)
39
+ durastore :get, url, **options
40
+ end
41
+
42
+ def get_content_properties(url, **options)
43
+ durastore :head, url, **options
44
+ end
45
+
46
+ def set_content_properties(url, **options)
47
+ durastore :post, url, **options
48
+ end
49
+
50
+ def store_content(url, **options)
51
+ durastore :put, url, **options
52
+ end
53
+
54
+ def delete_content(url, **options)
55
+ durastore :delete, url, **options
56
+ end
57
+
58
+ def get_audit_log
59
+ raise NotImplementedError
60
+ end
61
+
62
+ def get_manifest
63
+ raise NotImplementedError
64
+ end
65
+
66
+ def get_bit_integrity_report
67
+ raise NotImplementedError
68
+ end
69
+
70
+ def get_bit_integrity_report_properties
71
+ raise NotImplementedError
72
+ end
73
+
74
+ def get_tasks
75
+ raise NotImplementedError
76
+ end
77
+
78
+ def perform_task
79
+ raise NotImplementedError
80
+ end
81
+
82
+ private
83
+
84
+ def durastore(*args)
85
+ execute(DurastoreRequest, *args)
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,104 @@
1
+ require "date"
2
+ require "nokogiri"
3
+ require "forwardable"
4
+
5
+ require "duracloud/persistence"
6
+ require "duracloud/has_properties"
7
+ require "duracloud/space_properties"
8
+ require "duracloud/space_acls"
9
+
10
+ module Duracloud
11
+ class Space
12
+ extend Forwardable
13
+ include Persistence
14
+ include HasProperties
15
+
16
+ MAX_RESULTS = 1000
17
+
18
+ def self.all
19
+ response = Client.get_spaces
20
+ doc = Nokogiri::XML(response.body)
21
+ doc.css('space').map { |s| new(s['id']) }
22
+ end
23
+
24
+ def self.create(id)
25
+ # TODO
26
+ end
27
+
28
+ def self.find(id)
29
+ space = new(id)
30
+ space.load_properties
31
+ space
32
+ end
33
+
34
+ attr_reader :id
35
+
36
+ delegate [:count, :created] => :properties
37
+
38
+ after_save :reset_acls
39
+ before_delete :reset_acls
40
+
41
+ def initialize(id)
42
+ @id = id
43
+ end
44
+
45
+ def acls
46
+ @acls ||= SpaceAcls.new(self)
47
+ end
48
+
49
+ def each(prefix: nil)
50
+ raise Error, "Space not yet persisted." unless persisted?
51
+ num = 0
52
+ while num < count
53
+ response = Client.get_space(id, query: {prefix: prefix, max_results: MAX_RESULTS})
54
+ xml = Nokogiri::XML(response.body)
55
+ content_ids = xml.css('item').map(&:text)
56
+ content_ids.each do |content_id|
57
+ yield content_id
58
+ end
59
+ num += content_ids.length
60
+ end
61
+ end
62
+
63
+ def each_item(prefix: nil)
64
+ each(prefix: prefix) do |content_id|
65
+ yield Content.find(self, content_id)
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def reset_acls
72
+ @acls = nil
73
+ end
74
+
75
+ def create
76
+ Client.create_space(id)
77
+ end
78
+
79
+ def update
80
+ Client.set_space_acls(id, acls)
81
+ end
82
+
83
+ def properties_class
84
+ SpaceProperties
85
+ end
86
+
87
+ def get_properties_response
88
+ Client.get_space_properties(id)
89
+ end
90
+
91
+ def do_delete
92
+ Client.delete_space(id)
93
+ end
94
+
95
+ def do_save
96
+ if persisted?
97
+ update
98
+ else
99
+ create
100
+ end
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,23 @@
1
+ require "delegate"
2
+ require "duracloud/properties"
3
+
4
+ module Duracloud
5
+ class SpaceAcls < Properties
6
+
7
+ def self.property?(prop)
8
+ space_acl?(prop)
9
+ end
10
+
11
+ attr_reader :space
12
+
13
+ def initialize(space)
14
+ super()
15
+ @space = space
16
+ if space.persisted?
17
+ response = Client.get_space_acls(space.id)
18
+ update SpaceAcls.filter(response.headers)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ require "date"
2
+ require "duracloud/properties"
3
+
4
+ module Duracloud
5
+ class SpaceProperties < Properties
6
+
7
+ def self.property?(prop)
8
+ space_property?(prop)
9
+ end
10
+
11
+ def count
12
+ space_count.to_i
13
+ end
14
+
15
+ def created
16
+ DateTime.parse(space_created)
17
+ rescue ArgumentError
18
+ space_created
19
+ end
20
+
21
+ end
22
+ end