worochi 0.0.7 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/lib/worochi/agent/{sample.rb → #example.rb} +2 -14
  3. data/lib/worochi/agent/dropbox.rb +3 -13
  4. data/lib/worochi/agent/github.rb +8 -23
  5. data/lib/worochi/agent.rb +46 -18
  6. data/lib/worochi/config/#example.yml +35 -0
  7. data/lib/worochi/config/dropbox.yml +14 -0
  8. data/lib/worochi/config/github.yml +17 -0
  9. data/lib/worochi/config.rb +9 -84
  10. data/lib/worochi/configurator.rb +120 -0
  11. data/lib/worochi/helper/github_helper.rb +111 -0
  12. data/lib/worochi/helper.rb +28 -7
  13. data/lib/worochi/item.rb +5 -3
  14. data/lib/worochi/log.rb +8 -14
  15. data/lib/worochi/oauth.rb +69 -0
  16. data/lib/worochi/version.rb +1 -1
  17. data/lib/worochi.rb +20 -3
  18. data/spec/cassettes/Worochi/_push/pushes_with_agents.yml +512 -0
  19. data/spec/cassettes/Worochi_Agent_Dropbox/_push_item/pushes_it_chunked_if_size_exceeds_limit.yml +1049 -0
  20. data/spec/cassettes/Worochi_Agent_Dropbox/_push_item_chunked/raises_an_error.yml +44 -0
  21. data/spec/cassettes/Worochi_Agent_Dropbox/_push_items/pushes_multiple_items.yml +165 -0
  22. data/spec/cassettes/Worochi_Agent_Dropbox/it_should_behave_like_a_service_agent/_files/raises_error_on_invalid_path.yml +38 -0
  23. data/spec/cassettes/Worochi_Agent_Dropbox/it_should_behave_like_a_service_agent/_files_and_folders/shows_detailed_listing.yml +79 -0
  24. data/spec/cassettes/Worochi_Agent_Dropbox/it_should_behave_like_a_service_agent/_files_and_folders/shows_detailed_listing_including_the_required_fields.yml +79 -0
  25. data/spec/cassettes/Worochi_Agent_Github/_list/works_with_absolute_paths.yml +12 -12
  26. data/spec/cassettes/Worochi_Agent_Github/_repos/lists_the_repos.yml +207 -0
  27. data/spec/cassettes/Worochi_Agent_Github/_source_branch/retrieves_the_master_branch_correctly.yml +6 -6
  28. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files/accepts_a_different_relative_path.yml +12 -12
  29. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files/contains_file1.yml +12 -12
  30. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files/does_not_contain_folder1.yml +12 -12
  31. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files/raises_error_on_invalid_path.yml +133 -0
  32. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files_and_folders/contains_folder1_and_file1.yml +24 -24
  33. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files_and_folders/shows_detailed_listing_including_the_required_fields.yml +133 -0
  34. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_folders/accepts_a_different_relative_path.yml +12 -12
  35. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_folders/contains_folder1.yml +12 -12
  36. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_folders/does_not_contain_file1.yml +12 -12
  37. data/spec/cassettes/Worochi_Agent_Github/{_push_all → modifies_the_repo/_push_all}/pushes_a_list_of_items_to_create_a_new_commit.yml +80 -80
  38. data/spec/cassettes/Worochi_Agent_Github/{_push_all → modifies_the_repo/_push_all}/pushes_the_file_to_the_right_place.yml +60 -60
  39. data/spec/cassettes/Worochi_Agent_Github/modifies_the_repo/_push_blob/pushes_the_blob_even_when_it_is_larger_than_block_size.yml +181 -0
  40. data/spec/cassettes/Worochi_Agent_Github/modifies_the_repo/_push_item/pushes_a_single_item_and_makes_a_commit.yml +687 -0
  41. data/spec/cassettes/Worochi_Agent_Github/modifies_the_repo/_stream_blob/streams_the_file_as_an_Base64_JSON_field.yml +181 -0
  42. data/spec/cassettes/Worochi_OAuth/_flow_end/rejects_bad_code.yml +56 -0
  43. data/spec/{helper.rb → spec_helper.rb} +14 -1
  44. data/spec/support/aws_uri_matcher.rb +1 -1
  45. data/spec/support/shared_exampes_for_agents.rb +13 -2
  46. data/spec/support/test_files.rb +4 -4
  47. data/spec/worochi/agent/dropbox_spec.rb +29 -3
  48. data/spec/worochi/agent/github_spec.rb +54 -26
  49. data/spec/worochi/agent_spec.rb +34 -1
  50. data/spec/worochi/config_spec.rb +46 -30
  51. data/spec/worochi/helper/github_helper_spec.rb +94 -0
  52. data/spec/worochi/helper_spec.rb +15 -3
  53. data/spec/worochi/item_spec.rb +9 -6
  54. data/spec/worochi/log_spec.rb +30 -0
  55. data/spec/worochi/oauth_spec.rb +33 -0
  56. data/spec/worochi_spec.rb +25 -1
  57. data/worochi.gemspec +5 -1
  58. metadata +104 -11
  59. data/lib/worochi/helper/github.rb +0 -100
  60. data/spec/worochi/helper/github_spec.rb +0 -57
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5aa10b9332e68182b3a9dbed21aff7f1fcc79c78
4
- data.tar.gz: 451929c1f40471004dc786839f1897965f04a803
3
+ metadata.gz: 0ba8b523ca7164198a72a0cb2b02230f6a394012
4
+ data.tar.gz: 30d3cba96de58d8c04c6c2498f23e747ee320eef
5
5
  SHA512:
6
- metadata.gz: 1c76eb196eb3a1c759ea9fbde0c75e23e3a34af09176086c2f91ee65aadb69de98df0ba5eb9e7cff769187d061d92f864f31dc72d83f458cd376bd0c25eb7e49
7
- data.tar.gz: 740a453516871166e8fe34b7f28b8e31d351d8d3f4bd796f4217978720a6bca2ccf0c9f763a39d711de9ea11e74eb3610d71e7ba506845de9ef40b18cb57ad8c
6
+ metadata.gz: 91d81134ec1ebd38502aadf22f4a90e19d648ae4cb8be0c15712961ad124ea8a57ef2ead0334af2d62a0dfb89270225f5a395880f10ab747238b2d8147806c25
7
+ data.tar.gz: 2f1665102e34f54b1f29f17c40da921854ebede6cadd1662fa5f8198f4d79c965f2bb1032b14700f98171f98a15d63c9b618e58c995f14c6bcd259793734f535
@@ -1,19 +1,7 @@
1
1
  class Worochi
2
- # The {Agent} for a sample API.
2
+ # The {Agent} for an example API.
3
3
  # This is a sample of methods that should be implemented by a service agent.
4
- class Agent::Sample < Agent
5
-
6
- # This is the minimum set of default options that should be specified.
7
- #
8
- # @return [Hash] default options for sample API
9
- # @see Agent#set_options
10
- def default_options
11
- {
12
- service: :sample, # service name
13
- dir: '/' # remote directory to act on
14
- }
15
- end
16
-
4
+ class Agent::Example < Agent
17
5
  # This is where the service-specific API client should be initialized.
18
6
  #
19
7
  # @return [ApiClient]
@@ -4,16 +4,6 @@ class Worochi
4
4
  # The {Agent} for Dropbox API. This wraps around the `dropbox-sdk` gem.
5
5
  # @see https://www.dropbox.com/developers/core/start/ruby
6
6
  class Agent::Dropbox < Agent
7
- # @return [Hash] default options for Dropbox
8
- def default_options
9
- {
10
- service: :dropbox,
11
- chunk_size: 2*1024*1024,
12
- overwrite: true,
13
- dir: '/'
14
- }
15
- end
16
-
17
7
  # Initializes Dropbox SDK client. Refer to
18
8
  # {https://www.dropbox.com/developers/core/start/ruby
19
9
  # official Dropbox documentation}.
@@ -27,16 +17,16 @@ class Worochi
27
17
  # Push a single {Item} to Dropbox.
28
18
  #
29
19
  # @param item [Item]
30
- # @return [nil]
20
+ # @return [Boolean] true if chunk uploader was used
31
21
  def push_item(item)
32
22
  Worochi::Log.debug "Uploading #{item.path} (#{item.size} bytes) to Dropbox..."
33
- if item.size > options[:chunk_size]
23
+ if chunked = item.size > options[:chunk_size]
34
24
  push_item_chunked(item)
35
25
  else
36
26
  @client.put_file(full_path(item), item.content, options[:overwrite])
37
27
  end
38
28
  Worochi::Log.debug "Uploaded"
39
- nil
29
+ chunked
40
30
  end
41
31
 
42
32
  # Returns a list of files and subdirectories at the remote path specified
@@ -1,25 +1,10 @@
1
1
  require 'octokit'
2
2
  require 'base64'
3
- require 'worochi/helper/github'
4
3
 
5
4
  class Worochi
6
5
  # The {Agent} for GitHub API. This wraps around the `octokit` gem.
7
6
  # @see https://github.com/octokit/octokit.rb
8
7
  class Agent::Github < Agent
9
-
10
- # @return [Hash] default options for GitHub
11
- def default_options
12
- {
13
- service: :github,
14
- source: 'worochi',
15
- target: 'worochi',
16
- repo: 'darkmirage/test',
17
- block_size: Worochi::Helper::Github::BLOCK_SIZE,
18
- commit_msg: 'Empty commit message',
19
- dir: '/'
20
- }
21
- end
22
-
23
8
  # Initializes Octokit client. Refer to
24
9
  # {https://github.com/octokit/octokit.rb
25
10
  # octokit.rb documentation}.
@@ -68,12 +53,14 @@ class Worochi
68
53
  x.path.split('/').size <=> y.path.split('/').size
69
54
  end
70
55
 
71
- # Checks that folders are at the requested path and not at a lower or
72
- # higher level
56
+ # Filters for folders containing the specified path
57
+ result.reject! { |elem| !elem.path.match(remote_path + '($|\/.+)') }
58
+ raise Error, 'Invalid GitHub path specified' if result.empty?
59
+
60
+ # Filters out lower levels
73
61
  result.reject! do |elem|
74
- !elem.path.match(remote_path + '($|\/.+)') ||
75
- (File.join(remote_path,
76
- elem.path.split('/').last).sub(/^\//, '') != elem.path)
62
+ filename = elem.path.split('/').last
63
+ File.join(remote_path, filename).sub(/^\//, '') != elem.path
77
64
  end
78
65
 
79
66
  result.map do |elem|
@@ -142,7 +129,7 @@ class Worochi
142
129
  # @return [String] SHA1 checksum of the created blob
143
130
  def stream_blob(item)
144
131
  Worochi::Log.debug "Using JSON streaming..."
145
- post_stream = Worochi::Helper::Github::StreamIO.new(item)
132
+ post_stream = Helper::StreamIO.new(item)
146
133
 
147
134
  uri = URI("https://api.github.com/repos/#{repo}/git/blobs")
148
135
  request = Net::HTTP::Post.new(uri.path)
@@ -156,8 +143,6 @@ class Worochi
156
143
  http.request request do |response|
157
144
  return JSON.parse(response.body)['sha']
158
145
  end
159
-
160
- raise Error, 'Failed to upload file to GitHub'
161
146
  end
162
147
 
163
148
  # @return [Hash] repo information
data/lib/worochi/agent.rb CHANGED
@@ -1,17 +1,15 @@
1
+ require 'yaml'
2
+
1
3
  class Worochi
2
4
  # The parent class for all service agents.
3
5
  class Agent
4
- # Service name.
5
- # @return [Symbol]
6
- attr_reader :type
7
6
  # Service options.
8
- # @return [Hash]
7
+ # @return [Hashie::Mash]
9
8
  attr_accessor :options
10
9
 
11
10
  # @param opts [Hash] service options
12
11
  def initialize(opts={})
13
12
  set_options(opts)
14
- @type = options[:service]
15
13
  init_client
16
14
  end
17
15
 
@@ -92,6 +90,11 @@ class Worochi
92
90
  list_helper(:folders, args)
93
91
  end
94
92
 
93
+ # Returns a list of files and folders at the remote path specified by
94
+ # `options[:dir]`. Relies on the service-specific implementation of
95
+ # `#list`. Refer to {#files} for overloaded prototypes.
96
+ #
97
+ # @return [Array<String>, Array<Hash>] list of files and folders
95
98
  def files_and_folders(*args)
96
99
  list_helper(:both, args)
97
100
  end
@@ -99,29 +102,35 @@ class Worochi
99
102
  # Updates {.options} using `opts`.
100
103
  #
101
104
  # @param opts [Hash] new options
102
- # @return [Hash] the updated options
105
+ # @return [Hashie::Mash] the updated options
103
106
  def set_options(opts={})
104
107
  self.options ||= default_options
105
- sym_opts = {}
106
- opts.each { |k, v| sym_opts[k.to_sym] = v }
107
- options.merge!(sym_opts)
108
+ opts = Hashie::Mash.new(opts)
109
+ options.merge!(opts)
108
110
  end
109
111
 
110
112
  # Sets the remote target directory path. This is the same as modifying
111
113
  # `options[:dir]`.
112
114
  #
113
115
  # @param path [String] the new path
114
- # @return [Hash] the updated options
116
+ # @return [Hashie::Mash] the updated options
115
117
  def set_dir(path)
116
- options[:dir] = path
118
+ options.dir = path
117
119
  options
118
120
  end
119
121
 
122
+ # Returns the service type for the agent.
123
+ #
124
+ # @return [Symbol] service type
125
+ def type
126
+ options.service
127
+ end
128
+
120
129
  # Returns the display name for the agent's service.
121
130
  #
122
131
  # @return [String] display name
123
132
  def name
124
- Worochi::Config.service_display_name(type)
133
+ Worochi::Config.service_display_name(options.service)
125
134
  end
126
135
 
127
136
  private
@@ -129,7 +138,7 @@ class Worochi
129
138
  #
130
139
  # @param mode [Symbol] display files, folders, or both
131
140
  # @param args [Array] argument list
132
- # @return [Array<String>, Array<Hash>] list of files or folders
141
+ # @return [Array<String>, Array<Hashie::Mash>] list of files or folders
133
142
  def list_helper(mode, args)
134
143
  details = true if args.first == true
135
144
  if args.first.kind_of?(String)
@@ -147,20 +156,39 @@ class Worochi
147
156
  end
148
157
 
149
158
  result = list(path).reject { |elem| elem[:type] == excluded }
150
- result.map! { |elem| elem[:name] } unless details
159
+ if details
160
+ result.map! { |elem| Hashie::Mash.new(elem) }
161
+ else
162
+ result.map! { |elem| elem[:name] }
163
+ end
151
164
  result
152
165
  end
153
166
 
154
167
  # @return [String] full path combining remote directory and item path
155
168
  def full_path(item)
156
- File.join(options[:dir], item.path)
169
+ File.join(options.dir, item.path)
157
170
  end
158
171
 
159
- # Agents should override this.
172
+ # Agents should either override this or have a YAML config file at
173
+ # config/service_name.yml.
160
174
  #
161
- # @return [nil]
175
+ # @return [Hashie::Mash] options parsed from YAML config
162
176
  def default_options
163
- raise Error, 'Default options not specified.'
177
+ service = service_name.to_sym
178
+ Worochi::Config.service_opts(service)
179
+ end
180
+
181
+ # Returns the service name based on the class name.
182
+ #
183
+ # @return [Symbol] service name
184
+ def service_name
185
+ name = self.class.to_s.split( '::' ).last
186
+ name.gsub!(/::/, '/')
187
+ name.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
188
+ name.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
189
+ name.tr!("-", "_")
190
+ name.downcase!
191
+ name.to_sym
164
192
  end
165
193
 
166
194
  class << self
@@ -0,0 +1,35 @@
1
+ # Use this as a template for configuring new services
2
+
3
+ # General configurations (must be present)
4
+ # id must be unique across all services
5
+ id: 0
6
+ display_name: Sample Agent
7
+ dir: /
8
+
9
+ # Required if using OAuth2 functionality
10
+ oauth:
11
+ # These are the ENV names and not the actual ID and secret. If not defined,
12
+ # then assumed to be SERVICE_NAME_ID and SERVICE_NAME_SECRET.
13
+ id_env: SAMPLE_ID
14
+ secret_env: SAMPLE_SECRET
15
+
16
+ # API endpoints for OAuth2
17
+ token_url: /1/oauth2/token
18
+ authorize_url: /1/oauth2/authorize
19
+
20
+ # Some services (e.g. Dropbox) have a different domain for the second step
21
+ # of the OAuth flow. In those cases, :site will be used as the endpoint for
22
+ # the start of the authorization flow while :token_site will be used as the
23
+ # endpoint for token retrieval.
24
+ site: https://www.sample.com
25
+ token_site: https://api.sample.com
26
+
27
+ # Scope if required by the service
28
+ scope: gallery
29
+
30
+ # Service specific (optional; example is for GitHub)
31
+ repo: darkmirage/test
32
+ source: master
33
+ target: worochi
34
+ block_size: 12288
35
+ commit_msg: Empty commit message
@@ -0,0 +1,14 @@
1
+ id: 2
2
+
3
+ display_name: Dropbox
4
+ dir: /
5
+ oauth:
6
+ id_env: DROPBOX_ID
7
+ secret_env: DROPBOX_SECRET
8
+ token_url: /1/oauth2/token
9
+ authorize_url: /1/oauth2/authorize
10
+ site: https://www.dropbox.com
11
+ token_site: https://api.dropbox.com
12
+
13
+ chunk_size: 2097152
14
+ overwrite: true
@@ -0,0 +1,17 @@
1
+ id: 1
2
+
3
+ display_name: GitHub
4
+ dir: /
5
+ oauth:
6
+ id_env: GITHUB_ID
7
+ secret_env: GITHUB_SECRET
8
+ token_url: /login/oauth/access_token
9
+ authorize_url: /login/oauth/authorize
10
+ site: https://github.com
11
+ scope: repo
12
+
13
+ repo: darkmirage/test
14
+ source: master
15
+ target: worochi
16
+ block_size: 12288
17
+ commit_msg: Empty commit message
@@ -1,85 +1,10 @@
1
- class Worochi
2
- # Configurations for Worochi.
3
- module Config
4
- @services = {
5
- github: [1, 'GitHub'],
6
- dropbox: [2, 'Dropbox']
7
- }
8
- @s3_bucket = 'data-pixelapse'
9
- @s3_prefix = 's3'
10
-
11
- class << self
12
- # Array of service names.
13
- #
14
- # @return [Array<Symbol>]
15
- def services
16
- @services.keys
17
- end
18
-
19
- # Returns display name for the service.
20
- #
21
- # @overload service_display_name(service)
22
- # @param service [Symbol]
23
- # @overload service_display_name(service_id)
24
- # @param service_id [Integer]
25
- # @return [String] display name
26
- def service_display_name(arg)
27
- service = arg.to_sym if arg.respond_to?(:to_sym)
28
- service = service_name(arg) unless @services.include?(service)
29
- if service.nil?
30
- nil
31
- else
32
- @services[service][1]
33
- end
34
- end
35
-
36
- # Returns the service ID for the service, which can be used as a
37
- # primary key for databases.
38
- #
39
- # @param service [Symbol]
40
- # @return [Integer] service ID
41
- def service_id(service)
42
- if @services[service.to_sym].nil?
43
- nil
44
- else
45
- @services[service.to_sym][0]
46
- end
47
- end
48
-
49
- # Returns the service name given the service ID.
50
- #
51
- # @param service_id [Integer]
52
- # @return [Symbol] if service exists
53
- # @return [nil] if service does not exist
54
- def service_name(service_id)
55
- @service_ids ||= {}
56
- return @service_ids[service_id] if @service_ids.include?(service_id)
57
- @services.each do |key, value|
58
- if value[0] == service_id
59
- @service_ids[value[0]] = key
60
- return key
61
- end
62
- end
63
- nil
64
- end
65
-
66
- # Name of S3 bucket.
67
- #
68
- # @return [String]
69
- attr_reader :s3_bucket
70
-
71
- # Prefix for S3 resource paths.
72
- #
73
- # @return [String]
74
- attr_reader :s3_prefix
75
-
76
- # Disable debug and error messages if `true`.
77
- #
78
- # @return [Boolean]
79
- attr_accessor :silent
80
-
81
- alias_method :silent?, :silent
82
- end
83
- end
84
- end
1
+ require 'worochi/configurator'
85
2
 
3
+ class Worochi
4
+ # Default configurations
5
+ Config.s3_enabled = true
6
+ Config.s3_bucket = 'worochi'
7
+ Config.s3_prefix = 's3'
8
+ Config.silent = true
9
+ Config.logdev = $stdout
10
+ end
@@ -0,0 +1,120 @@
1
+ class Worochi
2
+ # Configuration methods for loading and modifying options.
3
+ module Config
4
+ class << self
5
+ # Loads options from YAML configuration files.
6
+ #
7
+ # @return [nil]
8
+ def load_yaml
9
+ @options = {}
10
+ services.each do |service|
11
+ path = File.join(File.dirname(__FILE__), "config/#{service}.yml")
12
+ raise Error, "Missing config for #{service}" unless File.file?(path)
13
+ @options[service] = Hashie::Mash.new(YAML.load_file(path))
14
+ @options[service].service = service
15
+ end
16
+ end
17
+
18
+ # Returns the service configurations that was loaded from YAML.
19
+ #
20
+ # @return [Hashie::Mash]
21
+ def service_opts(service)
22
+ if service.nil? || @options.nil? || @options[service].nil?
23
+ raise Error, "Invalid service (#{service}) specified"
24
+ end
25
+ @options[service].clone
26
+ end
27
+
28
+ # Array of service names. Parsed from the file names of any .rb files in
29
+ # the worochi/agent directory that does not contain the `#` character.
30
+ #
31
+ # @return [Array<Symbol>]
32
+ def services
33
+ return @services if @services
34
+ files = Dir[File.join(File.dirname(__FILE__), 'agent/[^#]*.rb')]
35
+ @services = files.map do |file|
36
+ File.basename(file, '.rb').to_sym
37
+ end
38
+ @services
39
+ end
40
+
41
+ # Returns display name for the service.
42
+ #
43
+ # @overload service_display_name(service)
44
+ # @param service [Symbol]
45
+ # @overload service_display_name(service_id)
46
+ # @param service_id [Integer]
47
+ # @return [String] display name
48
+ def service_display_name(arg)
49
+ service = arg.to_sym if arg.respond_to?(:to_sym)
50
+ service = service_name(arg) unless @options.include?(service)
51
+ if service.nil?
52
+ nil
53
+ else
54
+ @options[service].display_name
55
+ end
56
+ end
57
+
58
+ # Returns the service ID for the service, which can be used as a
59
+ # primary key for databases.
60
+ #
61
+ # @param service [Symbol]
62
+ # @return [Integer] service ID
63
+ def service_id(service)
64
+ if @options[service.to_sym].nil?
65
+ nil
66
+ else
67
+ @options[service.to_sym].id
68
+ end
69
+ end
70
+
71
+ # Returns the service name given the service ID.
72
+ #
73
+ # @param id [Integer]
74
+ # @return [Symbol] if service exists
75
+ # @return [nil] if service does not exist
76
+ def service_name(id)
77
+ @service_names ||= {}
78
+ return @service_names[id] if @service_names.include?(id)
79
+ @options.each do |key, value|
80
+ if value.id == id
81
+ @service_names[value.id] = key
82
+ return key
83
+ end
84
+ end
85
+ nil
86
+ end
87
+
88
+ # Enables AWS S3 support. AWS_SECRET_ACCESS_KEY and AWS_ACCESS_KEY_ID
89
+ # should be present in ENV.
90
+ #
91
+ # @return [Boolean]
92
+ attr_accessor :s3_enabled
93
+
94
+ # Name of S3 bucket.
95
+ #
96
+ # @return [String]
97
+ attr_accessor :s3_bucket
98
+
99
+ # Prefix for S3 resource paths.
100
+ #
101
+ # @return [String]
102
+ attr_accessor :s3_prefix
103
+
104
+ # Logging device.
105
+ #
106
+ # @return [IO]
107
+ attr_accessor :logdev
108
+
109
+ # Disable debug and error messages if `true`.
110
+ #
111
+ # @return [Boolean]
112
+ attr_accessor :silent
113
+
114
+ alias_method :silent?, :silent
115
+ alias_method :s3_enabled?, :s3_enabled
116
+
117
+ end
118
+ end
119
+ end
120
+
@@ -0,0 +1,111 @@
1
+ require 'base64'
2
+
3
+ class Worochi
4
+ class Agent::Github
5
+ # Helper classes for GitHub JSON streaming.
6
+ module Helper
7
+ # Size to read in. Must be a multiple of 3 for Base64 streaming.
8
+ BLOCK_SIZE = 12288
9
+
10
+ # This is a wrapper that produces a JSON stream that works with
11
+ # `Net::HTTP::Post#body_stream`.
12
+ class StreamIO
13
+ def initialize(item)
14
+ item.content.rewind
15
+ @parts = [
16
+ StringIO.new('{"content":"'),
17
+ Base64IO.new(item.content),
18
+ StringIO.new('","encoding":"base64"}')
19
+ ]
20
+ @part_no = 0
21
+ @pos = 0
22
+ @size = @parts.inject(0) {|sum, p| sum + p.size}
23
+ end
24
+
25
+ # Rewind each component of the stream.
26
+ #
27
+ # @return [nil]
28
+ def rewind
29
+ @parts.each { |part| part.rewind }
30
+ @part_no = 0
31
+ @pos = 0
32
+ nil
33
+ end
34
+
35
+ # @return [Boolean] file has been fully read.
36
+ def eof?
37
+ @pos >= size
38
+ end
39
+
40
+ # @param length [Integer]
41
+ # @param outbuf [IO]
42
+ # @return [String]
43
+ def read(length=nil, outbuf=nil)
44
+ if eof?
45
+ outbuf.clear if outbuf
46
+ return length.nil? ? '' : nil
47
+ end
48
+
49
+ length ||= size
50
+
51
+ output = ''
52
+ while output.length < length
53
+ curr = @parts[@part_no]
54
+ output += curr.read(length - output.length).to_s
55
+ @part_no += 1 if curr.eof?
56
+ break if @part_no == @parts.size
57
+ end
58
+ @pos += output.length
59
+
60
+ unless outbuf.nil?
61
+ outbuf.clear
62
+ outbuf.insert(0, output)
63
+ end
64
+
65
+ output
66
+ end
67
+
68
+ # @return [Integer] size of the JSON
69
+ attr_reader :size
70
+ end
71
+
72
+ # This is a wrapper around the file content that streams the file as a
73
+ # Base64 encoded string.
74
+ class Base64IO
75
+ def initialize(file)
76
+ file.rewind
77
+ @file = file
78
+ @encoded_size = (@file.size / 3.0).ceil * 4
79
+ @buffer = ''
80
+ end
81
+
82
+ # @return [Integer] size of the JSON
83
+ def size
84
+ @encoded_size
85
+ end
86
+
87
+ # @return [Boolean] file has been fully read.
88
+ def eof?
89
+ @buffer.empty? && @file.eof?
90
+ end
91
+
92
+ # Rewind the stream.
93
+ def rewind
94
+ @file.rewind
95
+ @buffer = ''
96
+ nil
97
+ end
98
+
99
+ # @param length [Integer]
100
+ # @param outbuf [IO]
101
+ # @return [String]
102
+ def read(length=size, outbuf=nil)
103
+ while @buffer.length < length and not @file.eof?
104
+ @buffer += Base64.strict_encode64 @file.read(BLOCK_SIZE)
105
+ end
106
+ @buffer.empty? ? nil : @buffer.slice!(0, length)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end