ink_file_picker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d22df05e49b4463a633bb87fbb978dae55653c9f
4
+ data.tar.gz: 590c40f9b6e7f1ff9f0d2669a29e68d7f6a9f64d
5
+ SHA512:
6
+ metadata.gz: ee4b1ade5cb8f4ab41017a119384cc75029d13f11d338b9262096e3238c88ccc40cae29f6c0db0ecd39d077f666e5c7c5b4410ea6b1b972e53b2b3269f8422b8
7
+ data.tar.gz: d22c324c7ad0b417bdb0e32883c7190f00c3571ed9d68dd4e1bb0ec3c671b4096aa18b8821ff3613e1d6b8ff4cdc3877bcb1cb1cbe9627fc2245d7625dfa6e50
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ bin/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ink_file_picker.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Thorbjørn Hermansen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # InkFilePicker
2
+
3
+ Ruby API client for Ink File Picker (known as filepicker.io).
4
+
5
+
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'ink_file_picker'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install ink_file_picker
20
+
21
+
22
+
23
+ ## Usage
24
+
25
+ This client mirrors part of File Picker's JavaScript API.
26
+
27
+
28
+
29
+ ### Creating a client
30
+
31
+ ```ruby
32
+ # Create a client which will sign URLs. You may drop secret if you have
33
+ # not enabled this feature in your developer portal for your application.
34
+ client = InkFilePicker.client(key: 'you-api-key', secret: 'your-secret')
35
+ ```
36
+
37
+ ### Storing a file
38
+ ```ruby
39
+ response = client.store_file file_or_path, content_type
40
+ response = client.store_url 'http://www.example.com/img.jpg'
41
+ ```
42
+
43
+ ### Removing a file
44
+ ```ruby
45
+ response = client.remove url_or_handle_name
46
+ ```
47
+
48
+ ### Read operations
49
+ ```ruby
50
+ url = client.convert_url url_or_handle_name, w: 100, h: 100
51
+ url = client.convert_url url_or_handle_name, {w: 100, h: 100}, expiry: 10.minutes.from_now.to_i
52
+
53
+ # Adds policy and signature, if secret given when client was created.
54
+ url = client.retrieve_url url_or_handle_name
55
+ url = client.retrieve_url url_or_handle_name, expiry: 10.minutes.from_now.to_i
56
+
57
+
58
+ # Get simple stat on a file, like the Javascript client
59
+ stat = client.stat url_or_handle_name
60
+
61
+ dimentions = client.stat url_or_handle_name, {width: true, height: true}
62
+ ```
63
+
64
+ ### Errors
65
+
66
+ When making requests to the API errors may occur. `InkFilePicker::ClientError` or `InkFilePicker::ServerError` will
67
+ be raised if we are getting 4xx or 5xx responses back from File Picker. All errors inherits from `InkFilePicker::Error`.
68
+
69
+
70
+ ## Contributing
71
+
72
+ 1. Fork it (https://github.com/Skalar/ink_file_picker/fork)
73
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
74
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
75
+ 4. Push to the branch (`git push origin my-new-feature`)
76
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ink_file_picker/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ink_file_picker"
8
+ spec.version = InkFilePicker::VERSION
9
+ spec.authors = ["Thorbjørn Hermansen"]
10
+ spec.email = ["thhermansen@gmail.com"]
11
+ spec.summary = %q{Client for Ink File Picker}
12
+ #spec.description = %q{TODO: Write a longer description. Optional.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activesupport", ">= 3.2.14", "< 5"
22
+ spec.add_dependency "faraday", "~> 0.9.0"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.5"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec", "~> 2.14.1"
27
+ end
@@ -0,0 +1,21 @@
1
+ module InkFilePicker
2
+ module Assignable
3
+
4
+ def []=(name, value)
5
+ public_send "#{name}=", value
6
+ end
7
+
8
+ def [](name)
9
+ public_send name
10
+ end
11
+
12
+
13
+ private
14
+
15
+ def assign(attributes)
16
+ attributes.each_pair do |name, value|
17
+ self[name] = value
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,183 @@
1
+ require "faraday"
2
+
3
+ module InkFilePicker
4
+ class Client
5
+ attr_accessor :configuration
6
+
7
+ def initialize(configuration)
8
+ self.configuration = Configuration.new configuration
9
+ end
10
+
11
+
12
+ # Public: Store a file from given URL.
13
+ #
14
+ # url - URL to resource
15
+ # policy_attributes - If you use security policies you may send in for instance {expire: 10.minutes.from_now} here
16
+ #
17
+ # Returns a hash representing the response where you can read for instance 'url'
18
+ def store_url(url, policy_attributes = {})
19
+ params = {key: configuration.key}
20
+
21
+ add_policy_to params, from: policy_attributes, ensure_included: {call: 'store'}
22
+
23
+ response = http_connection.post configuration.store_path do |request|
24
+ request.params = params
25
+ request.body = {url: url}
26
+ end
27
+
28
+ wrap_response_or_fail_unless_success! response
29
+ end
30
+
31
+ # Public: Store a file from given local file or path.
32
+ #
33
+ # file_or_path - File or path to file
34
+ # content_type - The file's content type
35
+ # filename - The file's name, optional
36
+ # policy_attributes - If you use security policies you may send in for instance {expire: 10.minutes.from_now} here
37
+ #
38
+ # Returns a hash representing the response where you can read for instance 'url'
39
+ def store_file(file_or_path, content_type, filename = nil, policy_attributes = {})
40
+ file_upload = Faraday::UploadIO.new file_or_path, content_type, filename
41
+ params = {key: configuration.key}
42
+
43
+ add_policy_to params, from: policy_attributes, ensure_included: {call: 'store'}
44
+
45
+ response = http_connection.post configuration.store_path do |request|
46
+ request.params = params
47
+ request.body = {fileUpload: file_upload}
48
+ end
49
+
50
+ wrap_response_or_fail_unless_success! response
51
+ end
52
+
53
+ # Public: Removes a file from file picker.
54
+ #
55
+ # handle_or_url - The handle or URL to the file
56
+ # policy_attributes - If you use security policies you may send in for instance {expire: 10.minutes.from_now} here
57
+ #
58
+ # Returns boolean value
59
+ def remove(handle_or_url, policy_attributes = {})
60
+ response = http_connection.delete remove_url(handle_or_url, policy_attributes)
61
+
62
+ wrap_response_or_fail_unless_success! response
63
+ end
64
+
65
+
66
+ # Public: Returns short stat for a file
67
+ #
68
+ # handle_or_url - The handle or URL to the file
69
+ # params - Request params, like {width: true, height: true} to get width and height info. May be empty for default response
70
+ # policy_attributes - If you use security policies you may send in for instance {expire: 10.minutes.from_now} here
71
+ #
72
+ # Returns hash of headers returned from file picker or false if request was unsuccessful
73
+ def stat(handle_or_url, params = {}, policy_attributes = {})
74
+ response = http_connection.get stat_url(handle_or_url, params, policy_attributes)
75
+
76
+ wrap_response_or_fail_unless_success! response
77
+ end
78
+
79
+ # Public: Generates a you can use for removing an asset on file picker.
80
+ #
81
+ # handle_or_url - The handle or URL to the file
82
+ # policy_attributes - If you use security policies you may send in for instance {expire: 10.minutes.from_now} here
83
+ #
84
+ # Returns a URL to the converted image
85
+ def remove_url(handle_or_url, policy_attributes = {})
86
+ generate_url handle_or_url, {key: configuration.key}, policy_attributes, call: 'remove'
87
+ end
88
+
89
+ # Public: Generates a convert URL for given file.
90
+ #
91
+ # handle_or_url - The handle or URL to the file
92
+ # params - Convert params, like {w: 100, h:100}
93
+ # policy_attributes - If you use security policies you may send in for instance {expire: 10.minutes.from_now} here
94
+ #
95
+ # Returns a URL to the converted image
96
+ def convert_url(handle_or_url, params = {}, policy_attributes = {})
97
+ generate_url handle_or_url, params, policy_attributes, call: 'convert', url_action: 'convert'
98
+ end
99
+
100
+
101
+ # Public: Generates a URL for a given file
102
+ #
103
+ # handle_or_url - The handle or URL to the file
104
+ # params - Params to be added as get params, like {cache: true}
105
+ # policy_attributes - If you use security policies you may send in for instance {expire: 10.minutes.from_now} here
106
+ #
107
+ # This method is not that usefull unless you have enabled security policy
108
+ #
109
+ # Returns a URL to the image
110
+ def retrieve_url(handle_or_url, params = {}, policy_attributes = {})
111
+ generate_url handle_or_url, params, policy_attributes, call: 'read'
112
+ end
113
+
114
+
115
+ # Public: Generates a stat URL for a given file
116
+ #
117
+ # handle_or_url - The handle or URL to the file
118
+ # policy_attributes - If you use security policies you may send in for instance {expire: 10.minutes.from_now} here
119
+ #
120
+ # Returns a URL to the image you can do a HEAD request to in order to get stats
121
+ def stat_url(handle_or_url, params, policy_attributes = {})
122
+ generate_url handle_or_url, params, policy_attributes, call: 'stat', url_action: 'metadata'
123
+ end
124
+
125
+
126
+
127
+
128
+
129
+ # Public: Creates a policy with default configuration set in this client.
130
+ #
131
+ # Returns Policy object
132
+ def policy(attributes)
133
+ attributes.reverse_merge!(
134
+ secret: configuration.secret,
135
+ expiry: Time.now.to_i + configuration.default_expiry
136
+ )
137
+
138
+ Policy.new attributes
139
+ end
140
+
141
+
142
+
143
+
144
+
145
+ def http_connection
146
+ @http_connection ||= Faraday.new(url: configuration.filepicker_url) do |builder|
147
+ builder.request :multipart
148
+ builder.request :url_encoded
149
+ builder.adapter configuration.http_adapter || Faraday.default_adapter
150
+ end
151
+ end
152
+
153
+
154
+ private
155
+
156
+ def generate_url(handle_or_url, params, policy_attributes, options)
157
+ file_handle = FileHandle.new handle_or_url, configuration.cdn_url
158
+
159
+ add_policy_to params, from: policy_attributes, ensure_included: {handle: file_handle.handle, call: options[:call]}
160
+
161
+ url = UrlBuilder.new(file_url: file_handle.url, action: options[:url_action], params: params).to_s
162
+ end
163
+
164
+ def add_policy_to(params, options = {})
165
+ policy_attributes = (options[:from] || {}).merge options[:ensure_included]
166
+ params.merge! policy(policy_attributes)
167
+ end
168
+
169
+ # Private: Inspects response for error and raise a InkFilePicker error if client/server error.
170
+ def wrap_response_or_fail_unless_success!(response)
171
+ case response.status
172
+ when 200...300
173
+ Response.new response
174
+ when 400...500
175
+ fail ClientError.new response.body, response
176
+ when 500...600
177
+ fail ServerError.new response.body, response
178
+ else
179
+ fail Error, "Response was neither a success, nor within http status 400...600. Response was: '#{response.inspect}'."
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,32 @@
1
+ module InkFilePicker
2
+ class Configuration
3
+ include Assignable
4
+
5
+ API_DEFAULTS = {
6
+ secret: nil,
7
+ default_expiry: 600, # in 10 hours
8
+ cdn_url: 'https://www.filepicker.io/api/file/',
9
+ filepicker_url: 'https://www.filepicker.io',
10
+ store_path: '/api/store/S3',
11
+ http_adapter: :net_http
12
+ }
13
+
14
+ attr_accessor :key, :secret, :default_expiry, :cdn_url, :filepicker_url, :store_path, :http_adapter
15
+
16
+ def initialize(attributes = {})
17
+ assign API_DEFAULTS
18
+ assign attributes
19
+
20
+ verify!
21
+ end
22
+
23
+
24
+ private
25
+
26
+ def verify!
27
+ if key.blank?
28
+ fail ArgumentError, "An API key must be provided"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ module InkFilePicker
2
+ module ErrorWithOriginal
3
+ attr_reader :msg, :related
4
+
5
+ def initialize(msg, related)
6
+ @msg = msg
7
+ @related = related
8
+ end
9
+
10
+ def to_s
11
+ "#{self.class.name}: Message: '#{msg}'. Related object: '#{related.inspect}'."
12
+ end
13
+ alias inspect to_s
14
+ end
15
+
16
+
17
+ # Public: Base class for errors related to InkFilePicker.
18
+ class Error < StandardError
19
+ end
20
+
21
+ # Public: Got a request error back trying to do a request
22
+ #
23
+ # This includes wire errors like timeouts etc, and server errors
24
+ # like 5xx. Inspect error_or_response for more information.
25
+ #
26
+ class ServerError < Error
27
+ include ErrorWithOriginal
28
+ end
29
+
30
+ # Public: Got an error where the client seems to be doing something wrong
31
+ #
32
+ # These errors mainly comes from http 4xx errors.
33
+ class ClientError < Error
34
+ include ErrorWithOriginal
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ require 'uri'
2
+
3
+ module InkFilePicker
4
+ # Public: Simple class for working with file URL and file handles.
5
+ #
6
+ # Does conversions like url to handle and from a handle to url
7
+ class FileHandle
8
+ attr_accessor :handle, :url, :cdn_url
9
+
10
+ def initialize(handle_or_url, cdn_url)
11
+ self.cdn_url = cdn_url
12
+ self.handle = extract_handle handle_or_url
13
+ self.url = build_url_from_handle
14
+ end
15
+
16
+
17
+
18
+ private
19
+
20
+ def build_url_from_handle
21
+ joins_with = cdn_url.ends_with?('/') ? '' : '/'
22
+ [cdn_url, handle].join joins_with
23
+ end
24
+
25
+ def extract_handle(handle_or_url)
26
+ uri = URI.parse handle_or_url
27
+ uri.path.split('/').last
28
+ rescue URI::InvalidURIError
29
+ handle_or_url
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+
4
+ module InkFilePicker
5
+ class Policy
6
+ include Assignable
7
+
8
+ POLICY_ATTRIBUTES = %w[expiry call handle max_size min_size path container].freeze
9
+
10
+ attr_accessor :secret, *POLICY_ATTRIBUTES
11
+
12
+ def initialize(attributes)
13
+ assign attributes
14
+ end
15
+
16
+
17
+
18
+ def policy
19
+ Base64.urlsafe_encode64 policy_json
20
+ end
21
+
22
+ def signature
23
+ OpenSSL::HMAC.hexdigest 'sha256', secret, policy
24
+ end
25
+
26
+
27
+ def to_hash
28
+ return {} if secret.blank?
29
+
30
+ {
31
+ policy: policy,
32
+ signature: signature
33
+ }
34
+ end
35
+
36
+
37
+
38
+ def policy_json
39
+ out = {}
40
+
41
+ POLICY_ATTRIBUTES.each do |attr_name|
42
+ if value = self[attr_name] and value.present?
43
+ out[attr_name] = value
44
+ end
45
+ end
46
+
47
+ out.to_json
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ module InkFilePicker
2
+ # Public: Simple decorator class for response.
3
+ #
4
+ # Decorates the response with hash like access to the
5
+ # parsed body, which is expected to be JSON.
6
+ class Response
7
+ attr_reader :http_response
8
+
9
+ delegate :success?, to: :http_response
10
+
11
+ def initialize(http_response)
12
+ @http_response = http_response
13
+ end
14
+
15
+ def [](key)
16
+ parsed_body[key.to_s]
17
+ end
18
+
19
+ def parsed_body
20
+ @parsed_body ||= JSON.parse http_response.body
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module InkFilePicker
2
+ # Public: Takes a file url, adds action to the path (if any), and includes params.
3
+ class UrlBuilder
4
+ include Assignable
5
+
6
+ attr_accessor :file_url, :action, :params
7
+
8
+ def initialize(attributes = {})
9
+ assign attributes
10
+ end
11
+
12
+ def url
13
+ url = [file_url, action].compact.join '/'
14
+ url = [url, params.to_param].join '?' if params.any?
15
+
16
+ url
17
+ end
18
+ alias to_s url
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module InkFilePicker
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,25 @@
1
+ require "ink_file_picker/version"
2
+
3
+ require "active_support/all"
4
+ require "ink_file_picker/errors"
5
+
6
+ module InkFilePicker
7
+ extend ActiveSupport::Autoload
8
+
9
+ autoload :Assignable
10
+ autoload :Configuration
11
+ autoload :FileHandle
12
+ autoload :UrlBuilder
13
+ autoload :Response
14
+ autoload :Client
15
+ autoload :Policy
16
+
17
+ # Public: Creates a new Ink File Picker Client.
18
+ #
19
+ # configuration - configuration for the client with an API key
20
+ #
21
+ # Returns InkFilePicker::Client
22
+ def self.client(configuration)
23
+ Client.new configuration
24
+ end
25
+ end
Binary file
@@ -0,0 +1,410 @@
1
+ require 'spec_helper'
2
+
3
+ describe InkFilePicker::Client do
4
+ let(:attributes) do
5
+ {
6
+ key: 'key',
7
+ secret: '6U5CWAU57NAHDC2ICXQKMXYZ4Q',
8
+ http_adapter: :test
9
+ }
10
+ end
11
+
12
+ subject { described_class.new attributes }
13
+
14
+ describe "#store_url" do
15
+ let(:url) { 'https://s3.amazonaws.com/test.jpg' }
16
+ let(:response) { '{"url": "https://www.filepicker.io/api/file/WmFxB2aSe20SGT2kzSsr", "size": 234, "type": "image/jpeg", "filename": "test.jpg", "key": "WmFxB2aSe20SGT2kzSsr_test.jpg"}' }
17
+
18
+ context "without secret" do
19
+ before { subject.configuration.secret = nil }
20
+
21
+ it "posts to filepicker" do
22
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
23
+ stub.post(subject.configuration.store_path + '?key=key', {url: url}) { [200, {}, response] }
24
+ end
25
+
26
+ stubbed_connection = Faraday.new do |builder|
27
+ builder.adapter :test, stubs
28
+ end
29
+
30
+ subject.stub(:http_connection).and_return stubbed_connection
31
+
32
+ response = subject.store_url url
33
+
34
+ stubs.verify_stubbed_calls
35
+ expect(response['url']).to eq 'https://www.filepicker.io/api/file/WmFxB2aSe20SGT2kzSsr'
36
+ expect(response[:url]).to eq 'https://www.filepicker.io/api/file/WmFxB2aSe20SGT2kzSsr'
37
+ expect(response.http_response).to be_a Faraday::Response
38
+ end
39
+ end
40
+
41
+ context "with secret" do
42
+ it "includes policy and signature" do
43
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
44
+ store_path = subject.configuration.store_path + '?key=key&policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJzdG9yZSJ9&signature=60cb43bb945543d7fdbd2662ae21d5c53e28529720263619cfebc3509e820807'
45
+ stub.post(store_path, {url: url}) { [200, {}, response] }
46
+ end
47
+
48
+ stubbed_connection = Faraday.new do |builder|
49
+ builder.adapter :test, stubs
50
+ end
51
+
52
+ subject.stub(:http_connection).and_return stubbed_connection
53
+
54
+ response = subject.store_url url, expiry: 1394363896
55
+
56
+ stubs.verify_stubbed_calls
57
+ expect(response['url']).to eq 'https://www.filepicker.io/api/file/WmFxB2aSe20SGT2kzSsr'
58
+ end
59
+
60
+ it "handles client errors correctly" do
61
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
62
+ store_path = subject.configuration.store_path + '?key=key&policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJzdG9yZSJ9&signature=60cb43bb945543d7fdbd2662ae21d5c53e28529720263619cfebc3509e820807'
63
+ stub.post(store_path, {url: url}) { [403, {}, '[uuid=AF614DF7F9594A87] This action has been secured by the developer of this website. Error: The signature was not valid'] }
64
+ end
65
+
66
+ stubbed_connection = Faraday.new do |builder|
67
+ builder.adapter :test, stubs
68
+ end
69
+
70
+ subject.stub(:http_connection).and_return stubbed_connection
71
+
72
+ expect { subject.store_url url, expiry: 1394363896 }.to raise_error InkFilePicker::ClientError
73
+ end
74
+
75
+ it "handles server errors correctly" do
76
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
77
+ store_path = subject.configuration.store_path + '?key=key&policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJzdG9yZSJ9&signature=60cb43bb945543d7fdbd2662ae21d5c53e28529720263619cfebc3509e820807'
78
+ stub.post(store_path, {url: url}) { [502, {}, 'Bad Gateway'] }
79
+ end
80
+
81
+ stubbed_connection = Faraday.new do |builder|
82
+ builder.adapter :test, stubs
83
+ end
84
+
85
+ subject.stub(:http_connection).and_return stubbed_connection
86
+
87
+ expect { subject.store_url url, expiry: 1394363896 }.to raise_error InkFilePicker::ServerError
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "#store_file" do
93
+ let(:path) { File.join(File.dirname(__FILE__), '../fixtures', 'skalar.png') }
94
+ let(:file) { File.open path }
95
+ let!(:file_upload) { Faraday::UploadIO.new file, 'image/png' }
96
+ let(:response) { '{"url": "https://www.filepicker.io/api/file/WmFxB2aSe20SGT2kzSsr", "size": 234, "type": "image/jpeg", "filename": "test.jpg", "key": "WmFxB2aSe20SGT2kzSsr_test.jpg"}' }
97
+
98
+ context "without secret" do
99
+ before { subject.configuration.secret = nil }
100
+
101
+ it "uploads the given file as file" do
102
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
103
+ stub.post(subject.configuration.store_path + '?key=key', {fileUpload: file_upload}) { [200, {}, response] }
104
+ end
105
+
106
+ stubbed_connection = Faraday.new do |builder|
107
+ builder.adapter :test, stubs
108
+ end
109
+
110
+ subject.stub(:http_connection).and_return stubbed_connection
111
+ Faraday::UploadIO.stub(:new).and_return file_upload # Need same object, so request equals the stub
112
+
113
+ response = subject.store_file file, 'image/png'
114
+
115
+ stubs.verify_stubbed_calls
116
+ expect(response['url']).to eq 'https://www.filepicker.io/api/file/WmFxB2aSe20SGT2kzSsr'
117
+ end
118
+
119
+ it "uploads the given file as path" do
120
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
121
+ stub.post(subject.configuration.store_path + '?key=key', {fileUpload: file_upload}) { [200, {}, response] }
122
+ end
123
+
124
+ stubbed_connection = Faraday.new do |builder|
125
+ builder.adapter :test, stubs
126
+ end
127
+
128
+ subject.stub(:http_connection).and_return stubbed_connection
129
+ Faraday::UploadIO.stub(:new).and_return file_upload # Need same object, so request equals the stub
130
+
131
+ response = subject.store_file path, 'image/png'
132
+
133
+ stubs.verify_stubbed_calls
134
+ expect(response['url']).to eq 'https://www.filepicker.io/api/file/WmFxB2aSe20SGT2kzSsr'
135
+ end
136
+ end
137
+
138
+ context "with secret" do
139
+ it "uploads the given file as file" do
140
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
141
+ stub.post(subject.configuration.store_path + '?key=key&policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJzdG9yZSJ9&signature=60cb43bb945543d7fdbd2662ae21d5c53e28529720263619cfebc3509e820807', {fileUpload: file_upload}) { [200, {}, response] }
142
+ end
143
+
144
+ stubbed_connection = Faraday.new do |builder|
145
+ builder.adapter :test, stubs
146
+ end
147
+
148
+ subject.stub(:http_connection).and_return stubbed_connection
149
+ Faraday::UploadIO.stub(:new).and_return file_upload # Need same object, so request equals the stub
150
+
151
+ response = subject.store_file file, 'image/png', nil, expiry: 1394363896
152
+
153
+ stubs.verify_stubbed_calls
154
+ expect(response['url']).to eq 'https://www.filepicker.io/api/file/WmFxB2aSe20SGT2kzSsr'
155
+ end
156
+
157
+ it "handles client errors correctly" do
158
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
159
+ store_path = subject.configuration.store_path + '?key=key&policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJzdG9yZSJ9&signature=60cb43bb945543d7fdbd2662ae21d5c53e28529720263619cfebc3509e820807'
160
+ stub.post(store_path) { [403, {}, '[uuid=AF614DF7F9594A87] This action has been secured by the developer of this website. Error: The signature was not valid'] }
161
+ end
162
+
163
+ stubbed_connection = Faraday.new do |builder|
164
+ builder.adapter :test, stubs
165
+ end
166
+
167
+ subject.stub(:http_connection).and_return stubbed_connection
168
+
169
+ expect { subject.store_file file, 'image/png', nil, expiry: 1394363896 }.to raise_error InkFilePicker::ClientError
170
+ end
171
+
172
+ it "handles server errors correctly" do
173
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
174
+ store_path = subject.configuration.store_path + '?key=key&policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJzdG9yZSJ9&signature=60cb43bb945543d7fdbd2662ae21d5c53e28529720263619cfebc3509e820807'
175
+ stub.post(store_path) { [502, {}, 'Bad Gateway'] }
176
+ end
177
+
178
+ stubbed_connection = Faraday.new do |builder|
179
+ builder.adapter :test, stubs
180
+ end
181
+
182
+ subject.stub(:http_connection).and_return stubbed_connection
183
+
184
+ expect { subject.store_file file, 'image/png', nil, expiry: 1394363896 }.to raise_error InkFilePicker::ServerError
185
+ end
186
+ end
187
+ end
188
+
189
+ describe "#remove" do
190
+ let(:file_url) { 'https://www.filepicker.io/api/file/WmFxB2aSe20SGT2kzSsr' }
191
+
192
+ context "without secret" do
193
+ before { subject.configuration.secret = nil }
194
+
195
+ it "makes a delete request with url" do
196
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
197
+ stub.delete(file_url + '?key=key') { [200, {}, 'success'] }
198
+ end
199
+
200
+ stubbed_connection = Faraday.new do |builder|
201
+ builder.adapter :test, stubs
202
+ end
203
+
204
+ subject.stub(:http_connection).and_return stubbed_connection
205
+
206
+ response = subject.remove file_url
207
+
208
+ stubs.verify_stubbed_calls
209
+ expect(response).to be_true
210
+ end
211
+
212
+ it "makes delete request with file handle name" do
213
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
214
+ stub.delete(file_url + '?key=key') { [200, {}, 'success'] }
215
+ end
216
+
217
+ stubbed_connection = Faraday.new do |builder|
218
+ builder.adapter :test, stubs
219
+ end
220
+
221
+ subject.stub(:http_connection).and_return stubbed_connection
222
+
223
+ response = subject.remove 'WmFxB2aSe20SGT2kzSsr'
224
+
225
+ stubs.verify_stubbed_calls
226
+ expect(response).to be_true
227
+ end
228
+
229
+ it "handles server errors correctly" do
230
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
231
+ stub.delete(file_url + '?key=key') { [502, {}, 'Bad Gateway'] }
232
+ end
233
+
234
+ stubbed_connection = Faraday.new do |builder|
235
+ builder.adapter :test, stubs
236
+ end
237
+
238
+ subject.stub(:http_connection).and_return stubbed_connection
239
+
240
+ expect { subject.remove 'WmFxB2aSe20SGT2kzSsr' }.to raise_error InkFilePicker::ServerError
241
+ end
242
+ end
243
+
244
+ context "with secret" do
245
+ it "includes policy and signature" do
246
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
247
+ stub.delete(file_url + '?key=key&policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJyZW1vdmUiLCJoYW5kbGUiOiJXbUZ4QjJhU2UyMFNHVDJrelNzciJ9&signature=a557d55a680892235619ff0bec6c7254fbb8088e53a53d923b4fad1d39df3955') { [200, {}, 'success'] }
248
+ end
249
+
250
+ stubbed_connection = Faraday.new do |builder|
251
+ builder.adapter :test, stubs
252
+ end
253
+
254
+ subject.stub(:http_connection).and_return stubbed_connection
255
+
256
+ response = subject.remove file_url, expiry: 1394363896
257
+
258
+ stubs.verify_stubbed_calls
259
+ expect(response).to be_true
260
+ end
261
+ end
262
+ end
263
+
264
+ describe "#stat" do
265
+ let(:file_url) { 'https://www.filepicker.io/api/file/WmFxB2aSe20SGT2kzSsr' }
266
+
267
+ it "handles server errors correctly" do
268
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
269
+ stub.get(file_url + '/metadata') { [502, {}, 'Bad Gateway'] }
270
+ end
271
+
272
+ stubbed_connection = Faraday.new do |builder|
273
+ builder.adapter :test, stubs
274
+ end
275
+
276
+ subject.stub(:http_connection).and_return stubbed_connection
277
+
278
+ expect { subject.stat 'WmFxB2aSe20SGT2kzSsr' }.to raise_error InkFilePicker::ServerError
279
+ end
280
+
281
+ context "with secret" do
282
+ it "includes policy and signature" do
283
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
284
+ stub.get(file_url + '/metadata' + '?policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJzdGF0IiwiaGFuZGxlIjoiV21GeEIyYVNlMjBTR1Qya3pTc3IifQ%3D%3D&signature=d70d11f59750903c628f4e35ecc15ef504d71b1ed104c653fe57b2231a7d667c') do
285
+ [200, {}, '{"mimetype": "image/jpeg", "uploaded": 13.0}']
286
+ end
287
+ end
288
+
289
+ stubbed_connection = Faraday.new do |builder|
290
+ builder.adapter :test, stubs
291
+ end
292
+
293
+ subject.stub(:http_connection).and_return stubbed_connection
294
+
295
+ response = subject.stat file_url, {}, expiry: 1394363896
296
+
297
+ stubs.verify_stubbed_calls
298
+ expect(response['uploaded']).to eq 13.0
299
+ end
300
+
301
+ it "forwards get params" do
302
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
303
+ stub.get(file_url + '/metadata' + '?heigth=true&policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJzdGF0IiwiaGFuZGxlIjoiV21GeEIyYVNlMjBTR1Qya3pTc3IifQ%3D%3D&signature=d70d11f59750903c628f4e35ecc15ef504d71b1ed104c653fe57b2231a7d667c&width=true') do
304
+ [200, {}, '{"width": 100, "height": 100}']
305
+ end
306
+ end
307
+
308
+ stubbed_connection = Faraday.new do |builder|
309
+ builder.adapter :test, stubs
310
+ end
311
+
312
+ subject.stub(:http_connection).and_return stubbed_connection
313
+
314
+ response = subject.stat file_url, {width: true, heigth: true}, expiry: 1394363896
315
+
316
+ stubs.verify_stubbed_calls
317
+ expect(response['width']).to eq 100
318
+ end
319
+ end
320
+ end
321
+
322
+ describe "#convert_url" do
323
+ let(:handle) { 'PHqJHHWpRAGUsIfyx0og' }
324
+ let(:url) { "https://www.filepicker.io/api/file/#{handle}" }
325
+
326
+ context "without secret" do
327
+ before { subject.configuration.secret = nil }
328
+
329
+ it "builds expected convert URL when given a URL" do
330
+ expect(subject.convert_url url, w: 300, h: 200).to eq 'https://www.filepicker.io/api/file/PHqJHHWpRAGUsIfyx0og/convert?h=200&w=300'
331
+ end
332
+
333
+ it "builds expected convert URL when given a handle" do
334
+ expect(subject.convert_url handle, w: 300, h: 200).to eq 'https://www.filepicker.io/api/file/PHqJHHWpRAGUsIfyx0og/convert?h=200&w=300'
335
+ end
336
+ end
337
+
338
+ context "with secret" do
339
+ it "builds expected convert URL when given a URL" do
340
+ expect(subject.convert_url url, {w: 300, h: 200}, expiry: 1394363896).to eq 'https://www.filepicker.io/api/file/PHqJHHWpRAGUsIfyx0og/convert?h=200&policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJjb252ZXJ0IiwiaGFuZGxlIjoiUEhxSkhIV3BSQUdVc0lmeXgwb2cifQ%3D%3D&signature=b370d4ae604c7917c169fe5b10a6274683bb82056c7b80993a7601d486b89d22&w=300'
341
+ end
342
+ end
343
+ end
344
+
345
+ describe "#retrieve_url" do
346
+ let(:handle) { 'PHqJHHWpRAGUsIfyx0og' }
347
+ let(:url) { "https://www.filepicker.io/api/file/#{handle}" }
348
+
349
+ context "without secret" do
350
+ before { subject.configuration.secret = nil }
351
+
352
+ it "builds expected retrieve URL when given a URL" do
353
+ expect(subject.retrieve_url url).to eq 'https://www.filepicker.io/api/file/PHqJHHWpRAGUsIfyx0og'
354
+ end
355
+
356
+ it "builds expected retrieve URL when given a handle" do
357
+ expect(subject.retrieve_url handle).to eq 'https://www.filepicker.io/api/file/PHqJHHWpRAGUsIfyx0og'
358
+ end
359
+
360
+ it "can include params like cache set to true" do
361
+ expect(subject.retrieve_url handle, cache: true).to eq 'https://www.filepicker.io/api/file/PHqJHHWpRAGUsIfyx0og?cache=true'
362
+ end
363
+ end
364
+
365
+ context "with secret" do
366
+ it "builds expected retrieve URL when given a URL" do
367
+ expect(subject.retrieve_url url, {}, expiry: 1394363896).to eq 'https://www.filepicker.io/api/file/PHqJHHWpRAGUsIfyx0og?policy=eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJyZWFkIiwiaGFuZGxlIjoiUEhxSkhIV3BSQUdVc0lmeXgwb2cifQ%3D%3D&signature=6bba22df7390a44a13329d2f2ca8317c48317fe6612b21f957670969a074f778'
368
+ end
369
+ end
370
+ end
371
+
372
+
373
+
374
+ describe "#configuration" do
375
+ it "has key set" do
376
+ expect(subject.configuration.key).to eq 'key'
377
+ end
378
+
379
+ it "has secret set" do
380
+ expect(subject.configuration.secret).to eq '6U5CWAU57NAHDC2ICXQKMXYZ4Q'
381
+ end
382
+ end
383
+
384
+ describe "#policy" do
385
+ let(:policy_attributes) { {call: 'read'} }
386
+ let(:policy) { double }
387
+
388
+ describe "expiry" do
389
+ context "is given" do
390
+ it "uses given value" do
391
+ InkFilePicker::Policy.should_receive(:new).with(hash_including(call: 'read', expiry: 60)).and_return policy
392
+
393
+ expect(subject.policy policy_attributes.merge(expiry: 60)).to eq policy
394
+ end
395
+ end
396
+
397
+ context "not given" do
398
+ before { Time.stub_chain(:now, :to_i).and_return 1 }
399
+
400
+ it "uses default_expiry from config" do
401
+ subject.configuration.stub(:default_expiry).and_return 600
402
+
403
+ InkFilePicker::Policy.should_receive(:new).with(hash_including(call: 'read', expiry: 601)).and_return policy
404
+
405
+ expect(subject.policy policy_attributes).to eq policy
406
+ end
407
+ end
408
+ end
409
+ end
410
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe InkFilePicker::Configuration do
4
+ let(:attributes) do
5
+ {
6
+ key: 'key',
7
+ secret: 'secret'
8
+ }
9
+ end
10
+
11
+ subject { described_class.new attributes }
12
+
13
+ its(:key) { should eq 'key' }
14
+ its(:secret) { should eq 'secret' }
15
+ its(:default_expiry) { should eq 600 }
16
+ its(:cdn_url) { should eq 'https://www.filepicker.io/api/file/' }
17
+
18
+ describe "#initialize" do
19
+ it "fails when no key is given" do
20
+ expect { described_class.new }.to raise_error ArgumentError
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe InkFilePicker::FileHandle do
4
+ let(:cdn_url) { 'https://www.filepicker.io/api/file/' }
5
+ let(:handle) { 'PHqJHHWpRAGUsIfyx0og' }
6
+ let(:url) { "https://www.filepicker.io/api/file/#{handle}" }
7
+
8
+
9
+ describe "#url" do
10
+ it "url passes through if the cdn url is the same as given URL" do
11
+ expect(described_class.new(url, cdn_url).url).to eq url
12
+ end
13
+
14
+ it "builds a file URL given only a file handle" do
15
+ expect(described_class.new(handle, cdn_url).url).to eq url
16
+ end
17
+
18
+ it "ensures that we use CDN" do
19
+ expect(described_class.new(url, 'http://cdn.com/').url).to eq "http://cdn.com/#{handle}"
20
+ end
21
+ end
22
+
23
+ describe "handle" do
24
+ it "returns expected handle from URL" do
25
+ expect(described_class.new(url, cdn_url).handle).to eq handle
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe InkFilePicker::Policy do
4
+ let(:secret) { '6U5CWAU57NAHDC2ICXQKMXYZ4Q' }
5
+
6
+ subject do
7
+ described_class.new(
8
+ secret: secret,
9
+ call: 'read',
10
+ expiry: 1394363896
11
+ )
12
+ end
13
+
14
+ its(:policy) { should eq 'eyJleHBpcnkiOjEzOTQzNjM4OTYsImNhbGwiOiJyZWFkIn0=' }
15
+ its(:signature) { should eq '4c50ca71d9e123274a01eb00a7facd52069e07c2e9312517f55bf1b94447792e' }
16
+
17
+ describe "#to_hash" do
18
+ it "contains policy and signature when secret is given" do
19
+ expect(subject.to_hash).to eq({
20
+ policy: subject.policy,
21
+ signature: subject.signature
22
+ })
23
+ end
24
+
25
+ it "returns an empty hash when no secret is given" do
26
+ subject.secret = nil
27
+ expect(subject.to_hash).to eq({})
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe InkFilePicker do
4
+ describe ".client" do
5
+ it "takes given arguments and initializes a client" do
6
+ client = double
7
+ attributes = {some: 'attributes'}
8
+
9
+ InkFilePicker::Client.should_receive(:new).with(attributes).and_return client
10
+
11
+ expect(described_class.client(attributes)).to eq client
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ require 'bundler/setup'
2
+ require 'ink_file_picker'
3
+
4
+ RSpec.configure do |c|
5
+ c.treat_symbols_as_metadata_keys_with_true_values = true
6
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ink_file_picker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Thorbjørn Hermansen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.2.14
20
+ - - <
21
+ - !ruby/object:Gem::Version
22
+ version: '5'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.2.14
30
+ - - <
31
+ - !ruby/object:Gem::Version
32
+ version: '5'
33
+ - !ruby/object:Gem::Dependency
34
+ name: faraday
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ version: 0.9.0
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 0.9.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.5'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ version: '1.5'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ~>
80
+ - !ruby/object:Gem::Version
81
+ version: 2.14.1
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ version: 2.14.1
89
+ description:
90
+ email:
91
+ - thhermansen@gmail.com
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - .gitignore
97
+ - Gemfile
98
+ - LICENSE.txt
99
+ - README.md
100
+ - Rakefile
101
+ - ink_file_picker.gemspec
102
+ - lib/ink_file_picker.rb
103
+ - lib/ink_file_picker/assignable.rb
104
+ - lib/ink_file_picker/client.rb
105
+ - lib/ink_file_picker/configuration.rb
106
+ - lib/ink_file_picker/errors.rb
107
+ - lib/ink_file_picker/file_handle.rb
108
+ - lib/ink_file_picker/policy.rb
109
+ - lib/ink_file_picker/response.rb
110
+ - lib/ink_file_picker/url_builder.rb
111
+ - lib/ink_file_picker/version.rb
112
+ - spec/fixtures/skalar.png
113
+ - spec/ink_file_picker/client_spec.rb
114
+ - spec/ink_file_picker/configuration_spec.rb
115
+ - spec/ink_file_picker/file_handle_spec.rb
116
+ - spec/ink_file_picker/policy_spec.rb
117
+ - spec/ink_file_picker_spec.rb
118
+ - spec/spec_helper.rb
119
+ homepage: ''
120
+ licenses:
121
+ - MIT
122
+ metadata: {}
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubyforge_project:
139
+ rubygems_version: 2.0.0
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Client for Ink File Picker
143
+ test_files:
144
+ - spec/fixtures/skalar.png
145
+ - spec/ink_file_picker/client_spec.rb
146
+ - spec/ink_file_picker/configuration_spec.rb
147
+ - spec/ink_file_picker/file_handle_spec.rb
148
+ - spec/ink_file_picker/policy_spec.rb
149
+ - spec/ink_file_picker_spec.rb
150
+ - spec/spec_helper.rb