backblaze 0.1.0.pre.alpha → 0.2.0

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
  SHA1:
3
- metadata.gz: 068ed971b2bfb14ba4c2723d0ed6a8ab6e532028
4
- data.tar.gz: d318da137e35f01461104b7b80532944fcc362c5
3
+ metadata.gz: 58cfca96218c6f265a44d93fdeaf373a51431b7f
4
+ data.tar.gz: 12856ac81e879a92feb8177a17a1d15b83df8504
5
5
  SHA512:
6
- metadata.gz: 03c6c97c22963300e0deb4bfda498fbeef9e946a793a8897a21638da5a173154400ef715fd1b16f726198ee2a46bbd2f45c9c5752801c1ee25fc4b0c667fd550
7
- data.tar.gz: 37b8599f0d5b54152efd63e54c55ed4b1002dafcb68de058e17ee0c8200cbff2f7ed438e76d53e05f936f9aa21a6fc86360e65d04a88bf8d474c3e9610025a6c
6
+ metadata.gz: 02635c9ba73cbadd3610481e70bc92cd93b42a8fefa4005aaed57e3683b5c60d6a45ef641f074156a341924d515f59dab6b29fe8e25b6cc0c1f250b92dae04cb
7
+ data.tar.gz: 1d1a3dcc23ada161c464dff4f93864062ffee887400403629ff7753faab660e964b288c39e164137eccf4902a9b6d7ac2bcc24b5860ba5f71901f39ae8b170e1
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  .DS_Store
11
+ .b2_login*
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Backblaze
2
2
 
