duracloud-client 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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