bkblz 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []