bkblz 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7636a95e10db31242f5b3e666734c62e16f870da
4
+ data.tar.gz: 54425688a09f9dcec3d598ad9b9fc312e1cb8c42
5
+ SHA512:
6
+ metadata.gz: 73aa799368884833b533d5ae3e8f23f7dd3b4c0a8aa0126c69509420520b290bb631721860b788a66ab0d2b13839b27fcb68e064ad07adb635285234ac932eca
7
+ data.tar.gz: ba3359e74fc5b8e49da7f21ebd60a8f5e59d4ee421633a22dbd0d71428455120f16b294b72e7d012c467eac147832dddc1cbefca3e4287e9024f25fad22e5c54
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.13.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bkblz.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Erick Johnson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,141 @@
1
+ $: << 'lib'
2
+ require 'all'
3
+
4
+ # Run `ruby README.rb` for a working demo, but first add your
5
+ # applicatoin key and account id in the slots below.
6
+ Bkblz.configure do |config_map|
7
+ config_map.merge!(
8
+ :application_key => "!!! APP KEY !!!",
9
+ :account_id => "!!! ACCOUNT ID !!!",
10
+ :debug_http => false,
11
+ :log_level => :info # change this to :debug for more info
12
+ )
13
+ end
14
+
15
+ Bkblz.log.info do <<EOS
16
+ # Step 1, the block above configures some defaults (including the
17
+ # logger, which is why this is after the configure block), see
18
+ # Bkblz::Config for details. This is where to set the account_id
19
+ # and application_key.
20
+ EOS
21
+ end
22
+
23
+ if Bkblz.config.account_id.match /!!!/
24
+ Bkblz.log.error "you didn't fill in your credentials, read the comments"
25
+ exit 1
26
+ end
27
+
28
+ Bkblz.log.info do <<EOS
29
+ # Step 2, using the config above, create an authorized session. All
30
+ # requests will run in the context of this session. See
31
+ # +Bkblz::V1::Session#authorize+.
32
+ EOS
33
+ end
34
+ Bkblz::V1::Session.authorize Bkblz.config do |api_session|
35
+ Bkblz.log.info "API session => #{api_session}"
36
+
37
+ Bkblz.log.info do <<EOS
38
+ # Step 3, first try to find an existing bucket named my-test-bucket,
39
+ # we'll use that if it exists. All requests in a session are sent
40
+ # through the session so that the request object gets access to the
41
+ # auth credentials.
42
+ EOS
43
+ end
44
+ buckets = api_session.send(Bkblz::V1::ListBucketsRequest.new).buckets
45
+ bucket = buckets.select { |b| b.bucket_name == "my-test-bucket" }.first
46
+ Bkblz.log.info "bucket list => #{buckets}"
47
+
48
+ Bkblz.log.info do <<EOS
49
+ # Step 4, otherwise create a new my-test-bucket
50
+ EOS
51
+ end
52
+ unless bucket
53
+ bucket = Bkblz::V1::Model::Bucket.new \
54
+ :bucket_name => "my-test-bucket",
55
+ :bucket_type => "allPrivate",
56
+ :account_id => api_session.account_id
57
+
58
+ Bkblz.log.info do <<EOS
59
+ # Step 5, pass a model to the CreateBucketRequest,
60
+ # models are just named wrappers with dynamic methods
61
+ # around the JSON responses provided back from the Bkblz
62
+ # API. See lib/bkblz/v1/models.rb for a list of defined API
63
+ # objects. See Bkblz::V1::Model::Base for how it work.
64
+ EOS
65
+ end
66
+ request = Bkblz::V1::CreateBucketRequest.new bucket
67
+ Bkblz.log.info "bucket model => #{bucket}"
68
+
69
+ # Bkblz::V1::Response objects are returned from +send+. Some
70
+ # provide a to_model method if they declare the +response_model+
71
+ # in the class definition.
72
+ bucket = api_session.send(request).to_model
73
+ Bkblz.log.info "created bucket => #{bucket.bucket_name}/#{bucket.bucket_id}"
74
+ end
75
+
76
+ Bkblz.log.info do <<EOS
77
+ # Step 6, uploading a file begins with getting a dynamic URL from the API.
78
+ EOS
79
+ end
80
+ upload_auth = api_session.send(
81
+ Bkblz::V1::GetUploadUrlRequest.new bucket.bucket_id).to_model
82
+ Bkblz.log.info "upload file URL => #{upload_auth.upload_url}"
83
+
84
+
85
+ Bkblz.log.info do <<EOS
86
+ # Step 7, use the upload_auth model (a
87
+ # Bkblz::V1::Model::UploadAuth) to upload some files.
88
+ EOS
89
+ end
90
+ 5.times do |i|
91
+ body = "some text #{i}"
92
+ file_name = "some_text_#{i}.txt"
93
+ content_type = nil
94
+
95
+ upload_file_info = api_session.send(
96
+ Bkblz::V1::UploadFileRequest.new upload_auth, body, file_name,
97
+ content_type, Time.now.to_i * 1000).to_model
98
+ Bkblz.log.info "uploaded file => #{upload_file_info.file_name}"
99
+ end
100
+
101
+ Bkblz.log.info do <<EOS
102
+ # Step 8, we uploaded 5 files above, here we'll read back out
103
+ # metadata from the first 2 files in the bucket.
104
+ EOS
105
+ end
106
+ list_files_response = api_session.send(
107
+ Bkblz::V1::ListFileVersionsRequest.new bucket, 2)
108
+ bucket_files_info = list_files_response.files
109
+ Bkblz.log.info "first 2 files => #{bucket_files_info.map(&:file_name).join "\n"}"
110
+
111
+ Bkblz.log.info do <<EOS
112
+ # Step 9, the response object returned object is a
113
+ # Bkblz::Api::PaginatedResponse. Use its +has_more?+ and
114
+ # +next_request+ methods to page through more results.
115
+ EOS
116
+ end
117
+ while list_files_response.has_more?
118
+ list_files_response = api_session.send list_files_response.next_request 100
119
+ bucket_files_info.concat list_files_response.files
120
+ Bkblz.log.info "next N files => #{list_files_response.files.map(&:file_name).join "\n"}"
121
+ end
122
+
123
+ Bkblz.log.info do <<EOS
124
+ # Step 10, delete all the files in the bucket that we added. This is
125
+ # a service requirement to deleting a bucket.
126
+ EOS
127
+ end
128
+ bucket_files_info.each do |file_info|
129
+ request = Bkblz::V1::DeleteFileVersionRequest.new file_info
130
+ delete_file_version_response = api_session.send request
131
+ Bkblz.log.info "deleted file => #{delete_file_version_response.to_model.file_name}"
132
+ end
133
+
134
+ Bkblz.log.info do <<EOS
135
+ # Step 11, delete the bucket.
136
+ EOS
137
+ end
138
+ request = Bkblz::V1::DeleteBucketRequest.new bucket
139
+ delete_bucket_response = api_session.send request
140
+ Bkblz.log.info "deleted bucket => #{bucket.bucket_name}/#{bucket.bucket_id}"
141
+ end
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ desc "Run me to get an overview of using the API"
9
+ task :readme do
10
+ require './README'
11
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bkblz/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bkblz"
8
+ spec.version = Bkblz::VERSION
9
+ spec.authors = ["Erick Johnson"]
10
+ spec.email = ["erick@vos.io"]
11
+
12
+ spec.summary = "Bkblz GEM for the Backblaze B2 API. https://www.backblaze.com/b2/docs/"
13
+ spec.description = spec.description
14
+ spec.homepage = "https://github.com/erickj/bkblz"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.13"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec", "~> 3.0"
25
+ end
@@ -0,0 +1,6 @@
1
+ require "bkblz/core_ext"
2
+ require "bkblz/map_key_formatter"
3
+
4
+ require "bkblz"
5
+ require "bkblz/v1/all"
6
+ require "bkblz/version"
@@ -0,0 +1,35 @@
1
+ require "bkblz/config"
2
+ require "bkblz/logger"
3
+
4
+ module Bkblz
5
+
6
+ BaseError = Class.new ::StandardError
7
+
8
+ class << self
9
+ def configure(&block)
10
+ @config = Bkblz::Config.configure @config, &block
11
+ config_changed
12
+ end
13
+
14
+ def config
15
+ unless @config
16
+ @config = Bkblz::Config.configure
17
+ config_changed
18
+ end
19
+ @config
20
+ end
21
+
22
+ def log
23
+ @logger ||= config_logger
24
+ end
25
+
26
+ private
27
+ def config_changed
28
+ @logger = nil
29
+ end
30
+
31
+ def config_logger
32
+ @logger = Bkblz::Logger.configure config
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ module Bkblz
2
+
3
+ class Config
4
+
5
+ CONFIG_VARS = {
6
+ :application_key => '',
7
+ :account_id => '',
8
+ :debug_http => false,
9
+
10
+ :log_device => :stderr, # [:stdout, :stderr, :devnull, path, fd]
11
+ :log_level => :warn, # [:debug, :info, :warn, :error, :fatal, (-6..-1)]
12
+ :log_colorize => true
13
+ }.freeze
14
+
15
+ attr_reader *CONFIG_VARS.keys, :config_map
16
+
17
+ def initialize(**config_map)
18
+ config_map.each do |k,v|
19
+ # allows attr_reader methods from CONFIG_VAR to work
20
+ instance_variable_set :"@#{k}", v
21
+ end
22
+
23
+ @config_map = config_map
24
+ end
25
+
26
+ class << self
27
+ def configure(config=nil, &block)
28
+ map = config ? config.config_map : CONFIG_VARS.dup
29
+ yield map if block_given?
30
+ Config.new map
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ module Bkblz
2
+ module CoreExt
3
+ refine String do
4
+ def underscore
5
+ uncamelify = self.gsub /[a-z\W][A-Z]/ do |m|
6
+ m.gsub /(^.)/, '\1_'
7
+ end
8
+ uncamelify.downcase.gsub(/[^a-z0-9]+/, '_')
9
+ end
10
+
11
+ def demodulize
12
+ self.split('::').last
13
+ end
14
+
15
+ def camelcase
16
+ self.gsub /_(.)/ do |match|
17
+ match[1].upcase
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,139 @@
1
+ require 'logger'
2
+
3
+ module Bkblz
4
+ class Logger
5
+ COLOR_MAP = {
6
+ :d => :green,
7
+ :i => :blue,
8
+ :w => :yellow,
9
+ :e => :red,
10
+ :f => :pink
11
+ }
12
+
13
+ class << self
14
+ def configure(config)
15
+ device = configure_device config.log_device
16
+
17
+ logger = ::Logger.new device
18
+ class << logger
19
+ include LoggerDebugLevels
20
+ end
21
+ raise "missing logger debug methods" unless logger.respond_to? :debug1
22
+
23
+ logger.level = case config.log_level
24
+ when Symbol, String
25
+ ::Logger.const_get config.log_level.to_s.upcase
26
+ when Integer
27
+ # Negative numbers can be used for detailed debug levels
28
+ config.log_level
29
+ end
30
+
31
+ colorize = config.log_colorize && device.tty?
32
+ logger.formatter = default_formatter colorize
33
+
34
+ if config.log_colorize && !device.tty?
35
+ logger.warn "disabled log colorization for non-tty"
36
+ end
37
+
38
+ logger
39
+ end
40
+
41
+ private
42
+ def configure_device(device_value)
43
+ case device_value
44
+ when :stderr
45
+ STDERR
46
+ when :stdout
47
+ STDOUT
48
+ when :devnull
49
+ File.open File::NULL, "w"
50
+ when Integer
51
+ IO.new device_value, 'a'
52
+ when String
53
+ File.new device_value 'a'
54
+ else
55
+ raise ConfigError, "log_device => #{device_value}"
56
+ end
57
+ end
58
+
59
+ def default_formatter(colorize)
60
+ lambda do |severity, datetime, progname, msg|
61
+ s_key = severity[0].downcase.to_sym
62
+ if colorize && COLOR_MAP[s_key]
63
+ severity = Styleizer.apply severity, COLOR_MAP[s_key]
64
+ progname = Styleizer.apply progname, :bold, :dark_gray
65
+ end
66
+
67
+ if progname
68
+ "[%s] (%s): %s\n" % [severity, progname, msg]
69
+ else
70
+ "[%s] %s\n" % [severity, msg]
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ module LoggerDebugLevels
77
+ NUM_DEBUG_LEVELS = 6
78
+
79
+ ##
80
+ # Adds methods debug1, debug2, ... debug6 for more detailed
81
+ # debug levels. Set a negative index +logger.level+ to enable
82
+ # lower levels, e.g. logger.level = -6 for debug6 messages.
83
+ (1..NUM_DEBUG_LEVELS).to_a.each do |debug_level|
84
+ debug_method_name = "debug#{debug_level}"
85
+
86
+ define_method debug_method_name do |*args, &block|
87
+ severity = debug_level * -1
88
+ return if severity < self.level
89
+
90
+ debug_at_level severity, *args, &block
91
+ end
92
+ end
93
+
94
+ def format_severity(severity)
95
+ if severity < ::Logger::DEBUG
96
+ return "D" + severity.abs.to_s
97
+ else
98
+ super(severity)[0]
99
+ end
100
+ end
101
+
102
+ private
103
+ def debug_at_level(severity, progname=nil, &block)
104
+ raise 'block required for super debug loggers' unless block_given?
105
+ raise 'progname required for super debug loggers' unless progname
106
+
107
+ add severity, nil, progname, &block
108
+ end
109
+ end
110
+
111
+ class Styleizer
112
+ class << self
113
+
114
+ STYLE_CODES = {
115
+ :bold => 1,
116
+ :red => 31,
117
+ :green => 32,
118
+ :yellow => 33,
119
+ :blue => 34,
120
+ :pink => 35,
121
+ :light_blue => 36,
122
+ :light_gray => 37,
123
+ :dark_gray => 90
124
+ }
125
+
126
+ def apply(str, *styles)
127
+ return str unless str
128
+ styles.reduce(str) { |str, style| styleize STYLE_CODES[style], str }
129
+ end
130
+
131
+ private
132
+ def styleize(color_code, str)
133
+ "\e[#{color_code}m#{str}\e[0m"
134
+ end
135
+ end
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,21 @@
1
+ module Bkblz
2
+ class MapKeyFormatter
3
+ using Bkblz::CoreExt
4
+
5
+ def self.underscore_keys(map)
6
+ modified_map = {}
7
+ map.each do |k,v|
8
+ modified_map[k.to_s.underscore.to_sym] = v
9
+ end
10
+ modified_map
11
+ end
12
+
13
+ def self.camelcase_keys(map)
14
+ modified_map = {}
15
+ map.each do |k,v|
16
+ modified_map[k.to_s.camelcase.to_sym] = v
17
+ end
18
+ modified_map
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ require_relative "model_base"
2
+ require_relative "models"
3
+
4
+ require_relative "session"
5
+ require_relative "response"
6
+ require_relative "request"
7
+ require_relative "error_response"
8
+
9
+ require_relative "authorize_account"
10
+ require_relative "list_buckets"
11
+ require_relative "create_bucket"
12
+ require_relative "delete_bucket"
13
+ require_relative "get_upload_url"
14
+ require_relative "upload_file"
15
+ require_relative "list_file_versions"
16
+ require_relative "delete_file_version"
@@ -0,0 +1,24 @@
1
+ module Bkblz
2
+ module V1
3
+ class AuthorizeAccountResponse < Response
4
+ response_accessors :account_id,
5
+ :api_url,
6
+ :authorization_token,
7
+ :download_url,
8
+ :minimum_part_size
9
+ end
10
+
11
+ class AuthorizeAccountRequest < Request
12
+
13
+ API_URL = "https://api.backblaze.com/b2api/v1/b2_authorize_account"
14
+
15
+ response_class AuthorizeAccountResponse
16
+
17
+ def build_request(session)
18
+ req = Net::HTTP::Get.new URI(API_URL)
19
+ req.basic_auth(session.config.account_id, session.config.application_key)
20
+ req
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module Bkblz
2
+ module V1
3
+
4
+ class CreateBucketResponse < Response
5
+ response_model Model::Bucket
6
+ end
7
+
8
+ class CreateBucketRequest < Request
9
+
10
+ response_class CreateBucketResponse
11
+ url_suffix "/b2api/v1/b2_create_bucket"
12
+
13
+ def initialize(bucket_model)
14
+ @body = bucket_model.to_map
15
+ end
16
+
17
+ def build_request(session)
18
+ session.create_post url(session), @body
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ module Bkblz
2
+ module V1
3
+
4
+ class DeleteBucketResponse < Response
5
+ response_model Model::Bucket
6
+ end
7
+
8
+ class DeleteBucketRequest < Request
9
+
10
+ response_class DeleteBucketResponse
11
+ url_suffix "/b2api/v1/b2_delete_bucket"
12
+
13
+ def initialize(bucket_model)
14
+ @body = {:bucket_id => bucket_model.bucket_id,
15
+ :account_id => bucket_model.account_id}
16
+ end
17
+
18
+ def build_request(session)
19
+ session.create_post url(session), @body
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Bkblz
2
+ module V1
3
+
4
+ class DeleteFileVersionResponse < Response
5
+ response_model Model::PartialFileInfo
6
+ end
7
+
8
+ class DeleteFileVersionRequest < Request
9
+
10
+ response_class DeleteFileVersionResponse
11
+ url_suffix "/b2api/v1/b2_delete_file_version"
12
+
13
+ def initialize(short_file_info)
14
+ @body = {:file_name => short_file_info.file_name,
15
+ :file_id => short_file_info.file_id}
16
+ end
17
+
18
+ def build_request(session)
19
+ session.create_post url(session), @body
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ module Bkblz
2
+ module V1
3
+ class ErrorResponse < Response
4
+
5
+ response_model V1::Model::Error
6
+
7
+ ERROR_TYPE = {
8
+ 400 => :BAD_REQUEST,
9
+ 401 => :UNAUTHORIZED,
10
+ 403 => :FORBIDDEN,
11
+ 408 => :REQUEST_TIMEOUT,
12
+ 429 => :TOO_MANY_REQUESTS,
13
+ 500 => :INTERNAL_ERROR,
14
+ 503 => :SERVICE_UNAVAILABLE
15
+ }
16
+
17
+ def message
18
+ model = self.to_model
19
+ error_type = ERROR_TYPE[model.status]
20
+ "[#{model.status}:#{error_type}:#{model.code}] #{model.message}"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module Bkblz
2
+ module V1
3
+
4
+ class GetUploadUrlResponse < Response
5
+ response_model Model::UploadAuth
6
+ end
7
+
8
+ class GetUploadUrlRequest < Request
9
+
10
+ response_class GetUploadUrlResponse
11
+ url_suffix "/b2api/v1/b2_get_upload_url"
12
+
13
+ def initialize(bucket_id)
14
+ @body = {:bucket_id => bucket_id}
15
+ end
16
+
17
+ def build_request(session)
18
+ session.create_post url(session), @body
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ module Bkblz
2
+ module V1
3
+
4
+ class ListBucketsResponse < Response
5
+ response_accessor :buckets, Model::Bucket
6
+ end
7
+
8
+ class ListBucketsRequest < Request
9
+
10
+ response_class ListBucketsResponse
11
+ url_suffix "/b2api/v1/b2_list_buckets"
12
+
13
+ def build_request(session)
14
+ session.create_post url(session), :account_id => session.account_id
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,43 @@
1
+ module Bkblz
2
+ module V1
3
+
4
+ class ListFileVersionsResponse < PaginatedResponse
5
+ response_accessor :files, Model::FileInfo
6
+ pagination_accessors :next_file_name, :next_file_id
7
+
8
+ def build_next_request(limit)
9
+ bucket = original_request.bucket
10
+ ListFileVersionsRequest.new bucket, limit, self
11
+ end
12
+ end
13
+
14
+ class ListFileVersionsRequest < Request
15
+
16
+ response_class ListFileVersionsResponse
17
+ url_suffix "/b2api/v1/b2_list_file_versions"
18
+
19
+ attr_reader :bucket
20
+
21
+ def initialize(bucket, max_file_count=1000, paginate_from=nil)
22
+ @bucket = bucket
23
+ @body = {}
24
+ @body[:bucket_id] = bucket.bucket_id
25
+ @body[:max_file_count] = max_file_count
26
+
27
+ if paginate_from
28
+ raise 'invalid paginator' unless paginate_from.is_a? ListFileVersionsResponse
29
+
30
+ next_file_name = paginate_from.next_file_name
31
+ next_file_id = paginate_from.next_file_id
32
+
33
+ @body[:start_file_name] = next_file_name if next_file_name
34
+ @body[:start_file_id] = next_file_id if next_file_id
35
+ end
36
+ end
37
+
38
+ def build_request(session)
39
+ session.create_post url(session), @body
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ module Bkblz
2
+ module V1
3
+ module Model
4
+
5
+ def self.define(*fields)
6
+ model_klass = Class.new BaseModel
7
+ model_klass.field_accessors *fields
8
+ model_klass
9
+ end
10
+
11
+ class BaseModel
12
+
13
+ class << self
14
+ def field_accessors(*fields)
15
+ fields.each do |field|
16
+ define_method field do |*args|
17
+ @map[field]
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def initialize(map)
24
+ @map = map
25
+ end
26
+
27
+ def to_map
28
+ @map.dup
29
+ end
30
+
31
+ def to_s
32
+ "#<%s:%d %s>" % [self.class.name, __id__, @map]
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ module Bkblz
2
+ module V1
3
+ module Model
4
+
5
+ # Returned by list_buckets, create_bucket, delete_bucket
6
+ Bucket = Model.define :account_id, :bucket_id, :bucket_name, :bucket_type
7
+
8
+ # Returned by list_file_versions
9
+ File = Model.define *[
10
+ :action, :content_length, :file_id, :file_name, :size, :upload_timestamp
11
+ ]
12
+
13
+ # Returned by upload_file
14
+ FileInfo = Model.define *[
15
+ :account_id, :bucket_id, :content_length, :content_sha1, :content_type,
16
+ :file_id, :file_info, :file_name
17
+ ]
18
+
19
+ # Returned by delete_file_version
20
+ PartialFileInfo = Model.define :file_id, :file_name
21
+
22
+ # Returned by get_upload_url
23
+ UploadAuth = Model.define :bucket_id, :upload_url, :authorization_token
24
+
25
+ # Possibly returned by any request
26
+ Error = Model.define :status, :code, :message
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,81 @@
1
+ require 'net/http'
2
+
3
+ module Bkblz
4
+ module V1
5
+
6
+ TooManyRedirectError = Class.new Bkblz::BaseError
7
+
8
+ class RequestError < Bkblz::BaseError
9
+ def initialize(error_response)
10
+ super error_response.message
11
+ end
12
+ end
13
+
14
+ class Request
15
+
16
+ class << self
17
+ def response_class(klass=nil)
18
+ @response_class = klass unless klass.nil?
19
+ @response_class
20
+ end
21
+
22
+ def url_suffix(suffix=nil)
23
+ @url_suffix = suffix unless suffix.nil?
24
+ @url_suffix
25
+ end
26
+ end
27
+
28
+ def send(session)
29
+ request = build_request session
30
+ Bkblz.log.debug { "sending request => #{request} to URI => #{request.uri}" }
31
+ http = Net::HTTP.new(request.uri.host, request.uri.port)
32
+ http.use_ssl = true
33
+ http.set_debug_output(STDERR) if session.config.debug_http
34
+
35
+ build_response fetch(http, request)
36
+ end
37
+
38
+ protected
39
+
40
+ def build_request(session)
41
+ raise 'not implemented'
42
+ end
43
+
44
+ def build_response(response)
45
+ unless response.kind_of? Net::HTTPSuccess
46
+ error_response = ErrorResponse.new response, self
47
+ raise RequestError.new error_response
48
+ end
49
+ Bkblz.log.debug { "#build_response => #{response}" }
50
+
51
+ response_class = self.class.response_class || Response
52
+ response_class.new response, self
53
+ end
54
+
55
+ def url(session)
56
+ raise "no URL suffix for #{self.class}" unless self.class.url_suffix
57
+ session.create_url(self.class.url_suffix)
58
+ end
59
+
60
+ private
61
+
62
+ def fetch(http, request, limit=10)
63
+ # You should choose a better exception.
64
+ raise TooManyRedirectError, 'too many HTTP redirects' if limit == 0
65
+
66
+ response = http.start { |http| http.request(request) }
67
+
68
+ case response
69
+ when Net::HTTPSuccess then
70
+ response
71
+ when Net::HTTPRedirection then
72
+ location = response['location']
73
+ Bkblz.log.warn "redirected to #{location}"
74
+ fetch http, location, limit - 1
75
+ else
76
+ response
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,114 @@
1
+ require 'json'
2
+
3
+ module Bkblz
4
+ module V1
5
+ class Response
6
+
7
+ MissingResponseError = Class.new Bkblz::BaseError
8
+
9
+ attr_reader :parsed_body, :http_response
10
+
11
+ class << self
12
+
13
+ def response_model(klass=nil)
14
+ @response_model = klass unless klass.nil?
15
+ @response_model
16
+ end
17
+
18
+ def response_accessors(*response_fields)
19
+ response_fields.each do |response_field|
20
+ response_accessor response_field
21
+ end
22
+ end
23
+
24
+ def response_accessor(response_field, model_klass=nil, &block)
25
+ api_map_key_converter = lambda do |map|
26
+ raise "not a Hash" unless map.is_a? Hash
27
+ Bkblz::MapKeyFormatter.underscore_keys map
28
+ end
29
+ api_value_transformer = lambda do |value|
30
+ return value unless model_klass || block_given?
31
+ return yield value if block_given?
32
+
33
+ if value.is_a? Array
34
+ value.map do |v|
35
+ model_klass.new api_map_key_converter.call(v)
36
+ end
37
+ else
38
+ model_klass.new api_map_key_converter.call(value)
39
+ end
40
+ end
41
+
42
+ define_method response_field do |*args|
43
+ raise MissingResponseError unless @parsed_response
44
+ return @cache[response_field] if @cache.key? response_field
45
+
46
+ value = @parsed_response[response_field]
47
+ @cache[response_field] = api_value_transformer.call value
48
+ end
49
+ end
50
+ end
51
+
52
+ def initialize(http_response, original_request)
53
+ @http_response = http_response
54
+ @original_request = original_request
55
+
56
+ @parsed_response = parse http_response
57
+ @cache = {}
58
+ Bkblz.log.debug { "parsed response => #{@parsed_response}" }
59
+ end
60
+
61
+ attr_reader :original_request
62
+ protected :original_request
63
+
64
+ def [](key)
65
+ @parsed_response[key]
66
+ end
67
+
68
+ def to_model
69
+ raise 'no response model defined' unless self.class.response_model
70
+ self.class.response_model.new @parsed_response.dup
71
+ end
72
+
73
+ private
74
+ def parse(http_response)
75
+ parsed_json = JSON.parse http_response.body, {
76
+ :allow_nan => true,
77
+ :symbolize_names => true,
78
+ :max_nesting => 4
79
+ }
80
+ Bkblz::MapKeyFormatter.underscore_keys parsed_json
81
+ end
82
+ end
83
+
84
+ class PaginatedResponse < Response
85
+
86
+ NoMorePagesError = Class.new Bkblz::BaseError
87
+
88
+ class << self
89
+
90
+ attr_reader :pagination_fields
91
+ def pagination_accessors(*pagination_fields)
92
+ response_accessors *pagination_fields
93
+
94
+ @pagination_fields ||= []
95
+ @pagination_fields.concat pagination_fields
96
+ end
97
+ end
98
+
99
+ def has_more?
100
+ self.class.pagination_fields.any? { |f| !self[f].nil? }
101
+ end
102
+
103
+ def next_request(limit=nil)
104
+ raise NoMorePagesError unless has_more?
105
+ build_next_request(limit)
106
+ end
107
+
108
+ private
109
+ def build_next_request(limit)
110
+ raise "not implemented"
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,72 @@
1
+ require 'uri'
2
+
3
+ module Bkblz
4
+ module V1
5
+
6
+ SessionNotAuthorizedError = Class.new Bkblz::BaseError
7
+
8
+ class Session
9
+
10
+ class << self
11
+ def authorize(config, &block)
12
+ session = Session.new config
13
+ session.auth_response =
14
+ session.send Bkblz::V1::AuthorizeAccountRequest.new
15
+
16
+ yield(session) if block_given?
17
+ session
18
+ end
19
+ end
20
+
21
+ attr_accessor :config, :auth_response
22
+
23
+ def initialize(config)
24
+ @config = config
25
+ end
26
+
27
+ def send(request)
28
+ request.send self
29
+ end
30
+
31
+ def account_id
32
+ check_authorized
33
+ auth_response.account_id
34
+ end
35
+
36
+ def authorized?
37
+ !!auth_response && !!auth_response.authorization_token
38
+ end
39
+
40
+ def create_url(url_suffix)
41
+ check_authorized
42
+ URI.join auth_response.api_url, url_suffix
43
+ end
44
+
45
+ def create_post(url, body=nil, addl_headers={})
46
+ Bkblz.log.debug { "creating post for request => #{url}" }
47
+ check_authorized
48
+
49
+ uri = url.is_a?(URI) ? url : URI(url)
50
+ request = Net::HTTP::Post.new uri
51
+
52
+ if body.is_a? Hash
53
+ body = Bkblz::MapKeyFormatter.camelcase_keys(body).to_json
54
+ end
55
+ request.body = body if body
56
+
57
+ headers = {:"Authorization" => auth_response.authorization_token}
58
+ headers.merge(addl_headers).each do |k,v|
59
+ Bkblz.log.debug2 { "adding request header => #{k}:#{v}" }
60
+ request.add_field k.to_s, v unless v.nil?
61
+ end
62
+
63
+ request
64
+ end
65
+
66
+ private
67
+ def check_authorized
68
+ raise SessionNotAuthorizedError unless authorized?
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,50 @@
1
+ require "digest/sha1"
2
+
3
+ module Bkblz
4
+ module V1
5
+
6
+ TooManyBzInfoHeadersError = Class.new Bkblz::BaseError
7
+
8
+ class UploadFileResponse < Response
9
+ response_model Model::FileInfo
10
+ end
11
+
12
+ class UploadFileRequest < Request
13
+
14
+ response_class UploadFileResponse
15
+
16
+ REQUIRED_HEADERS = {
17
+ :"Authorization" => nil,
18
+ :"X-Bz-File-Name" => nil,
19
+ :"Content-Type" => "b2/x-auto",
20
+ :"Content-Length" => nil,
21
+ :"X-Bz-Content-Sha1" => nil
22
+ }
23
+
24
+ def initialize(upload_auth, body, file_name, content_type=nil,
25
+ last_modified_millis=nil, **bz_info)
26
+ unless last_modified_millis
27
+ # Recommended https://www.backblaze.com/b2/docs/b2_upload_file.html
28
+ bz_info["src_last_modified_millis"] = last_modified_millis
29
+ end
30
+ raise TooManyBzInfoHeadersError, bz_info_headers if bz_info.size > 10
31
+
32
+ @upload_url = upload_auth.upload_url
33
+ @body = body.is_a?(IO) ? body.read : body
34
+ @headers = REQUIRED_HEADERS.dup
35
+ bz_info.each do |k,v|
36
+ @headers["X-Bz-Info-#{k.to-s}".to_sym] = v
37
+ end
38
+
39
+ @headers[:"Authorization"] = upload_auth.authorization_token
40
+ @headers[:"X-Bz-File-Name"] = file_name
41
+ @headers[:"Content-Length"] = @body.size
42
+ @headers[:"X-Bz-Content-Sha1"] = Digest::SHA1.hexdigest @body
43
+ end
44
+
45
+ def build_request(session)
46
+ session.create_post @upload_url, @body, @headers
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ module Bkblz
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bkblz
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Erick Johnson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: ''
56
+ email:
57
+ - erick@vos.io
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.rb
68
+ - Rakefile
69
+ - bkblz.gemspec
70
+ - lib/all.rb
71
+ - lib/bkblz.rb
72
+ - lib/bkblz/config.rb
73
+ - lib/bkblz/core_ext.rb
74
+ - lib/bkblz/logger.rb
75
+ - lib/bkblz/map_key_formatter.rb
76
+ - lib/bkblz/v1/all.rb
77
+ - lib/bkblz/v1/authorize_account.rb
78
+ - lib/bkblz/v1/create_bucket.rb
79
+ - lib/bkblz/v1/delete_bucket.rb
80
+ - lib/bkblz/v1/delete_file_version.rb
81
+ - lib/bkblz/v1/error_response.rb
82
+ - lib/bkblz/v1/get_upload_url.rb
83
+ - lib/bkblz/v1/list_buckets.rb
84
+ - lib/bkblz/v1/list_file_versions.rb
85
+ - lib/bkblz/v1/model_base.rb
86
+ - lib/bkblz/v1/models.rb
87
+ - lib/bkblz/v1/request.rb
88
+ - lib/bkblz/v1/response.rb
89
+ - lib/bkblz/v1/session.rb
90
+ - lib/bkblz/v1/upload_file.rb
91
+ - lib/bkblz/version.rb
92
+ homepage: https://github.com/erickj/bkblz
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.5.1
113
+ signing_key:
114
+ specification_version: 4
115
+ summary: Bkblz GEM for the Backblaze B2 API. https://www.backblaze.com/b2/docs/
116
+ test_files: []