3
- The Backblaze ruby gem is an implementation of the [Backblaze B2 Cloud Storage API](https://www.backblaze.com/b2/docs/). In addition to simplifying calls, it also implements an object oriented structure for dealing with files.
3
+ The Backblaze ruby gem is an implementation of the [Backblaze B2 Cloud Storage API](https://www.backblaze.com/b2/docs/). In addition to simplifying calls, it also implements an object oriented structure for dealing with files. Calling the api through different objects will not cause each to get updated. Always assume that data retrieved is just a snapshot from when the object was retrieved.
4
4
 
5
5
  ## Installation
6
6
 
data/Rakefile CHANGED
@@ -4,3 +4,33 @@ require "rspec/core/rake_task"
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
6
  task :default => :spec
7
+
8
+ task :console do
9
+ require 'pry'
10
+ require 'backblaze'
11
+
12
+ def reload!
13
+ headers = Backblaze::B2::Base.headers
14
+ vars = Backblaze::B2.instance_variables.map{|k| [k, Backblaze::B2.instance_variable_get(k)]}.to_h
15
+ base_uri = Backblaze::B2::Base.base_uri
16
+ files = $LOADED_FEATURES.select { |feat| feat =~ /\/backblaze\// }
17
+ files.each { |file| load file }
18
+ vars.each do |key, value|
19
+ Backblaze::B2.instance_variable_set(key, value)
20
+ end
21
+ Backblaze::B2::Base.base_uri(base_uri)
22
+ Backblaze::B2::Base.headers(headers)
23
+ true
24
+ end
25
+
26
+ # Will try to auto load the first .b2_login file it finds
27
+ success = false
28
+ Dir['.b2_login*'].each do |file|
29
+ break if success
30
+ puts file
31
+ success = Backblaze::B2.credentials_file(file, raise_errors: false, logging: true)
32
+ end
33
+
34
+ ARGV.clear
35
+ Pry.start
36
+ end
data/lib/backblaze/b2.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  require "backblaze/b2/base"
2
2
  require "backblaze/b2/bucket"
3
3
  require "backblaze/b2/file"
4
+ require "backblaze/b2/file_version"
4
5
 
5
6
  module Backblaze::B2
6
7
  class << self
7
- attr_reader :account_id, :token, :api_url, :download_url
8
+ attr_reader :account_id, :token, :api_url, :download_url, :api_path
8
9
 
9
10
  ##
10
11
  # Authenticates with the server to get the authorization data. Raises an error if there is a problem
@@ -17,7 +18,8 @@ module Backblaze::B2
17
18
  options = {
18
19
  basic_auth: {username: account_id, password: application_key}
19
20
  }
20
- response = HTTParty.get("https://api.backblaze.com/b2api/v1/b2_authorize_account", options)
21
+ api_path = "/#{api_path}/".gsub(/\/+/, '/')
22
+ response = HTTParty.get("https://api.backblaze.com#{api_path}b2_authorize_account", options)
21
23
  raise Backblaze::AuthError.new(response) unless response.code == 200
22
24
 
23
25
  @account_id = response['accountId']
@@ -29,5 +31,36 @@ module Backblaze::B2
29
31
  Backblaze::B2::Base.base_uri "#{@api_url}#{api_path}"
30
32
  Backblaze::B2::Base.headers 'Authorization' => @token, 'Content-Type' => 'application/json'
31
33
  end
34
+
35
+ def credentials_file(filename, raise_errors: true, logging: false)
36
+ opts = nil
37
+ open(filename, 'r') do |f|
38
+ if ::File.extname(filename) == '.json'
39
+ require 'json'
40
+ opts = JSON.load(f)
41
+ else
42
+ require 'psych'
43
+ opts = Psych.load(f.read)
44
+ end
45
+ end
46
+ parsed = {}
47
+ [:application_key, :account_id, :api_path].each do |key|
48
+ if opts[key.to_s].is_a? String
49
+ parsed[key] = opts[key.to_s]
50
+ end
51
+ end
52
+ if [:application_key, :account_id].inject(true) { |status, key| status && !parsed[key].nil? }
53
+ puts "Attempting #{parsed[:account_id]}" if logging
54
+ login(parsed)
55
+ true
56
+ else
57
+ puts "Missing params" if logging
58
+ false
59
+ end
60
+ rescue => e
61
+ puts e if logging
62
+ raise e if raise_errors
63
+ false
64
+ end
32
65
  end
33
66
  end
@@ -23,5 +23,73 @@ module Backblaze::B2
23
23
  self.class.send(req, path, options, &block)
24
24
  end
25
25
  end
26
+
27
+ protected
28
+
29
+ def file_versions(bucket_id:, convert:, limit:, double_check_server:, file_name: nil, &block)
30
+ retreive_count = (double_check_server ? 0 : -1)
31
+ files = file_list(bucket_id: bucket_id, limit: limit, retreived: retreive_count, file_name: file_name, first_file: nil, start_field: 'startFileId'.freeze)
32
+
33
+ files.map! do |f|
34
+ if block.nil?
35
+ Backblaze::B2::FileVersion.new(f)
36
+ else
37
+ block.call(f)
38
+ end
39
+ end if convert
40
+ files.compact
41
+ end
42
+
43
+ def file_list(limit:, retreived:, first_file:, start_field:, bucket_id:, file_name: nil)
44
+ params = {'bucketId'.freeze => bucket_id}
45
+ if limit == -1
46
+ params['maxFileCount'.freeze] = 1000
47
+ elsif limit > 1000
48
+ params['maxFileCount'.freeze] = 1000
49
+ elsif limit > 0
50
+ params['maxFileCount'.freeze] = limit
51
+ else
52
+ return []
53
+ end
54
+ unless first_file.nil?
55
+ params[start_field] = first_file
56
+ end
57
+
58
+ response = post("/b2_list_file_#{start_field == 'startFileName' ? 'names' : 'versions'}", body: params.to_json)
59
+
60
+ files = response['files'.freeze]
61
+ halt = false
62
+ files.map! do |f|
63
+ if halt
64
+ nil
65
+ else
66
+ ret = Hash[f.map{|k,v| [Backblaze::Utils.underscore(k).to_sym, v]}]
67
+ if file_name && file_name != ret[:file_name]
68
+ halt = true
69
+ end
70
+ halt ? nil : ret
71
+ end
72
+ end.compact!
73
+
74
+ retreived = retreived + files.size if retreived >= 0
75
+ if limit > 0
76
+ limit = limit - (retreived >= 0 ? files.size : 1000)
77
+ limit = 0 if limit < 0
78
+ end
79
+
80
+ next_item = response[start_field.sub('start'.freeze, 'next'.freeze)]
81
+
82
+ if (limit > 0 || limit == -1) && !!next_item && !halt
83
+ files.concat file_list(
84
+ first_file: next_item,
85
+ limit: limit,
86
+ retreived: retreived,
87
+ start_field: start_field,
88
+ bucket_id: bucket_id
89
+ )
90
+ else
91
+ files
92
+ end
93
+ end
26
94
  end
27
95
  end
@@ -3,14 +3,13 @@ module Backblaze::B2
3
3
  ##
4
4
  # A class to represent the online buckets. Mostly used for file access
5
5
  class Bucket < Base
6
-
7
6
  ##
8
7
  # Creates a bucket from all of the possible parameters. This sould be rarely used and instead use a finder or creator
9
8
  # @param [#to_s] bucket_name the bucket name
10
9
  # @param [#to_s] bucket_id the bucket id
11
10
  # @param [#to_s] bucket_type the bucket publicity type
12
11
  # @param [#to_s] account_id the account to which this bucket belongs
13
- def initialize(bucket_name:, bucket_id:, bucket_type:, account_id:)
12
+ def initialize(bucket_name:, bucket_id:, bucket_type:, account_id:, cache: false)
14
13
  @bucket_name = bucket_name
15
14
  @bucket_id = bucket_id
16
15
  @bucket_type = bucket_type
@@ -66,17 +65,18 @@ module Backblaze::B2
66
65
  # @return [Array<Hash>] when convert is false
67
66
  # @note many of these methods are for the recusion
68
67
  def file_names(limit: 100, cache: false, convert: true, double_check_server: false)
69
- if cache && !@file_cache.nil?
70
- if limit <= @file_cache[:limit] && convert == @file_cache[:convert]
71
- return @file_cache[:files]
68
+ if cache && !@file_name_cache.nil?
69
+ if limit <= @file_name_cache[:limit] && convert == @file_name_cache[:convert]
70
+ return @file_name_cache[:files]
72
71
  end
73
72
  end
74
73
 
75
74
  retreive_count = (double_check_server ? 0 : -1)
76
- files = file_list(limit: limit, retreived: retreive_count, first_file: nil, start_field: 'startFileName'.freeze)
75
+ files = file_list(bucket_id: bucket_id, limit: limit, retreived: retreive_count, first_file: nil, start_field: 'startFileName'.freeze)
77
76
 
77
+ merge_params = {bucket_id: bucket_id}
78
78
  files.map! do |f|
79
- Backblaze::B2::File.new(f)
79
+ Backblaze::B2::File.new(f.merge(merge_params))
80
80
  end if convert
81
81
  if cache
82
82
  @file_name_cache = {limit: limit, convert: convert, files: files}
@@ -84,6 +84,27 @@ module Backblaze::B2
84
84
  files
85
85
  end
86
86
 
87
+ def file_versions(limit: 100, cache: false, convert: true, double_check_server: false)
88
+ if cache && !@file_versions_cache.nil?
89
+ if limit <= @file_versions_cache[:limit] && convert == @file_versions_cache[:convert]
90
+ return @file_versions_cache[:files]
91
+ end
92
+ end
93
+ file_versions = super(limit: 100, convert: convert, double_check_server: double_check_server, bucket_id: bucket_id)
94
+ files = file_versions.group_by {|version| convert ? version.file_name : version[:file_name]}
95
+ if convert
96
+ files = files.map do |name, versions|
97
+ File.new(file_name: name, bucket_id: bucket_id, versions: versions)
98
+ end
99
+ end
100
+ if cache
101
+ @file_versions_cache = {limit: limit, convert: convert, files: files}
102
+ else
103
+ @file_versions_cache = {}
104
+ end
105
+ files
106
+ end
107
+
87
108
  class << self
88
109
  ##
89
110
  # Create a bucket
@@ -102,54 +123,23 @@ module Backblaze::B2
102
123
 
103
124
  raise Backblaze::BucketError.new(response) unless response.code / 100 == 2
104
125
 
105
- params = %w(account_id bucket_id bucket_name bucket_type).map {|e| [e.to_sym, response[camelize(e)]]}.to_h
126
+ params = Hash[response.map{|k,v| [Backblaze::Utils.underscore(k).to_sym, v]}]
106
127
 
107
128
  new(params)
108
129
  end
109
- end
110
130
 
111
- private
112
-
113
- def file_list(limit:, retreived:, first_file:, start_field:)
114
- params = {'bucketId' => bucket_id}
115
- if limit == -1
116
- params['maxFileCount'.freeze] = 1000
117
- elsif limit > 1000
118
- params['maxFileCount'.freeze] = 1000
119
- elsif limit > 0
120
- params['maxFileCount'.freeze] = limit
121
- else
122
- return []
123
- end
124
- unless first_file.nil?
125
- params[start_field] = first_file
126
- end
127
-
128
- response = post("/b2_list_file_#{start_field == 'startFileName' ? 'names' : 'versions'}", body: params.to_json)
129
-
130
- files = response['files']
131
- files.map! do |f|
132
- f.map {|k, v| [underscore(k).to_sym, v]}.to_h
133
- end
134
-
135
- retreived = retreived + files.size if retreived >= 0
136
- if limit > 0
137
- limit = limit - (retreived >= 0 ? files.size : 1000)
138
- limit = 0 if limit < 0
139
- end
140
-
141
- next_item = response[start_field.sub('start', 'next')]
142
-
143
- if (limit > 0 || limit == -1) && !!next_item
144
- files.concat file_list(
145
- first_file: next_item,
146
- limit: limit,
147
- convert: convert,
148
- retreived: retreived,
149
- start_field: start_field
150
- )
151
- else
152
- files
131
+ ##
132
+ # List buckets for account
133
+ # @return [Array<Backblaze::Bucket>] buckets for this account
134
+ def buckets
135
+ body = {
136
+ accountId: Backblaze::B2.account_id
137
+ }
138
+ response = post('/b2_list_buckets', body: body.to_json)
139
+ response['buckets'].map do |bucket|
140
+ params = Hash[bucket.map{|k,v| [Backblaze::Utils.underscore(k).to_sym, v]}]
141
+ new(params)
142
+ end
153
143
  end
154
144
  end
155
145
  end
@@ -1,8 +1,83 @@
1
1
  module Backblaze::B2
2
2
  class File < Base
3
- def initialize(file_id:, file_name:, size:, account_id:)
4
- @versions = []
3
+ def initialize(file_name:, bucket_id:, versions: nil, **file_version_args)
4
+ @file_name = file_name
5
+ @bucket_id = bucket_id
6
+ if versions
7
+ @fetched_all = true
8
+ @versions = versions
9
+ else
10
+ @fetched_all = false
11
+ @versions = [FileVersion.new(file_version_args.merge(file_name: file_name))]
12
+ end
13
+ end
14
+
15
+ def [](version)
16
+ versions[version]
17
+ end
18
+
19
+ def file_name
20
+ @file_name
21
+ end
22
+ alias_method :name, :file_name
23
+
24
+ def versions
25
+ unless @fetched_all
26
+ @versions = file_versions(bucket_id: @bucket_id, convert: true, limit: -1, double_check_server: false, file_name: file_name)
27
+ @fetched_all = true
28
+ end
29
+ @versions
30
+ end
31
+
32
+ def latest
33
+ @versions.first
34
+ end
35
+
36
+ def destroy!(thread_count: 4)
37
+ versions
38
+ thread_count = @versions.length if thread_count > @versions.length || thread_count < 1
39
+ lock = Mutex.new
40
+ errors = []
41
+ threads = []
42
+ thread_count.times do
43
+ threads << Thread.new do
44
+ version = nil
45
+ loop do
46
+ lock.synchronize { version = @versions.pop }
47
+ break if version.nil?
48
+ begin
49
+ version.destroy!
50
+ rescue Backblaze::FileError => e
51
+ lock.synchronize { errors << e }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ threads.map(&:join)
57
+ @destroyed = true
58
+ if errors.any?
59
+ raise Backblaze::DestroyErrors.new(errors)
60
+ end
61
+ end
62
+
63
+ def exists?
64
+ !@destroyed
65
+ end
66
+
67
+ def method_missing(m, *args, &block)
68
+ if latest.respond_to?(m)
69
+ latest.send(m, *args, &block)
70
+ else
71
+ super
72
+ end
73
+ end
5
74
 
75
+ def respond_to?(m)
76
+ if latest.respond_to?(m)
77
+ true
78
+ else
79
+ super
80
+ end
6
81
  end
7
82
  end
8
83
  end
@@ -1,5 +1,33 @@
1
1
  module Backblaze::B2
2
2
  class FileVersion < Base
3
+ attr_reader :file_id, :size, :action, :upload_timestamp, :file_name
3
4
 
5
+ def initialize(file_id:, size:, upload_timestamp:, action:, file_name:)
6
+ @file_id = file_id
7
+ @size = size
8
+ @action = action
9
+ @file_name = file_name
10
+ @upload_timestamp = Time.at(upload_timestamp / 1000.0)
11
+ end
12
+
13
+ def get_info
14
+ unless defined?(@get_info)
15
+ response = post('/b2_get_file_info', body: {fileId: file_id}.to_json)
16
+ raise Backblaze::FileError.new(response) unless response.code == 200
17
+
18
+ @get_info = Hash[response.map{|k,v| [Backblaze::Utils.underscore(k).to_sym, v]}]
19
+ end
20
+ @get_info
21
+ end
22
+
23
+ def destroy!
24
+ response = post('/b2_delete_file_version', body: {fileName: file_name, fileId: file_id}.to_json)
25
+ raise Backblaze::FileError.new(response) unless response.code == 200
26
+ @destroyed = true
27
+ end
28
+
29
+ def exists?
30
+ !@destroyed
31
+ end
4
32
  end
5
33
  end
@@ -53,6 +53,24 @@ module Backblaze
53
53
  end
54
54
  end
55
55
 
56
+ ##
57
+ # Errors destroying file versions
58
+ class DestroyErrors < Error
59
+ ##
60
+ # Creates the Error
61
+ # @param [Array<Backblaze::FileError>] errors errors raised destroying files
62
+ def initialize(errors)
63
+ @errors = errors
64
+ end
65
+
66
+ ##
67
+ # The Backblaze B2 error messages which broke things
68
+ # @return [Array<Backblaze::FileError>] errors errors raised destroying files
69
+ def errors
70
+ @errors
71
+ end
72
+ end
73
+
56
74
  ##
57
75
  # Error class for authentication errors
58
76
  class AuthError < RequestError; end
@@ -60,4 +78,8 @@ module Backblaze
60
78
  ##
61
79
  # Error class for bucket errors
62
80
  class BucketError < RequestError; end
81
+
82
+ ##
83
+ # Error class for file errors
84
+ class FileError < RequestError; end
63
85
  end
@@ -1,3 +1,3 @@
1
1
  module Backblaze
2
- VERSION = "0.1.0-alpha"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backblaze
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre.alpha
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Winston Durand
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-10-30 00:00:00.000000000 Z
11
+ date: 2015-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -151,9 +151,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
151
151
  version: '0'
152
152
  required_rubygems_version: !ruby/object:Gem::Requirement
153
153
  requirements:
154
- - - ">"
154
+ - - ">="
155
155
  - !ruby/object:Gem::Version
156
- version: 1.3.1
156
+ version: '0'
157
157
  requirements: []
158
158
  rubyforge_project:
159
159
  rubygems_version: 2.4.5