b2-client 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 363125274c03af55134baf86b01809e5a734e95ee66387d99d11f944335dbf77
4
- data.tar.gz: df553ccde16861367d1880b707e1b9cfcaad9d6ce3b9c57d7dee29bb63fd9598
3
+ metadata.gz: 405cc51a4b94788687e8ccfa7aa774df40c29135b9ac89251405f56c32d73835
4
+ data.tar.gz: 820653192390b83efbb713f4104d739b496e75d3ae9c39dec6b99b3ac2e1af77
5
5
  SHA512:
6
- metadata.gz: 832053595b87941afbd6c04b9ff7a3497501540856e329db4346c06b0c43cf01eb45c2e8536826f03d337a430a14e9c3da68cbdb0426c45cae15ee746a50e401
7
- data.tar.gz: f8912c4ba9c8f00e0554ecf01a9f1919c154efa14ad749e42bd74891ae389529d97272f2579de467737f0eed79b6ac35e6221c38031293aaee2384761d43e57d
6
+ metadata.gz: 73a212dbb80684eca0097f9e784ab8815a559e3a810cdb448eaf74fb9ec49a594c5f3f78790870fc05ce3fe47dc0accfb62f931dce6811178b771f63884fcdd1
7
+ data.tar.gz: 7f6a23851372cd9fa317faab23fa09fcca35344e024fe8c1496c3204bffdb258e75197f1e115b45c5b5f400b0f872b738827973bb5469b2d2b11d7a1bc9c6892
data/lib/b2.rb CHANGED
@@ -5,37 +5,46 @@ require 'net/http'
5
5
  require File.expand_path('../b2/errors', __FILE__)
6
6
  require File.expand_path('../b2/file', __FILE__)
7
7
  require File.expand_path('../b2/bucket', __FILE__)
8
+ require File.expand_path('../b2/api_connection', __FILE__)
8
9
  require File.expand_path('../b2/connection', __FILE__)
9
10
  require File.expand_path('../b2/upload_chunker', __FILE__)
10
11
 
11
12
  class B2
12
13
 
13
- def initialize(account_id:, application_key:)
14
- @account_id = account_id
15
- @connection = B2::Connection.new(account_id, application_key)
14
+ def initialize(key_id: , secret: )
15
+ @connection = B2::Connection.new(key_id, secret)
16
+ end
17
+
18
+ def account_id
19
+ @connection.account_id
16
20
  end
17
21
 
18
22
  def buckets
19
23
  @connection.buckets
20
24
  end
21
25
 
26
+ def bucket(name)
27
+ bs = @connection.post('/b2api/v2/b2_list_buckets', {accountId: account_id, bucketName: name})['buckets']
28
+ B2::Bucket.new(bs.first, @connection)
29
+ end
30
+
22
31
  def file(bucket, key)
23
32
  bucket_id = @connection.lookup_bucket_id(bucket)
24
33
 
