simple-images-downloader 1.0.3 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d6712c3ea229e3cb09526ef0f0144f79fa9dcf1d7af6f20b23a83502ea34be1
4
- data.tar.gz: b1147429efdf91682067decf309aec22050f2ed835b0b0261560f93fd6a63aa6
3
+ metadata.gz: 902c3b0e5fe1566296ac139123707162e33ded87accc9ff5705231fb3fa96f4f
4
+ data.tar.gz: c2812c8d700ca81d1f29b6a29d0063aef74b9dfd3162c652453733870b8073fe
5
5
  SHA512:
6
- metadata.gz: b3cf6db2633b3ebf0e094482079193fa2dbcb75e169c70ea3778e9c46b00abbdae4089378c36d8570630aa0698ca5343ee1a97e67f6e8e193c1d234c240bb35f
7
- data.tar.gz: cd9645e49d0a1658c74a6acde0a5608bd5a79fea67acda6235057c018066aaee7b5352ca150a5bf77d926189cd41dfb43950e62f0257edab98f31fc25f7a3f1b
6
+ metadata.gz: 5501d04aa338c0f992d391d94a5b29e8b63a410938c7a8f31941ed3178b2efa63a6ef84b9bb419f87278880197498367eeb235dea017509c57c708f758f7b4ab
7
+ data.tar.gz: 26ba7c6dbd4cfdf1e34750ce6b3f7e99650511ba56597a7c8ab1ee7950c210f7919f0a1104053fcc6195bec822b215678e9c8bc5015fee9d6fc8ae6eb27612e7
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
+ ## 1.1.0 - 2023-09-30
2
+ - Add validation of downloaded content over path validation
3
+ - Add ability to extend mime types from configuration
4
+ - Removed zeitwerk
5
+ - Refactored main interface
6
+
1
7
  ## 1.0.3 - 2023-09-27
2
8
  - Corrected destination validation in SimpleImagesDownloader::Dispenser
3
- - Corrected source file validation in SimpleImagesDownloader::SourceFiles
9
+ - Corrected source file validation in SimpleImagesDownloader::SourceFile
4
10
  - Refactored
5
11
  - Fixed error message when destination is invalid
6
12
 
data/Gemfile.lock CHANGED
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- simple-images-downloader (1.0.3)
5
- zeitwerk (~> 2.4.0)
4
+ simple-images-downloader (1.1.0)
6
5
 
7
6
  GEM
8
7
  remote: https://rubygems.org/
@@ -76,7 +75,6 @@ GEM
76
75
  addressable (>= 2.3.6)
77
76
  crack (>= 0.3.2)
78
77
  hashdiff (>= 0.4.0, < 2.0.0)
79
- zeitwerk (2.4.2)
80
78
 
81
79
  PLATFORMS
82
80
  ruby
data/README.md CHANGED
@@ -33,7 +33,7 @@ Or install it yourself as:
33
33
 
34
34
  The gem is provided with executable.
35
35
 
36
- You can either run plain commmand with file containing list of url:
36
+ You can either run plain command with file containing list of url:
37
37
 
38
38
  ```bash
39
39
  simple-images-downloader <path_to_file>
@@ -67,6 +67,7 @@ Downloading https://test-for-simple-images-downloader.s3.eu-central-1.amazonaws.
67
67
  Downloading is finished
68
68
  ```
69
69
 
70
+ ## Configuration
70
71
  **By default the images stored in a directory the script is run.**
71
72
  You can also configure the place where the images would be stored to:
72
73
 
@@ -78,6 +79,18 @@ You can also configure the place where the images would be stored to:
78
79
  [2] pry(main)* end
