b2-client 1.0.5 → 1.0.6

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.
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