s3_zipper 1.0.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,34 +1,38 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- s3_zipper (1.0.4)
5
- aws-sdk-s3 (~> 1)
4
+ s3_zipper (1.0.9)
5
+ aws-sdk (~> 2)
6
+ concurrent-ruby (~> 1.1)
7
+ multiblock (~> 0.2.1)
6
8
  ruby-progressbar (~> 1)
7
9
  rubyzip (>= 1.0.0)
8
10
 
9
11
  GEM
10
12
  remote: https://rubygems.org/
11
13
  specs:
12
- aws-eventstream (1.0.3)
13
- aws-partitions (1.159.0)
14
- aws-sdk-core (3.50.0)
15
- aws-eventstream (~> 1.0, >= 1.0.2)
16
- aws-partitions (~> 1.0)
17
- aws-sigv4 (~> 1.1)
18
- jmespath (~> 1.0)
19
- aws-sdk-kms (1.18.0)
20
- aws-sdk-core (~> 3, >= 3.48.2)
21
- aws-sigv4 (~> 1.1)
22
- aws-sdk-s3 (1.36.1)
23
- aws-sdk-core (~> 3, >= 3.48.2)
24
- aws-sdk-kms (~> 1)
14
+ ast (2.4.0)
15
+ aws-eventstream (1.1.0)
16
+ aws-sdk (2.11.632)
17
+ aws-sdk-resources (= 2.11.632)
18
+ aws-sdk-core (2.11.632)
25
19
  aws-sigv4 (~> 1.0)
26
- aws-sigv4 (1.1.0)
27
- aws-eventstream (~> 1.0, >= 1.0.2)
20
+ jmespath (~> 1.0)
21
+ aws-sdk-resources (2.11.632)
22
+ aws-sdk-core (= 2.11.632)
23
+ aws-sigv4 (1.2.2)
24
+ aws-eventstream (~> 1, >= 1.0.2)
25
+ concurrent-ruby (1.1.5)
28
26
  diff-lcs (1.3)
29
27
  docile (1.3.1)
28
+ jaro_winkler (1.5.2)
30
29
  jmespath (1.4.0)
31
30
  json (2.2.0)
31
+ multiblock (0.2.1)
32
+ parallel (1.17.0)
33
+ parser (2.6.3.0)
34
+ ast (~> 2.4.0)
35
+ rainbow (3.0.0)
32
36
  rake (10.5.0)
33
37
  rake-compiler (1.0.7)
34
38
  rake
@@ -45,14 +49,22 @@ GEM
45
49
  diff-lcs (>= 1.2.0, < 2.0)
46
50
  rspec-support (~> 3.8.0)
47
51
  rspec-support (3.8.0)
52
+ rubocop (0.70.0)
53
+ jaro_winkler (~> 1.5.1)
54
+ parallel (~> 1.10)
55
+ parser (>= 2.6)
56
+ rainbow (>= 2.2.2, < 4.0)
57
+ ruby-progressbar (~> 1.7)
58
+ unicode-display_width (>= 1.4.0, < 1.7)
48
59
  ruby-progressbar (1.10.0)
49
- rubyzip (1.2.2)
60
+ rubyzip (1.2.3)
50
61
  simplecov (0.16.1)
51
62
  docile (~> 1.1)
52
63
  json (>= 1.8, < 3)
53
64
  simplecov-html (~> 0.10.0)
54
65
  simplecov-html (0.10.2)
55
66
  thor (0.20.3)
67
+ unicode-display_width (1.6.0)
56
68
 
57
69
  PLATFORMS
58
70
  ruby
@@ -62,9 +74,10 @@ DEPENDENCIES
62
74
  rake (~> 10.0)
63
75
  rake-compiler
64
76
  rspec (~> 3.0)
77
+ rubocop (~> 0.70.0)
65
78
  s3_zipper!
66
79
  simplecov
67
80
  thor
68
81
 
69
82
  BUNDLED WITH