79
80
  ```
80
81
 
82
+ **By default the gem downloads only images with mime types: image/avif, image/gif, image/apng, image/jpg, image/jpeg, image/png, image/svg+xml, image/webp.**
83
+ You can also configure the mime types to download images with:
84
+
85
+
86
+ ```ruby
87
+ [1] pry(main)> require 'simple_images_downloader'
88
+
89
+ [2] pry(main)> SimpleImagesDownloader::Configuration.configure do |config|
90
+ [2] pry(main)* # You must use RFC 1341 - MIME (Multipurpose Internet Mail Extensions) format
91
+ [2] pry(main)* config.valid_mime_types = %w(image/png image/jpg image/jpeg)
92
+ [2] pry(main)* end
93
+ ```
81
94
  ## Development
82
95
 
83
96
  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.
@@ -6,6 +6,7 @@ module SimpleImagesDownloader
6
6
 
7
7
  ACCESSORS = %i[
8
8
  destination
9
+ valid_mime_types
9
10
  ].freeze
10
11
 
11
12
  REQUEST_OPTIONS = {
@@ -15,10 +16,31 @@ module SimpleImagesDownloader
15
16
  read_timeout: 30
16
17
  }.freeze
17
18
 
19
+ # https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types#common_image_file_types
20
+ DEFAULT_VALID_MIME_TYPES_MAP = {
21
+ 'image/avif' => true,
22
+ 'image/gif' => true,
23
+ 'image/apng' => true,
24
+ 'image/jpg' => true,
25
+ 'image/jpeg' => true,
26
+ 'image/png' => true,
27
+ 'image/svg+xml' => true,
28
+ 'image/webp' => true
29
+ }.freeze
30
+
31
+ DEFAULT_DESTINATION = './'
32
+
18
33
  attr_accessor(*ACCESSORS)
19
34
 
20
35
  def initialize
21
- @destination = './'
36
+ @destination = DEFAULT_DESTINATION
37
+ @valid_mime_types = DEFAULT_VALID_MIME_TYPES_MAP.keys
38
+ end
39
+
40
+ def valid_mime_types=(value)
41
+ raise BaseError, 'valid_mime_types must be an array' unless value.is_a?(Array)
42
+
43
+ @valid_mime_types = value
22
44
  end
23
45
 
24
46
  def self.configure
@@ -14,7 +14,7 @@ module SimpleImagesDownloader
14
14
  end
15
15
 
16
16
  def place
17
- validate!(destination_dir)
17
+ validate!({ path: destination_dir })
18
18
 
19
19
  FileUtils.mv @source, target
20
20
  end
@@ -2,22 +2,31 @@
2
2
 
3
3
  module SimpleImagesDownloader
4
4
  class Downloader
5
- def initialize(uri)
6
- @uri = uri
5
+ include Validatable
6
+
7
+ def initialize(uri, client = Client.new, validators = [MimeTypeValidator.new])
8
+ @uri = uri
9
+ @client = client
10
+ @validators = validators
7
11
  end
8
12
 
9
13
  def download
10
14
  puts "Downloading #{@uri}"
11
15
 
12
- io = Client.new.open(@uri)
16
+ io = @client.open(@uri)
17
+
18
+ raise Errors::EmptyResponse, @uri if io.nil?
19
+
20
+ validate!({ path: @uri.to_s, io: io })
13
21
 
14
- downloaded_file = StringioToTempfile.convert(io) unless io.nil?
22
+ tempfile = StringioToTempfile.convert(io)
15
23
 
16
- Dispenser.new(downloaded_file, @uri.path).place
24
+ Dispenser.new(tempfile, @uri.path).place
17
25
 
18
26
  puts 'Downloading is finished'
19
27
  ensure
20
- downloaded_file&.close
28
+ io&.close
29
+ tempfile&.close
21
30
  end
22
31
  end
23
32
  end
@@ -25,9 +25,9 @@ module SimpleImagesDownloader
25
25
  end
26
26
  end
27
27
 
28
- class MissingImageInPath < BaseError
29
- def initialize(path)
30
- message = "The path doesn't contain image #{path}"
28
+ class BadMimeType < BaseError
29
+ def initialize(path, mime_type)
30
+ message = "The image with path: #{path} has wrong mime type #{mime_type}"
31
31
  super(message)
32
32
  end
33
33
  end
@@ -66,5 +66,12 @@ module SimpleImagesDownloader
66
66
  super(message)
67
67
  end
68
68
  end
69
+
70
+ class EmptyResponse < BaseError
71
+ def initialize(uri)
72
+ message = "Nothing returned from request #{uri}"
73
+ super(message)
74
+ end
75
+ end
69
76
  end
70
77
  end
@@ -2,19 +2,12 @@
2
2
 
3
3
  module SimpleImagesDownloader
4
4
  class Line
5
- include Validatable
6
-
7
- def initialize(string, validators = [ImagePathValidator.new])
8
- @string = string
9
- @validators = validators
5
+ def initialize(string)
6
+ @string = string
10
7
  end
11
8
 
12
9
  def uri
13
- parsed_uri = URI.parse(@string)
14
-
15
- validate!(@string)
16
-
17
- parsed_uri
10
+ URI.parse(@string)
18
11
  rescue URI::Error
19
12
  raise Errors::BadUrl, @string
20
13
  end
@@ -10,7 +10,7 @@ module SimpleImagesDownloader
10
10
  end
11
11
 
12
12
  def each_line(&block)
13
- validate!(@path)
13
+ validate!({ path: @path })
14
14
 
15
15
  begin
16
16
  file.each(chomp: true, &block)
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleImagesDownloader
4
+ module Strategies
5
+ class FromFileStrategy < Strategy
6
+ def initialize(path)
7
+ super
8
+ @path = path
9
+ end
10
+
11
+ def process
12
+ source_file = SourceFile.new(@path)
13
+
14
+ source_file.each_line do |line|
15
+ uri = Line.new(line).uri
16
+ Downloader.new(uri).download
17
+ rescue Errors::BaseError => e
18
+ puts e.message
19
+ next
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleImagesDownloader
4
+ module Strategies
5
+ class FromUrlStrategy < Strategy
6
+ def initialize(url)
7
+ super
8
+ @url = url
9
+ end
10
+
11
+ def process
12
+ uri = Line.new(@url).uri
13
+
14
+ Downloader.new(uri).download
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleImagesDownloader
4
+ module Strategies
5
+ class Strategy
6
+ def initialize(_)
7
+ end
8
+
9
+ def process
10
+ raise NotImplementedError, 'must be implemented in subclass'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -9,8 +9,6 @@ module SimpleImagesDownloader
9
9
 
10
10
  IO.copy_stream(stringio, tempfile)
11
11
 
12
- stringio.close
13
-
14
12
  OpenURI::Meta.init tempfile, stringio
15
13
 
16
14
  tempfile
@@ -3,9 +3,11 @@
3
3
  module SimpleImagesDownloader
4
4
  module Validatable
5
5
  class DestinationValidator < Validator
6
- def validate(destination_dir)
7
- raise Errors::DestinationIsNotDirectory, destination_dir unless File.directory?(destination_dir)
8
- raise Errors::DestinationIsNotWritable, destination_dir unless File.writable?(destination_dir)
6
+ def validate(options)
7
+ path = options[:path]
8
+
9
+ raise Errors::DestinationIsNotDirectory, path unless File.directory?(path)
10
+ raise Errors::DestinationIsNotWritable, path unless File.writable?(path)
9
11
  end
10
12
  end
11
13
  end
@@ -3,10 +3,10 @@
3
3
  module SimpleImagesDownloader
4
4
  module Validatable
5
5
  class FileAccessibilityValidator < Validator
6
- def validate(path)
7
- return if File.readable?(path)
6
+ def validate(options)
7
+ return if File.readable?(options[:path])
8
8
 
9
- raise Errors::PermissionsError, path
9
+ raise Errors::PermissionsError, options[:path]
10
10
  end
11
11
  end
12
12
  end
@@ -3,10 +3,10 @@
3
3
  module SimpleImagesDownloader
4
4
  module Validatable
5
5
  class FilePersistanceValidator < Validator
6
- def validate(path)
7
- return if File.exist?(path)
6
+ def validate(options)
7
+ return if File.exist?(options[:path])
8
8
 
9
- raise Errors::MissingFileError, path
9
+ raise Errors::MissingFileError, options[:path]
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleImagesDownloader
4
+ module Validatable
5
+ class MimeTypeValidator < Validator
6
+ extend Forwardable
7
+
8
+ def_delegator 'SimpleImagesDownloader::Configuration', :valid_mime_types, :valid_mime_types
9
+
10
+ def validate(options)
11
+ path = options[:path]
12
+ io = options[:io]
13
+
14
+ mime_type = mime_type_of(io)
15
+
16
+ return if Configuration::DEFAULT_VALID_MIME_TYPES_MAP[mime_type]
17
+
18
+ raise Errors::BadMimeType.new(path, mime_type)
19
+ end
20
+
21
+ private
22
+
23
+ # Taken from Shrine https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/determine_mime_type.rb#L94
24
+ def mime_type_of(io)
25
+ Open3.popen3('file --mime-type --brief -') do |stdin, stdout, stderr, thread|
26
+ copy_stream(from: io, to: stdin.binmode)
27
+
28
+ io.rewind
29
+ stdin.close
30
+
31
+ status = thread.value
32
+
33
+ validate_thread_status(status)
34
+ $stderr.print(stderr.read)
35
+
36
+ output = stdout.read.strip
37
+
38
+ validate_command_output(output)
39
+
40
+ output
41
+ end
42
+ end
43
+
44
+ def copy_stream(from:, to:)
45
+ IO.copy_stream(from, to)
46
+ rescue Errno::EPIPE # rubocop:disable Lint/SuppressedException
47
+ end
48
+
49
+ def validate_thread_status(status)
50
+ raise Errors::BaseError, "file command failed to spawn: #{stderr.read}" if status.nil?
51
+ raise Errors::BaseError, "file command failed: #{stderr.read}" unless status.success?
52
+ end
53
+
54
+ def validate_command_output(output)
55
+ raise Errors::BaseError, "file command failed: #{output}" if output.include?('cannot open')
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleImagesDownloader
4
- VERSION = '1.0.3'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -4,27 +4,34 @@ require 'open-uri'
4
4
  require 'tempfile'
5
5
  require 'forwardable'
6
6
  require 'singleton'
7
- require 'zeitwerk'
8
-
9
- loader = Zeitwerk::Loader.for_gem
10
- loader.setup
7
+ require 'open3'
8
+ require_relative 'simple_images_downloader/version'
9
+ require_relative 'simple_images_downloader/errors'
10
+ require_relative 'simple_images_downloader/configuration'
11
+ require_relative 'simple_images_downloader/validatable'
12
+ require_relative 'simple_images_downloader/validatable/validator'
13
+ require_relative 'simple_images_downloader/validatable/destination_validator'
14
+ require_relative 'simple_images_downloader/validatable/file_accessibility_validator'
15
+ require_relative 'simple_images_downloader/validatable/file_persistance_validator'
16
+ require_relative 'simple_images_downloader/validatable/mime_type_validator'
17
+ require_relative 'simple_images_downloader/stringio_to_tempfile'
18
+ require_relative 'simple_images_downloader/client'
19
+ require_relative 'simple_images_downloader/source_file'
20
+ require_relative 'simple_images_downloader/runner'
21
+ require_relative 'simple_images_downloader/line'
22
+ require_relative 'simple_images_downloader/dispenser'
23
+ require_relative 'simple_images_downloader/strategies/strategy'
24
+ require_relative 'simple_images_downloader/downloader'
25
+ require_relative 'simple_images_downloader/strategies/from_file_strategy'
26
+ require_relative 'simple_images_downloader/strategies/from_url_strategy'
11
27
 
12
28
  module SimpleImagesDownloader
13
29
  def self.from_file(path)
14
- source_file = SourceFile.new(path)
15
-
16
- source_file.each_line do |line|
17
- uri = Line.new(line).uri
18
- Downloader.new(uri).download
19
- rescue Errors::BaseError => e
20
- puts e.message
21
- next
22
- end
30
+ SimpleImagesDownloader::Strategies::FromFileStrategy.new(path).process
23
31
  end
24
32
 
25
33
  def self.from_url(url)
26
- uri = Line.new(url).uri
27
- Downloader.new(uri).download
34
+ SimpleImagesDownloader::Strategies::FromUrlStrategy.new(url).process
28
35
  end
29
36
 
30
37
  def self.root
data/rubocop.yml CHANGED
@@ -69,6 +69,9 @@ Layout/LineLength:
69
69
  Metrics/BlockLength:
70
70
  Enabled: false
71
71
 
72
+ Metrics/MethodLength:
73
+ Max: 11
74
+
72
75
  Layout/EndAlignment:
73
76
  EnforcedStyleAlignWith: variable
74
77
  SupportedStylesAlignWith:
@@ -30,8 +30,6 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ['lib']
32
32
 
33
- spec.add_runtime_dependency 'zeitwerk', '~> 2.4.0'
34
-
35
33
  spec.add_development_dependency 'faker', '~> 2.14'
36
34
  spec.add_development_dependency 'pry', '~> 0.13.1'
37
35
  spec.add_development_dependency 'rake', '~> 12.0'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple-images-downloader
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - IlkhamGaysin
@@ -10,20 +10,6 @@ bindir: exe
10
10
  cert_chain: []
11
11
  date: 2023-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: zeitwerk
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 2.4.0
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 2.4.0
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: faker
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -214,12 +200,15 @@ files:
214
200
  - lib/simple_images_downloader/line.rb
215
201
  - lib/simple_images_downloader/runner.rb
216
202
  - lib/simple_images_downloader/source_file.rb
203
+ - lib/simple_images_downloader/strategies/from_file_strategy.rb
204
+ - lib/simple_images_downloader/strategies/from_url_strategy.rb
205
+ - lib/simple_images_downloader/strategies/strategy.rb
217
206
  - lib/simple_images_downloader/stringio_to_tempfile.rb
218
207
  - lib/simple_images_downloader/validatable.rb
219
208
  - lib/simple_images_downloader/validatable/destination_validator.rb
220
209
  - lib/simple_images_downloader/validatable/file_accessibility_validator.rb
221
210
  - lib/simple_images_downloader/validatable/file_persistance_validator.rb
222
- - lib/simple_images_downloader/validatable/image_path_validator.rb
211
+ - lib/simple_images_downloader/validatable/mime_type_validator.rb
223
212
  - lib/simple_images_downloader/validatable/validator.rb
224
213
  - lib/simple_images_downloader/version.rb
225
214
  - rubocop.yml
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SimpleImagesDownloader
4
- module Validatable
5
- class ImagePathValidator < Validator
6
- VALID_EXTENSIONS = %w[.png .jpg .gif .jpeg].freeze
7
-
8
- def validate(path)
9
- extension = File.extname(path)
10
-
11
- return if VALID_EXTENSIONS.include?(extension)
12
-
13
- raise Errors::MissingImageInPath, path
14
- end
15
- end
16
- end
17
- end