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.
- checksums.yaml +4 -4
- data/.env.test +0 -1
- data/.rubocop.yml +174 -0
- data/.rubocop_todo.yml +1569 -0
- data/Gemfile.lock +32 -19
- data/README.md +20 -1
- data/bin/console +1 -0
- data/exe/s3_zipper +13 -12
- data/lib/s3_zipper.rb +44 -26
- data/lib/s3_zipper/client.rb +13 -3
- data/lib/s3_zipper/progress.rb +38 -12
- data/lib/s3_zipper/spinner.rb +58 -0
- data/lib/s3_zipper/version.rb +3 -1
- data/s3_zipper.gemspec +13 -8
- metadata +56 -12
data/Gemfile.lock
CHANGED
@@ -1,34 +1,38 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
s3_zipper (1.0.
|
5
|
-
aws-sdk
|
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
|
-
|
13
|
-
aws-
|
14
|
-
aws-sdk
|
15
|
-
aws-
|
16
|
-
|
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
|
-
|
27
|
-
|
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.
|
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.
|
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/
|
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
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
|
5
|
-
require
|
5
|
+
require "thor"
|
6
|
+
require "awesome_print"
|
6
7
|
class CLI < Thor
|
7
|
-
desc
|
8
|
-
method_option :filename, type: :string, aliases:
|
9
|
-
method_option :bucket, type: :string, default: ENV[
|
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
|
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
|
16
|
-
method_option :filename, type: :string, aliases:
|
17
|
-
method_option :path, type: :string, aliases:
|
18
|
-
method_option :bucket, type: :string, default: ENV[
|
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
|
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
|
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
|
15
|
-
@
|
16
|
-
@progress
|
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
|
24
|
-
|
25
|
-
|
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
|
33
|
-
|
34
|
-
|
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: {}
|
45
|
-
|
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
|
48
|
-
|
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] =
|
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
|
63
|
-
|
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
|
-
|
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
|
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
|
{
|
data/lib/s3_zipper/client.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
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
|
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
|
-
|
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
|
data/lib/s3_zipper/progress.rb
CHANGED
@@ -1,38 +1,61 @@
|
|
1
|
-
|
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
|
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
|
31
|
+
return unless @progress_bar
|
32
|
+
|
33
|
+
@progress_bar.total
|
20
34
|
end
|
21
35
|
|
22
36
|
def percentage
|
23
|
-
@progress_bar
|
37
|
+
return unless @progress_bar
|
38
|
+
|
39
|
+
@progress_bar.to_h["percentage"]
|
24
40
|
end
|
25
41
|
|
26
42
|
def refresh
|
27
|
-
@progress_bar
|
43
|
+
return unless @progress_bar
|
44
|
+
|
45
|
+
@progress_bar.refresh
|
28
46
|
end
|
29
47
|
|
30
48
|
def progress
|
31
|
-
@progress_bar
|
49
|
+
return unless @progress_bar
|
50
|
+
|
51
|
+
@progress_bar.progress
|
32
52
|
end
|
33
53
|
|
34
|
-
def increment
|
35
|
-
@progress_bar
|
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
|