70
- 2.0.1
83
+ 2.2.12
data/README.md CHANGED
@@ -46,6 +46,25 @@ zipper.zip_to_s3(keys)
46
46
  # }
47
47
  ```
48
48
 
49
+ ### Event Handling
50
+ ```ruby
51
+ keys = ["documents/files/790/306/985/original/background-10.jpg", "documents/files/790/307/076/original/background-10.jpg"]
52
+ zipper.zip_to_s3 keys do
53
+ on.start { puts 'Starting to zip' }
54
+ on.progress { |progress| puts progress.percentage }
55
+ on.finish { puts 'Finished zipping' }
56
+ on.upload { puts 'Upload complete' }
57
+ end
58
+ # {
59
+ # :key=>"3dc29e9ba0a069eb5d0783f07b12e1b3.zip",
60
+ # :url => "https://bucket_name.s3.us-west-2.amazonaws.com/35b6f0e2ee91aa0e3c0640c7a4b2b7db.zip"
61
+ # :zipped=>["documents/files/790/306/985/original/background-10.jpg", "documents/files/790/307/076/original/background-10.jpg"],
62
+ # :failed=>[]
63
+ # }
64
+ ```
65
+
66
+
67
+
49
68
  ## Development
50
69
 
51
70
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -54,7 +73,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
54
73
 
55
74
  ## Contributing
56
75
 
57
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/s3_zipper. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
76
+ Bug reports and pull requests are welcome on GitHub at https://github.com/capshareinc/s3_zipper. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
58
77
 
59
78
  ## License
60
79
 
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "s3_zipper"
data/exe/s3_zipper CHANGED
@@ -1,24 +1,25 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "s3_zipper"
4
- require 'thor'
5
- require 'awesome_print'
5
+ require "thor"
6
+ require "awesome_print"
6
7
  class CLI < Thor
7
- desc 'local_file FILES', 'zip files from s3 to a local file'
8
- method_option :filename, type: :string, aliases: 'f', desc: 'Name of the created zip archive'
9
- method_option :bucket, type: :string, default: ENV['AWS_BUCKET'], required: !ENV['AWS_BUCKET'].nil?, aliases: 'b', desc: 'Name of the bucket the files are in'
8
+ desc "local_file FILES", "zip files from s3 to a local file"
9
+ method_option :filename, type: :string, aliases: "f", desc: "Name of the created zip archive"
10
+ method_option :bucket, type: :string, default: ENV["AWS_BUCKET"], required: !ENV["AWS_BUCKET"].nil?, aliases: "b", desc: "Name of the bucket the files are in"
10
11
 
11
- def local_file(*files)
12
+ def local_file *files
12
13
  S3Zipper.new(options[:bucket]).zip_to_local_file(files, file: options[:filename] || SecureRandom.hex)
13
14
  end
14
15
 
15
- desc 's3 FILES', 'zip files from s3 to s3'
16
- method_option :filename, type: :string, aliases: 'f', desc: 'Name of the created zip archive'
17
- method_option :path, type: :string, aliases: 'p', desc: 'Path to the file in s3'
18
- method_option :bucket, type: :string, default: ENV['AWS_BUCKET'], required: !ENV['AWS_BUCKET'].nil?, aliases: 'b', desc: 'Name of the bucket the files are in'
16
+ desc "s3 FILES", "zip files from s3 to s3"
17
+ method_option :filename, type: :string, aliases: "f", desc: "Name of the created zip archive"
18
+ method_option :path, type: :string, aliases: "p", desc: "Path to the file in s3"
19
+ method_option :bucket, type: :string, default: ENV["AWS_BUCKET"], required: !ENV["AWS_BUCKET"].nil?, aliases: "b", desc: "Name of the bucket the files are in"
19
20
 
20
- def s3(*files)
21
+ def s3 *files
21
22
  S3Zipper.new(options[:bucket]).zip_to_s3(files, filename: options[:filename] || SecureRandom.hex, path: options[:path])
22
23
  end
23
24
  end
24
- CLI.start(ARGV)
25
+ CLI.start(ARGV)
data/lib/s3_zipper.rb CHANGED
@@ -1,37 +1,46 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "s3_zipper/version"
2
- require 's3_zipper/progress'
4
+ require "s3_zipper/progress"
5
+ require "s3_zipper/spinner"
3
6
  require "s3_zipper/client"
4
7
  require "zip"
8
+ require "multiblock"
5
9
 
6
10
  class S3Zipper
7
- attr_accessor :client, :options, :progress
11
+ attr_accessor :client, :options, :progress, :zip_client, :wrapper
8
12
 
9
13
  # @param [String] bucket - bucket that files exist in
10
14
  # @param [Hash] options - options for zipper
11
15
  # @option options [Boolean] :progress - toggles progress tracking
12
16
  # @return [S3Zipper]
13
17
  def initialize bucket, options = {}
14
- @options = options
15
- @client = Client.new(bucket, options)
16
- @progress = Progress.new(enabled: options[:progress], format: "%e %c/%C %t", total: nil, length: 80, autofinish: false)
18
+ @options = options
19
+ @wrapper = Multiblock.wrapper
20
+ @progress = Progress.new(enabled: options[:progress], format: "%e %c/%C %t", total: nil, length: 80, autofinish: false)
21
+ @client = Client.new(bucket, options)
22
+ @zip_client = Client.new(options[:zip_bucket], options) if options[:zip_bucket]
23
+ @zip_client ||= @client
17
24
  end
18
25
 
19
26
  # Zips files from s3 to a local zip
20
27
  # @param [Array] keys - Array of s3 keys to zip
21
28
  # @param [String, File] file - Filename or file object for the zip, defaults to a random string
22
29
  # @return [Hash]
23
- def zip_to_local_file(keys, file: SecureRandom.hex, &block)
24
- file = file.is_a?(File) ? file : File.open("#{file}.zip", 'w')
25
- zip(keys, file.path, &block)
30
+ def zip_to_local_file keys, file: SecureRandom.hex
31
+ yield(wrapper) if block_given?
32
+ file = file.is_a?(File) ? file : File.open("#{file}.zip", "w")
33
+ zip(keys, file.path)
26
34
  end
27
35
 
28
36
  # Zips files from s3 to a temporary zip
29
37
  # @param [Array] keys - Array of s3 keys to zip
30
38
  # @param [String, File] filename - Name of file, defaults to a random string
31
39
  # @return [Hash]
32
- def zip_to_tempfile(keys, filename: SecureRandom.hex, cleanup: false, &block)
33
- zipfile = Tempfile.new([filename, '.zip'])
34
- result = zip(keys, zipfile.path, &block)
40
+ def zip_to_tempfile keys, filename: SecureRandom.hex, cleanup: false
41
+ yield(wrapper) if block_given?
42
+ zipfile = Tempfile.new([filename, ".zip"])
43
+ result = zip(keys, zipfile.path)
35
44
  zipfile.unlink if cleanup
36
45
  result
37
46
  end
@@ -41,36 +50,45 @@ class S3Zipper
41
50
  # @param [String, File] filename - Name of file, defaults to a random string
42
51
  # @param [String] path - path for file in s3
43
52
  # @return [Hash]
44
- def zip_to_s3 keys, filename: SecureRandom.hex, path: nil, s3_options: {}, &block
45
- progress.update :total, 1
53
+ def zip_to_s3 keys, filename: SecureRandom.hex, path: nil, s3_options: {}
54
+ yield(wrapper) if block_given?
46
55
  filename = "#{path ? "#{path}/" : ''}#{filename}.zip"
47
- result = zip_to_tempfile(keys, filename: filename, cleanup: false, &block)
48
- progress.update_attrs title: "Uploading zip to s3", total: nil
49
- client.upload(result.delete(:filename), filename, options: s3_options)
50
- progress.finish(title: "Uploaded zip to #{filename}")
56
+ result = zip_to_tempfile(keys, filename: filename, cleanup: false)
57
+ zip_client.upload(result.delete(:filename), filename, options: s3_options)
51
58
  result[:key] = filename
52
- result[:url] = client.get_url(result[:key])
59
+ result[:url] = zip_client.get_url(result[:key])
60
+ wrapper.call(:upload, result)
53
61
  result
54
62
  end
55
63
 
56
64
  private
57
65
 
66
+ def add_to_zip zipfile, filename, file, n = 0
67
+ existing_file = zipfile.find_entry(filename)
68
+ if existing_file
69
+ filename = "#{File.basename(filename, ".*").split('(').first}(#{n})#{File.extname(filename)}"
70
+ add_to_zip(zipfile, filename, file, n + 1)
71
+ else
72
+ zipfile.add(filename, file.path)
73
+ end
74
+ end
75
+
58
76
  # @param [Array] keys - Array of s3 keys to zip
59
77
  # @param [String] path - path to zip
60
78
  # @yield [progress]
61
79
  # @return [Hash]
62
- def zip(keys, path)
63
- total = progress.total || 0
64
- total += keys.size
65
- progress.update_attrs total: total, title: "Zipping Keys to #{path}"
80
+ def zip keys, path
81
+ progress.reset total: keys.size, title: "Zipping Keys to #{path}"
66
82
  Zip::File.open(path, Zip::File::CREATE) do |zipfile|
83
+ wrapper.call(:start, zipfile)
67
84
  @failed, @successful = client.download_keys keys do |file, key|
68
- progress.increment
69
- progress.update :title, "Zipping #{key} to #{path}"
70
- yield(zipfile, progress) if block_given?
85
+ progress.increment title: "Zipping #{key} to #{path}"
86
+ wrapper.call(:progress, progress)
71
87
  next if file.nil?
72
- zipfile.add(File.basename(key), file.path)
88
+ add_to_zip(zipfile, File.basename(key), file)
73
89
  end
90
+ progress.finish(title: "Zipped keys to #{path}")
91
+ wrapper.call(:finish, zipfile)
74
92
  end
75
93
  @successful.each { |_, temp| temp.unlink }
76
94
  {
@@ -1,4 +1,6 @@
1
- require 'aws-sdk-s3'
1
+ # frozen_string_literal: true
2
+
3
+ require "aws-sdk"
2
4
 
3
5
  class S3Zipper
4
6
  class Client
@@ -42,18 +44,26 @@ class S3Zipper
42
44
  temp.binmode
43
45
  temp = download_to_file key, temp
44
46
  return if temp.nil?
47
+
45
48
  yield(temp) if block_given?
46
49
  temp
47
50
  ensure
48
51
  temp&.unlink if cleanup
49
52
  end
50
53
 
51
- def get_url(key)
54
+ def get_url key
52
55
  resource.bucket(bucket_name).object(key).public_url
53
56
  end
54
57
 
55
58
  def upload local_path, repo_path, options: {}
56
- client.put_object(options.merge!(bucket: bucket_name, key: repo_path, body: File.open(local_path).read))
59
+ spinner = Spinner.new(
60
+ enabled: options[:progress],
61
+ title: "Uploading zip to #{bucket_name}/#{repo_path}",
62
+ )
63
+ spinner.start
64
+ object = client.put_object(options.merge!(bucket: bucket_name, key: repo_path, body: File.open(local_path).read))
65
+ spinner.finish title: "Uploaded zip to #{bucket_name}/#{repo_path}"
66
+ object
57
67
  end
58
68
  end
59
69
  end
@@ -1,38 +1,61 @@
1
- require 'ruby-progressbar'
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-progressbar"
2
4
  class Progress
5
+
3
6
  def initialize options = {}
4
7
  return unless options[:enabled] || true
8
+
5
9
  @options = options
6
10
  @format = options[:format]
7
11
  @progress_bar = ProgressBar.create(@options)
8
12
  end
9
13
 
10
- def reset(title: nil, total: nil, format: nil)
14
+ def reset title: nil, total: nil, format: nil
15
+ return unless @progress_bar
16
+
11
17
  @progress_bar.progress = 0
12
- @progress_bar.title = title
13
- @progress_bar.total = total
14
- @progress_bar.format = format
18
+ @progress_bar.title = title if title
19
+ @progress_bar.total = total if total
20
+ @progress_bar.format = format if format
15
21
  refresh
16
22
  end
17
23
 
24
+ def spin
25
+ until @progress_bar.finished?
26
+ increment
27
+ end
28
+ end
29
+
18
30
  def total
19
- @progress_bar&.total
31
+ return unless @progress_bar
32
+
33
+ @progress_bar.total
20
34
  end
21
35
 
22
36
  def percentage
23
- @progress_bar&.to_h['percentage']
37
+ return unless @progress_bar
38
+
39
+ @progress_bar.to_h["percentage"]
24
40
  end
25
41
 
26
42
  def refresh
27
- @progress_bar&.refresh
43
+ return unless @progress_bar
44
+
45
+ @progress_bar.refresh
28
46
  end
29
47
 
30
48
  def progress
31
- @progress_bar&.progress
49
+ return unless @progress_bar
50
+
51
+ @progress_bar.progress
32
52
  end
33
53
 
34
- def increment
35
- @progress_bar&.increment
54
+ def increment attrs = {}
55
+ return unless @progress_bar
56
+
57
+ @progress_bar.increment
58
+ update_attrs(attrs) unless attrs.empty?
36
59
  end
37
60
 
38
61
  def update_attrs attrs
@@ -41,11 +64,13 @@ class Progress
41
64
 
42
65
  def update attr, value
43
66
  return unless @progress_bar
67
+
44
68
  @progress_bar.send("#{attr}=", value)
45
69
  end
46
70
 
47
71
  def finish title: nil, format: nil
48
72
  return unless @progress_bar
73
+
49
74
  @progress_bar.title = title if title
50
75
  @progress_bar.format = format if format
51
76
  @progress_bar.finish
@@ -57,6 +82,7 @@ class Progress
57
82
 
58
83
  def get_attr attr
59
84
  return unless @progress_bar
85
+
60
86
  @progress_bar.send(attr)
61
87
  end
62
- end
88
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-progressbar"
4
+ require "concurrent-ruby"
5
+ class S3Zipper
6
+ class Spinner
7
+ include Concurrent::Async
8
+
9
+ def initialize title: "", enabled: true, steps: %w[▸▹▹▹▹▹ ▹▸▹▹▹▹ ▹▹▸▹▹▹ ▹▹▹▸▹▹ ▹▹▹▹▸▹ ▹▹▹▹▹▸ ▹▹▹▹▹]
10
+ return unless enabled || true
11
+
12
+ @progress_bar = ProgressBar.create(
13
+ format: "[%B] %t",
14
+ total: nil,
15
+ length: 100,
16
+ title: title,
17
+ autofinish: false,
18
+ unknown_progress_animation_steps: steps,
19
+ )
20
+ end
21
+
22
+ def reset title: nil, total: nil, format: nil
23
+ return unless @progress_bar
24
+
25
+ @progress_bar.progress = 0
26
+ @progress_bar.title = title if title
27
+ @progress_bar.total = total if total
28
+ @progress_bar.format = format if format
29
+ refresh
30
+ end
31
+
32
+ def start
33
+ async.spin
34
+ end
35
+
36
+ def spin
37
+ return unless @progress_bar
38
+ until @progress_bar.finished?
39
+ increment
40
+ sleep(2)
41
+ end
42
+ end
43
+
44
+ def increment
45
+ return unless @progress_bar
46
+
47
+ @progress_bar.increment
48
+ end
49
+
50
+ def finish title: nil
51
+ return unless @progress_bar
52
+
53
+ @progress_bar.title = title if title
54
+ @progress_bar.format = "[✔] %t"
55
+ @progress_bar.finish
56
+ end
57
+ end
58
+ end