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.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/README.md +59 -16
- data/Rakefile +6 -0
- data/duracloud.gemspec +7 -1
- data/lib/duracloud/client.rb +30 -26
- data/lib/duracloud/configuration.rb +13 -6
- data/lib/duracloud/connection.rb +0 -10
- data/lib/duracloud/content.rb +144 -82
- data/lib/duracloud/content_properties.rb +4 -110
- data/lib/duracloud/durastore_request.rb +9 -0
- data/lib/duracloud/error_handler.rb +0 -10
- data/lib/duracloud/has_properties.rb +45 -0
- data/lib/duracloud/persistence.rb +59 -0
- data/lib/duracloud/properties.rb +108 -0
- data/lib/duracloud/request.rb +21 -16
- data/lib/duracloud/response.rb +1 -15
- data/lib/duracloud/rest_methods.rb +89 -0
- data/lib/duracloud/space.rb +104 -0
- data/lib/duracloud/space_acls.rb +23 -0
- data/lib/duracloud/space_properties.rb +22 -0
- data/lib/duracloud/store.rb +26 -0
- data/lib/duracloud/version.rb +1 -1
- data/lib/duracloud.rb +3 -1
- data/spec/fixtures/responses/GetSpaces.xml +5 -0
- data/spec/fixtures/responses/GetStores.xml +11 -0
- data/spec/spec_helper.rb +94 -0
- data/spec/unit/client_spec.rb +9 -0
- data/spec/unit/content_spec.rb +30 -0
- data/spec/unit/properties_spec.rb +13 -0
- data/spec/unit/space_spec.rb +5 -0
- data/spec/unit/store_spec.rb +5 -0
- metadata +90 -10
- data/lib/duracloud/content_request.rb +0 -16
- data/lib/duracloud/content_response.rb +0 -11
- data/lib/duracloud/request_options.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b304d53dd688ed00817d59d286e32a5d53f88acc
|
4
|
+
data.tar.gz: 065f2f3e9f0307f83e863f8ba7b1a60b680414e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d73d05c4ef0bbd91a4928b79c2c260fdec0b755ece9a43423eac2229a56874c65e24e0e7cf8a7830dcabd02a293a0945a20a585c8bb2790493dae9a5d2be9220
|
7
|
+
data.tar.gz: 6f2edaffb35c789e94e264c4f97607129e403d6673f52dcf6a28af28719e50b82c4fe0b703555923aa417144a38962300c7e8fc11ef293cc18d8f50cbc24b13c
|
data/.rspec
ADDED
data/README.md
CHANGED
@@ -43,32 +43,71 @@ end
|
|
43
43
|
=> #<Duracloud::Client:0x007fe953a1c630 @config=#<Duracloud::Configuration host="foo.duracloud.org", port=nil, user="bob@example.com", password="******">>
|
44
44
|
```
|
45
45
|
|
46
|
-
|
46
|
+
#### Logging
|
47
47
|
|
48
|
-
|
48
|
+
By default, `Duracloud::Client` logs to `STDERR`. Use the `logger` config setting to change:
|
49
49
|
|
50
|
+
```ruby
|
51
|
+
Duracloud::Client.configure do |config|
|
52
|
+
config.logger = Rails.logger
|
53
|
+
end
|
50
54
|
```
|
51
|
-
|
52
|
-
|
53
|
-
|
55
|
+
|
56
|
+
### List Storage Providers
|
57
|
+
|
58
|
+
```
|
59
|
+
> stores = Duracloud::Store.all
|
60
|
+
=> [#<Duracloud::Store:0x007faa592e9068 @owner_id="0", @primary="0", @id="1", @provider_type="AMAZON_GLACIER">, #<Duracloud::Store:0x007faa592dbd78 @owner_id="0", @primary="1", @id="2", @provider_type="AMAZON_S3">]
|
61
|
+
|
62
|
+
> stores.first.primary?
|
63
|
+
=> false
|
64
|
+
```
|
65
|
+
|
66
|
+
### Space Methods
|
67
|
+
|
68
|
+
TODO
|
69
|
+
|
70
|
+
### Content Methods
|
71
|
+
|
72
|
+
#### Create a new content item and store it in DuraCloud
|
73
|
+
|
74
|
+
1. Initialize instance of `Duracloud::Content` and save:
|
75
|
+
|
76
|
+
```
|
77
|
+
>> new_content = Duracloud::Content.new(space_id: "rest-api-testing", id: "ark:/99999/fk4zzzz")
|
78
|
+
=> #<Duracloud::Content space_id="rest-api-testing", id="ark:/99999/fk4zzzz">
|
79
|
+
|
80
|
+
>> new_content.body = "test"
|
54
81
|
=> "test"
|
55
|
-
|
82
|
+
|
83
|
+
>> new_content.content_type = "text/plain"
|
56
84
|
=> "text/plain"
|
57
|
-
|
58
|
-
|
85
|
+
|
86
|
+
>> new_content.save
|
87
|
+
=> #<Duracloud::Content space_id="rest-api-testing", id="ark:/99999/fk4zzzz">
|
88
|
+
```
|
89
|
+
|
90
|
+
2. Create with class method `Duracloud::Content.create`:
|
91
|
+
|
92
|
+
```
|
93
|
+
>> Duracloud::Content.create(space_id: "rest-api-testing", id="ark:/99999/fk4zzzz") do |c|
|
94
|
+
c.body = "test"
|
95
|
+
c.content_type = "text/plain"
|
96
|
+
end
|
97
|
+
=> #<Duracloud::Content space_id="rest-api-testing", id="ark:/99999/fk4zzzz">
|
59
98
|
```
|
60
99
|
|
61
|
-
|
100
|
+
#### Retrieve an existing content item from DuraCloud
|
62
101
|
|
63
102
|
```ruby
|
64
|
-
Duracloud::Content.find(
|
103
|
+
Duracloud::Content.find(id: "contentID", space_id: "spaceID")
|
65
104
|
```
|
66
105
|
|
67
|
-
|
106
|
+
#### Update the properties for an item
|
68
107
|
|
69
108
|
TODO
|
70
109
|
|
71
|
-
|
110
|
+
#### Delete a content item
|
72
111
|
|
73
112
|
TODO
|
74
113
|
|
@@ -76,10 +115,14 @@ TODO
|
|
76
115
|
|
77
116
|
We endeavor to follow semantic versioning. In particular, versions < 1.0 may introduce backward-incompatible changes without notice. Use at your own risk. Version 1.0 signals a stable API.
|
78
117
|
|
79
|
-
##
|
118
|
+
## Contributing
|
80
119
|
|
81
|
-
|
120
|
+
1. Fork it ( https://github.com/[my-github-username]/duracloud-ruby-client/fork )
|
121
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
122
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
123
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
124
|
+
5. Create a new Pull Request
|
82
125
|
|
83
|
-
##
|
126
|
+
## Maintainers
|
84
127
|
|
85
|
-
|
128
|
+
* [David Chandek-Stark](https://github.com/dchandekstark) (Duke University)
|
data/Rakefile
CHANGED
data/duracloud.gemspec
CHANGED
@@ -18,9 +18,15 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.required_ruby_version = ">= 2.1"
|
22
|
+
|
21
23
|
spec.add_dependency "hashie", "~> 3.4"
|
22
24
|
spec.add_dependency "httpclient", "~> 2.7"
|
25
|
+
spec.add_dependency "activemodel", "~> 4.2"
|
26
|
+
spec.add_dependency "nokogiri", "~> 1.6"
|
23
27
|
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.4"
|
29
|
+
spec.add_development_dependency "rspec-its", "~> 1.2"
|
24
30
|
spec.add_development_dependency "bundler", "~> 1.7"
|
25
|
-
spec.add_development_dependency "rake", "~>
|
31
|
+
spec.add_development_dependency "rake", "~> 11.1"
|
26
32
|
end
|
data/lib/duracloud/client.rb
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
require "forwardable"
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
|
3
|
+
require "duracloud/configuration"
|
4
|
+
require "duracloud/connection"
|
5
|
+
require "duracloud/error_handler"
|
6
|
+
require "duracloud/rest_methods"
|
5
7
|
|
6
8
|
module Duracloud
|
7
9
|
class Client
|
8
10
|
extend Forwardable
|
11
|
+
extend RestMethods
|
12
|
+
include RestMethods
|
13
|
+
|
14
|
+
def self.execute(request_class, http_method, url, **options)
|
15
|
+
new.execute(request_class, http_method, url, **options)
|
16
|
+
end
|
9
17
|
|
10
18
|
def self.configure
|
11
19
|
yield Configuration
|
@@ -13,35 +21,31 @@ module Duracloud
|
|
13
21
|
|
14
22
|
attr_reader :config
|
15
23
|
|
16
|
-
delegate [:host, :port, :user, :password, :base_url] => :config
|
24
|
+
delegate [:host, :port, :user, :password, :base_url, :logger] => :config
|
17
25
|
|
18
26
|
def initialize(**options)
|
19
27
|
@config = Configuration.new(**options)
|
20
28
|
end
|
21
29
|
|
22
|
-
def get_content(url, **options)
|
23
|
-
execute ContentRequest, :get, url, **options
|
24
|
-
#ContentRequest.get(self, url, **options)
|
25
|
-
end
|
26
|
-
|
27
|
-
def get_content_properties(url, **options)
|
28
|
-
execute ContentRequest, :head, url, **options
|
29
|
-
end
|
30
|
-
|
31
|
-
def set_content_properties(url, **options)
|
32
|
-
execute ContentRequest, :post, url, **options
|
33
|
-
end
|
34
|
-
|
35
|
-
def store_content(url, **options)
|
36
|
-
execute ContentRequest, :put, url, **options
|
37
|
-
end
|
38
|
-
|
39
|
-
def delete_content(url, **options)
|
40
|
-
execute ContentRequest, :delete, url, **options
|
41
|
-
end
|
42
|
-
|
43
30
|
def execute(request_class, http_method, url, **options)
|
44
|
-
request_class.new(self, http_method, url, **options)
|
31
|
+
request = request_class.new(self, http_method, url, **options)
|
32
|
+
response = request.execute
|
33
|
+
handle_response(response)
|
34
|
+
response
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def handle_response(response)
|
40
|
+
logger.debug([self.class.to_s, response.request_method, response.url,
|
41
|
+
response.status, response.reason].join(' '))
|
42
|
+
if response.error?
|
43
|
+
ErrorHandler.call(response)
|
44
|
+
elsif %w(POST PUT DELETE).include?(response.request_method) &&
|
45
|
+
response.plain_text? &&
|
46
|
+
response.has_body?
|
47
|
+
logger.info(response.body)
|
48
|
+
end
|
45
49
|
end
|
46
50
|
end
|
47
51
|
end
|
@@ -1,16 +1,21 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "uri"
|
3
|
+
|
1
4
|
module Duracloud
|
2
5
|
class Configuration
|
6
|
+
|
3
7
|
class << self
|
4
|
-
attr_accessor :host, :port, :user, :password
|
8
|
+
attr_accessor :host, :port, :user, :password, :logger
|
5
9
|
end
|
6
10
|
|
7
|
-
attr_reader :host, :port, :user, :password
|
11
|
+
attr_reader :host, :port, :user, :password, :logger
|
8
12
|
|
9
|
-
def initialize(host: nil, port: nil, user: nil, password: nil)
|
13
|
+
def initialize(host: nil, port: nil, user: nil, password: nil, logger: nil)
|
10
14
|
@host = host || default(:host)
|
11
15
|
@port = port || default(:port)
|
12
16
|
@user = user || default(:user)
|
13
17
|
@password = password || default(:password)
|
18
|
+
@logger = logger || Logger.new(STDERR)
|
14
19
|
freeze
|
15
20
|
end
|
16
21
|
|
@@ -18,14 +23,16 @@ module Duracloud
|
|
18
23
|
URI::HTTPS.build(host: host, port: port)
|
19
24
|
end
|
20
25
|
|
26
|
+
def inspect
|
27
|
+
"#<#{self.class} host=#{host.inspect}, port=#{port.inspect}, user=#{user.inspect}," \
|
28
|
+
" password=\"******\", logger=#{logger.inspect}>"
|
29
|
+
end
|
30
|
+
|
21
31
|
private
|
22
32
|
|
23
33
|
def default(attr)
|
24
34
|
self.class.send(attr) || ENV["DURACLOUD_#{attr.to_s.upcase}"]
|
25
35
|
end
|
26
36
|
|
27
|
-
def inspect
|
28
|
-
"#<#{self.class} host=#{host.inspect}, port=#{port.inspect}, user=#{user.inspect}, password=\"******\">"
|
29
|
-
end
|
30
37
|
end
|
31
38
|
end
|
data/lib/duracloud/connection.rb
CHANGED
@@ -9,20 +9,10 @@ module Duracloud
|
|
9
9
|
# custom case-sensitive content property headers (x-dura-meta-*).
|
10
10
|
#
|
11
11
|
class Connection < HTTPClient
|
12
|
-
# class << self
|
13
|
-
# attr_accessor :base_path
|
14
|
-
# end
|
15
|
-
|
16
|
-
# self.base_path = '/'
|
17
|
-
|
18
12
|
def initialize(client, base_path = '/')
|
19
13
|
base_url = client.base_url + base_path
|
20
14
|
super(base_url: base_url, force_basic_auth: true)
|
21
15
|
set_auth(client.base_url, client.user, client.password)
|
22
16
|
end
|
23
17
|
end
|
24
|
-
|
25
|
-
# class DurastoreConnection < Connection
|
26
|
-
# self.base_path = '/durastore/'
|
27
|
-
# end
|
28
18
|
end
|
data/lib/duracloud/content.rb
CHANGED
@@ -1,127 +1,189 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
1
|
+
require "uri"
|
2
|
+
require "stringio"
|
3
|
+
require "active_model"
|
4
|
+
|
5
|
+
require "duracloud/content_properties"
|
6
|
+
require "duracloud/persistence"
|
7
|
+
require "duracloud/has_properties"
|
4
8
|
|
5
9
|
module Duracloud
|
6
10
|
#
|
7
11
|
# A piece of content in DuraCloud
|
8
12
|
#
|
9
13
|
class Content
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
include ActiveModel::Dirty
|
15
|
+
include Persistence
|
16
|
+
include HasProperties
|
17
|
+
|
18
|
+
after_save :changes_applied
|
19
|
+
|
20
|
+
# Find content in DuraCloud.
|
21
|
+
#
|
22
|
+
# @param id [String] the content ID
|
23
|
+
# @param space_id [String] the space ID.
|
24
|
+
# @return [Duraclound::Content] the content
|
25
|
+
# @raise [Duracloud::NotFoundError] the space or content ID does not exist.
|
26
|
+
def self.find(id:, space_id:)
|
27
|
+
new(id: id, space_id: space_id) do |content|
|
28
|
+
content.load_properties
|
15
29
|
end
|
30
|
+
end
|
16
31
|
|
17
|
-
|
18
|
-
|
32
|
+
# Store content in DuraCloud
|
33
|
+
#
|
34
|
+
# @param id [String] The content ID
|
35
|
+
# @param space_id [String] The space ID.
|
36
|
+
# @param body [String, #read] The content body
|
37
|
+
# @return [Duracloud::Content] the content
|
38
|
+
# @raise [Duracloud::NotFoundError] if the space ID does not exist
|
39
|
+
# @raise [Duracloud::Error] if the body is empty.
|
40
|
+
def self.create(id:, space_id:, body:)
|
41
|
+
new(id: id, space_id: space_id) do |content|
|
42
|
+
content.body = body
|
43
|
+
yield content if block_given?
|
44
|
+
content.save
|
19
45
|
end
|
46
|
+
end
|
20
47
|
|
21
|
-
|
22
|
-
|
23
|
-
|
48
|
+
attr_reader :id, :space_id
|
49
|
+
|
50
|
+
define_attribute_methods :content_type, :body, :md5
|
51
|
+
|
52
|
+
# Initialize a new piece of content
|
53
|
+
#
|
54
|
+
# @param id [String] The content ID
|
55
|
+
# @param space_id [String] The space ID
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# new(id: mycontent.txt", space_id: "myspace")
|
59
|
+
def initialize(id:, space_id:)
|
60
|
+
@id = id.freeze
|
61
|
+
@space_id = space_id.freeze
|
62
|
+
@body = nil
|
63
|
+
@content_type = nil
|
64
|
+
@md5 = nil
|
65
|
+
yield self if block_given?
|
24
66
|
end
|
25
67
|
|
26
|
-
|
27
|
-
|
68
|
+
def space
|
69
|
+
Space.find(space_id)
|
70
|
+
end
|
28
71
|
|
29
|
-
|
72
|
+
def inspect
|
73
|
+
"#<#{self.class} id=#{id.inspect}, space_id=#{space_id.inspect}>"
|
74
|
+
end
|
30
75
|
|
31
|
-
|
76
|
+
# @api private
|
77
|
+
# @raise [Duracloud::NotFoundError] the content does not exist in DuraCloud.
|
78
|
+
def load_body
|
79
|
+
response = Client.get_content(url)
|
80
|
+
@body = response.body # don't use setter
|
81
|
+
persisted!
|
82
|
+
end
|
32
83
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
raise Error, "Content requires either :url OR both :space_id AND :content_id."
|
39
|
-
end
|
40
|
-
options.values_at(:space_id, :content_id).join('/')
|
41
|
-
end
|
42
|
-
@url = URI(url).path # might not be exactly what we want, but it works
|
43
|
-
@body = options[:body] || options[:payload]
|
44
|
-
@md5 = options[:md5]
|
45
|
-
@content_type = options[:content_type]
|
46
|
-
self.properties = options[:properties].to_h
|
47
|
-
yield self if block_given?
|
84
|
+
def load_properties
|
85
|
+
super do |response|
|
86
|
+
# don't mark content_type as changed
|
87
|
+
@content_type = response.content_type
|
88
|
+
end
|
48
89
|
end
|
49
90
|
|
50
|
-
def
|
51
|
-
|
91
|
+
def body=(str_or_io)
|
92
|
+
val = read_string_or_io(str_or_io)
|
93
|
+
raise ArgumentError, "Cannot set body to empty string." if val.empty?
|
94
|
+
self.md5 = Digest::MD5.hexdigest(val)
|
95
|
+
body_will_change! if md5_changed?
|
96
|
+
@body = StringIO.new(val, "r")
|
52
97
|
end
|
53
98
|
|
54
|
-
def
|
55
|
-
|
99
|
+
def body
|
100
|
+
load_body if persisted? && empty?
|
101
|
+
@body
|
56
102
|
end
|
57
103
|
|
58
|
-
def
|
59
|
-
|
60
|
-
load_from_response(response)
|
61
|
-
self
|
104
|
+
def empty?
|
105
|
+
@body.nil? || @body.size == 0
|
62
106
|
end
|
63
|
-
alias_method :reload, :get
|
64
107
|
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
self
|
108
|
+
def content_type=(val)
|
109
|
+
content_type_will_change! unless val == @content_type
|
110
|
+
@content_type = val
|
69
111
|
end
|
70
|
-
alias_method :reload_properties, :get_properties
|
71
112
|
|
72
|
-
def
|
73
|
-
|
74
|
-
client.set_content_properties(url, properties: properties)
|
75
|
-
self
|
113
|
+
def content_type
|
114
|
+
@content_type
|
76
115
|
end
|
77
116
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
117
|
+
def md5
|
118
|
+
@md5
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def md5=(val)
|
124
|
+
md5_will_change! unless val == @md5
|
125
|
+
@md5 = val
|
126
|
+
end
|
127
|
+
|
128
|
+
def set_properties
|
129
|
+
headers = properties.to_h
|
130
|
+
headers["Content-Type"] = content_type if content_type_changed?
|
131
|
+
response = Client.set_content_properties(url, headers: headers)
|
132
|
+
# response.body is a text message -- log?
|
83
133
|
end
|
84
134
|
|
85
135
|
def store
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
options = {
|
92
|
-
payload: body, md5: md5, content_type: content_type, properties: properties
|
93
|
-
}
|
94
|
-
client.store_content(url, **options)
|
95
|
-
reload_properties
|
96
|
-
self
|
136
|
+
headers = { "Content-MD5" => md5,
|
137
|
+
"Content-Type" => content_type || "application/octet-stream" }
|
138
|
+
headers.merge!(properties)
|
139
|
+
response = Client.store_content(url, body: body, headers: headers)
|
140
|
+
# response.body is a text message -- log?
|
97
141
|
end
|
98
142
|
|
99
|
-
def
|
100
|
-
|
101
|
-
self.md5 = response.md5
|
102
|
-
self.content_type = response.content_type
|
103
|
-
load_properties_from_response(response)
|
143
|
+
def url
|
144
|
+
[space_id, id].join("/")
|
104
145
|
end
|
105
146
|
|
106
|
-
def
|
107
|
-
|
147
|
+
def properties_class
|
148
|
+
ContentProperties
|
108
149
|
end
|
109
150
|
|
110
|
-
def
|
111
|
-
|
151
|
+
def get_properties_response
|
152
|
+
Client.get_content_properties(url)
|
112
153
|
end
|
113
154
|
|
114
|
-
def
|
115
|
-
|
155
|
+
def do_delete
|
156
|
+
Client.delete_content(url)
|
116
157
|
end
|
117
158
|
|
118
|
-
def
|
119
|
-
|
159
|
+
def do_save
|
160
|
+
if !empty? && body_changed?
|
161
|
+
store
|
162
|
+
elsif persisted?
|
163
|
+
set_properties
|
164
|
+
else
|
165
|
+
raise Error, "Cannot store empty content."
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def read_string_or_io(str_or_io)
|
170
|
+
if str_or_io.respond_to?(:read)
|
171
|
+
read_io_like(str_or_io)
|
172
|
+
elsif str_or_io.respond_to?(:to_str)
|
173
|
+
str_or_io.to_str
|
174
|
+
else
|
175
|
+
raise ArgumentError, "IO-like or String-like argument required."
|
176
|
+
end
|
120
177
|
end
|
121
178
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
179
|
+
def read_io_like(io_like)
|
180
|
+
begin
|
181
|
+
io_like.rewind if io_like.respond_to?(:rewind)
|
182
|
+
io_like.read
|
183
|
+
ensure
|
184
|
+
io_like.rewind if io_like.respond_to?(:rewind)
|
185
|
+
end
|
125
186
|
end
|
187
|
+
|
126
188
|
end
|
127
189
|
end
|