25
- file = @connection.post('/b2api/v1/b2_list_file_names', {
34
+ file = @connection.post('/b2api/v2/b2_list_file_names', {
26
35
  bucketId: bucket_id,
27
36
  startFileName: key
28
37
  })['files'].find {|f| f['fileName'] == key }
29
38
 
30
- file ? B2::File.new(file.merge({'bucketId' => bucket_id})) : nil
39
+ file ? B2::File.new(file.merge({'bucketId' => bucket_id}), @connection) : nil
31
40
  end
32
41
 
33
42
  def delete(bucket, key)
34
43
  object = file(bucket, key)
35
44
  if object
36
- @connection.post('/b2api/v1/b2_delete_file_version', {
37
- fileName: file.name,
38
- fileId: file.id
45
+ @connection.post('/b2api/v2/b2_delete_file_version', {
46
+ fileName: object.name,
47
+ fileId: object.id
39
48
  })
40
49
  else
41
50
  false
@@ -43,7 +52,7 @@ class B2
43
52
  end
44
53
 
45
54
  def get_upload_token(bucket)
46
- @connection.post("/b2api/v1/b2_get_upload_url", {
55
+ @connection.post("/b2api/v2/b2_get_upload_url", {
47
56
  bucketId: @connection.lookup_bucket_id(bucket)
48
57
  })
49
58
  end
@@ -85,11 +94,11 @@ class B2
85
94
 
86
95
 
87
96
  def download_to_file(bucket, key, filename)
88
- file = File.open(filename, 'w')
97
+ file = ::File.open(filename, 'wb')
89
98
  download(bucket, key) do |chunk|
90
99
  file << chunk
91
100
  end
92
101
  file.close
93
102
  end
94
103
 
95
- end
104
+ end
@@ -0,0 +1,101 @@
1
+ class B2
2
+ class APIConnection
3
+
4
+ attr_reader :key_id, :key_secret, :download_url
5
+
6
+ def initialize(key_id, secret)
7
+ @key_id = key_id
8
+ @key_secret = secret
9
+ end
10
+
11
+ def connect!
12
+ conn = Net::HTTP.new('api.backblazeb2.com', 443)
13
+ conn.use_ssl = true
14
+
15
+ req = Net::HTTP::Get.new('/b2api/v2/b2_authorize_account')
16
+ req.basic_auth(@key_id, @key_secret)
17
+
18
+ key_expiration = Time.now.to_i + 86_400 #24hr expiry
19
+ resp = conn.start { |http| http.request(req) }
20
+ if resp.is_a?(Net::HTTPSuccess)
21
+ resp = JSON.parse(resp.body)
22
+ else
23
+ raise "Error connecting to B2 API"
24
+ end
25
+
26
+ uri = URI.parse(resp['apiUrl'])
27
+ @connection = Net::HTTP.new(uri.host, uri.port)
28
+ @connection.use_ssl = uri.scheme == 'https'
29
+ @connection.start
30
+
31
+ @auth_token_expires_at = key_expiration
32
+ @account_id = resp['accountId']
33
+ @minimum_part_size = resp['absoluteMinimumPartSize']
34
+ @recommended_part_size = resp['recommendedPartSize']
35
+ @auth_token = resp['authorizationToken']
36
+ @download_url = resp['downloadUrl']
37
+ @buckets_cache = []
38
+ end
39
+
40
+ def account_id
41
+ return @account_id if !@account_id.nil?
42
+
43
+ connect!
44
+ @account_id
45
+ end
46
+
47
+ def disconnect!
48
+ if @connection
49
+ @connection.finish if @connection.active?
50
+ @connection = nil
51
+ end
52
+ end
53
+
54
+ def reconnect!
55
+ disconnect!
56
+ connect!
57
+ end
58
+
59
+ def authorization_token
60
+ if @auth_token_expires_at.nil? || @auth_token_expires_at <= Time.now.to_i
61
+ reconnect!
62
+ end
63
+ @auth_token
64
+ end
65
+
66
+ def active?
67
+ !@connection.nil? && @connection.active?
68
+ end
69
+
70
+ def connection
71
+ reconnect! if !active?
72
+ @connection
73
+ end
74
+
75
+ def send_request(request, body=nil, &block)
76
+ request['Authorization'] = authorization_token
77
+ request.body = (body.is_a?(String) ? body : JSON.generate(body)) if body
78
+
79
+ return_value = nil
80
+ close_connection = false
81
+ connection.request(request) do |response|
82
+ close_connection = response['Connection'] == 'close'
83
+
84
+ case response
85
+ when Net::HTTPSuccess
86
+ if block_given?
87
+ return_value = yield(response)
88
+ else
89
+ return_value = JSON.parse(response.body)
90
+ end
91
+ else
92
+ raise "Error connecting to B2 API #{response.body}"
93
+ end
94
+ end
95
+ disconnect! if close_connection
96
+
97
+ return_value
98
+ end
99
+
100
+ end
101
+ end
@@ -14,7 +14,7 @@ class B2
14
14
  end
15
15
 
16
16
  def get_upload_token
17
- @connection.post("/b2api/v1/b2_get_upload_url", { bucketId: @id })
17
+ @connection.post("/b2api/v2/b2_get_upload_url", { bucketId: @id })
18
18
  end
19
19
 
20
20
  def upload_file(key, io_or_string, mime_type: nil, sha1: nil, content_disposition: nil, info: {})
@@ -46,9 +46,19 @@ class B2
46
46
 
47
47
  B2::File.new(result, @connection)
48
48
  end
49
+
50
+ def keys(prefix: nil, delimiter: nil)
51
+ #TODO: add abilty to get all names
52
+ @connection.post('/b2api/v2/b2_list_file_names', {
53
+ bucketId: @id,
54
+ maxFileCount: 1000,
55
+ prefix: prefix,
56
+ delimiter: delimiter
57
+ })['files'].map{ |f| f['fileName'] }
58
+ end
49
59
 
50
60
  def has_key?(key)
51
- !@connection.post('/b2api/v1/b2_list_file_names', {
61
+ !@connection.post('/b2api/v2/b2_list_file_names', {
52
62
  bucketId: @id,
53
63
  startFileName: key,
54
64
  maxFileCount: 1,
@@ -57,7 +67,7 @@ class B2
57
67
  end
58
68
 
59
69
  def file(key)
60
- file = @connection.post('/b2api/v1/b2_list_file_names', {
70
+ file = @connection.post('/b2api/v2/b2_list_file_names', {
61
71
  bucketId: @id,
62
72
  startFileName: key,
63
73
  maxFileCount: 1,
@@ -80,4 +90,4 @@ class B2
80
90
  end
81
91
 
82
92
  end
83
- end
93
+ end
@@ -1,93 +1,67 @@
1
1
  require 'cgi'
2
+ require 'thread'
2
3
 
3
4
  class B2
4
5
  class Connection
5
6
 
6
- attr_reader :account_id, :application_key, :download_url
7
-
8
- def initialize(account_id, application_key)
9
- @account_id = account_id
10
- @application_key = application_key
11
- end
12
-
13
- def connect!
14
- conn = Net::HTTP.new('api.backblazeb2.com', 443)
15
- conn.use_ssl = true
7
+ def initialize(key_id, secret, pool: 5, timeout: 5)
8
+ @mutex = Mutex.new
9
+ @availability = ConditionVariable.new
10
+ @max = pool
11
+ @timeout = timeout
12
+ @free_pool = []
13
+ @used_pool = []
16
14
 
17
- req = Net::HTTP::Get.new('/b2api/v1/b2_authorize_account')
18
- req.basic_auth(account_id, application_key)
19
-
20
- key_expiration = Time.now.to_i + 86_400 #24hr expiry
21
- resp = conn.start { |http| http.request(req) }
22
- if resp.is_a?(Net::HTTPSuccess)
23
- resp = JSON.parse(resp.body)
24
- else
25
- raise "Error connecting to B2 API"
26
- end
27
-
28
- uri = URI.parse(resp['apiUrl'])
29
- @connection = Net::HTTP.new(uri.host, uri.port)
30
- @connection.use_ssl = uri.scheme == 'https'
31
- @connection.start
15
+ @key_id = key_id
16
+ @key_secret = secret
32
17
 
33
- @auth_token_expires_at = key_expiration
34
- @minimum_part_size = resp['absoluteMinimumPartSize']
35
- @recommended_part_size = resp['recommendedPartSize']
36
- @auth_token = resp['authorizationToken']
37
- @download_url = resp['downloadUrl']
38
18
  @buckets_cache = []
39
19
  end
40
20
 
41
- def disconnect!
42
- if @connection
43
- @connection.finish if @connection.active?
44
- @connection = nil
45
- end
21
+ def account_id
22
+ return @account_id if !@account_id.nil?
23
+
24
+ @account_id = with_connection { |conn| conn.account_id }
46
25
  end
47
-
48
- def reconnect!
49
- disconnect!
50
- connect!
26
+
27
+ def with_connection
28
+ conn = @mutex.synchronize do
29
+ cxn = if !@free_pool.empty?
30
+ @free_pool.shift
31
+ elsif @free_pool.size + @used_pool.size < @max
32
+ B2::APIConnection.new(@key_id, @key_secret)
33
+ else
34
+ @availability.wait(@mutex, @timeout)
35
+ @free_pool.shift || B2::APIConnection.new(@key_id, @key_secret)
36
+ end
37
+
38
+ @used_pool << cxn
39
+ cxn
40
+ end
41
+
42
+ yield conn
43
+ ensure
44
+ @mutex.synchronize do
45
+ @used_pool.delete(conn)
46
+ @free_pool << conn if conn.active?
47
+ @availability.signal()
48
+ end
51
49
  end
52
50
 
53
51
  def authorization_token
54
- if @auth_token_expires_at.nil? || @auth_token_expires_at <= Time.now.to_i
55
- reconnect!
56
- end
57
- @auth_token
52
+ with_connection { |conn| conn.authorization_token }
58
53
  end
59
54
 
60
- def active?
61
- !@connection.nil? && @connection.active?
62
- end
63
-
64
55
  def send_request(request, body=nil, &block)
65
- request['Authorization'] = authorization_token
66
- request.body = (body.is_a?(String) ? body : JSON.generate(body)) if body
67
-
68
- return_value = nil
69
- close_connection = false
70
- @connection.request(request) do |response|
71
- close_connection = response['Connection'] == 'close'
72
-
73
- case response
74
- when Net::HTTPSuccess
75
- if block_given?
76
- return_value = yield(response)
77
- else
78
- return_value = JSON.parse(response.body)
79
- end
80
- else
81
- raise "Error connecting to B2 API #{response.body}"
82
- end
83
- end
84
- @connection.finish if close_connection
85
-
86
- return_value
56
+ with_connection { |conn| conn.send_request(request, body, &block) }
57
+ end
58
+
59
+ def download_url
60
+ with_connection { |conn| conn.download_url }
87
61
  end
88
62
 
89
63
  def buckets
90
- post('/b2api/v1/b2_list_buckets', {accountId: @account_id})['buckets'].map do |b|
64
+ post('/b2api/v2/b2_list_buckets', {accountId: account_id})['buckets'].map do |b|
91
65
  B2::Bucket.new(b, self)
92
66
  end
93
67
  end
@@ -95,30 +69,30 @@ class B2
95
69
  def lookup_bucket_id(name)
96
70
  bucket = @buckets_cache.find{ |b| b.name == name }
97
71
  return bucket.id if bucket
98
-
72
+
99
73
  @buckets_cache = buckets
100
74
  @buckets_cache.find{ |b| b.name == name }&.id
101
75
  end
102
76
 
103
77
  def get_download_url(bucket, filename, expires_in: 3_600, disposition: nil)
104
- response = post("/b2api/v1/b2_get_download_authorization", {
78
+ response = post("/b2api/v2/b2_get_download_authorization", {
105
79
  bucketId: lookup_bucket_id(bucket),
106
80
  fileNamePrefix: filename,
107
81
  validDurationInSeconds: expires_in,
108
82
  b2ContentDisposition: disposition
109
83
  })
110
- url = @download_url + '/file/' + bucket + '/' + filename + "?Authorization=" + response['authorizationToken']
84
+ url = download_url + '/file/' + bucket + '/' + filename + "?Authorization=" + response['authorizationToken']
111
85
  url += "&b2ContentDisposition=#{CGI.escape(disposition)}" if disposition
112
86
  url
113
87
  end
114
88
 
115
- def download(bucket, key, to=nil, &block)
89
+ def download(bucket, key, to=nil)
116
90
  opened_file = (to && to.is_a?(String))
117
91
  to = ::File.open(to, 'wb') if to.is_a?(String)
118
92
  digestor = Digest::SHA1.new
119
93
  data = ""
120
94
 
121
- uri = URI.parse(@download_url)
95
+ uri = URI.parse(download_url)
122
96
  conn = Net::HTTP.new(uri.host, uri.port)
123
97
  conn.use_ssl = uri.scheme == 'https'
124
98
 
@@ -132,15 +106,15 @@ class B2
132
106
  digestor << chunk
133
107
  if to
134
108
  to << chunk
135
- elsif block
136
- block(chunk)
109
+ elsif block_given?
110
+ yield(chunk)
137
111
  else
138
112
  data << chunk
139
113
  end
140
114
  end
141
115
 
142
- if digestor.hexdigest != response['X-Bz-Content-Sha1']
143
- raise 'file error'
116
+ if response['X-Bz-Content-Sha1'] != 'none' && digestor.hexdigest != response['X-Bz-Content-Sha1']
117
+ rase B2::FileIntegrityError.new("SHA1 Mismatch, expected: \"#{response['X-Bz-Content-Sha1']}\", actual: \"#{digestor.hexdigest}\"")
144
118
  end
145
119
  when Net::HTTPNotFound
146
120
  raise B2::NotFound.new(JSON.parse(response.body)['message'])
@@ -164,7 +138,7 @@ class B2
164
138
  elsif to
165
139
  to.flush
166
140
  end
167
- block.nil? && to.nil? ? data : nil
141
+ !block_given? && to.nil? ? data : nil
168
142
  end
169
143
 
170
144
  def get(path, body=nil, &block)
@@ -180,4 +154,5 @@ class B2
180
154
  end
181
155
 
182
156
  end
183
- end
157
+ end
158
+
@@ -6,4 +6,7 @@ class B2
6
6
  class NotFound < Error
7
7
  end
8
8
 
9
+ class FileIntegrityError < Error
10
+ end
11
+
9
12
  end
@@ -26,7 +26,7 @@ class B2
26
26
  end
27
27
 
28
28
  def delete!
29
- @connection.post('/b2api/v1/b2_delete_file_version', {
29
+ @connection.post('/b2api/v2/b2_delete_file_version', {
30
30
  fileId: @id,
31
31
  fileName: @name
32
32
  })
@@ -1,3 +1,3 @@
1
1
  class B2
2
- VERSION = '1.0.5'
2
+ VERSION = '1.0.6'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: b2-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-28 00:00:00.000000000 Z
11
+ date: 2020-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -36,6 +36,7 @@ files:
36
36
  - README.md
37
37
  - b2-client.gemspec
38
38
  - lib/b2.rb
39
+ - lib/b2/api_connection.rb
39
40
  - lib/b2/bucket.rb
40
41
  - lib/b2/client.rb
41
42
  - lib/b2/connection.rb
@@ -62,8 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
63
  - !ruby/object:Gem::Version
63
64
  version: '0'
64
65
  requirements: []
65
- rubyforge_project:
66
- rubygems_version: 2.7.4
66
+ rubygems_version: 3.0.6
67
67
  signing_key:
68
68
  specification_version: 4
69
69
  summary: Backblaze B2 Client