gemirro 2.0.0 → 3.0.1

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.
@@ -1,119 +1,178 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Gemirro::CLI.options.command 'server' do
4
- banner 'Usage: gemirro server [OPTIONS]'
5
- description 'Manage web server'
6
- separator "\nOptions:\n"
7
-
8
- on :start, 'Run web server'
9
- on :stop, 'Stop web server'
10
- on :restart, 'Restart web server'
11
- on :status, 'Status of web server'
12
- on :c=, :config=, 'Path to the configuration file'
13
- on :l=, :log_level=, 'Set logger level'
14
-
15
- @pid_file = nil
16
-
17
- run do |opts, _args|
18
- load_configuration(opts)
19
- start if opts[:start]
20
- stop if opts[:stop]
21
- restart if opts[:restart]
22
- status if opts[:status]
23
- end
24
-
25
- def load_configuration(opts)
26
- Gemirro::CLI.load_configuration(opts[:c])
27
- config = Gemirro.configuration
28
- config.logger_level = opts[:l] if opts[:l]
29
- unless File.directory?(config.destination)
30
- config.logger.error("The directory #{config.destination} does not exist")
31
- abort
32
- end
33
-
34
- @pid_file = File.expand_path(File.join(config.destination,
35
- '..',
36
- 'gemirro.pid'))
37
- require 'gemirro/server'
38
- end
39
-
40
- # Copy stdout because we'll need to reopen it later on
41
- @orig_stdout = $stdout.clone
42
- $PROGRAM_NAME = 'gemirro'
43
-
44
- def create_pid
45
- File.write(@pid_file, Process.pid.to_s)
46
- rescue Errno::EACCES
47
- $stdout.reopen @orig_stdout
48
- puts "Error: Can't write to #{@pid_file} - Permission denied"
49
- exit!
50
- end
51
-
52
- def destroy_pid
53
- File.delete(@pid_file) if File.exist?(@pid_file) && pid == Process.pid
54
- end
55
-
56
- def pid
57
- File.open(@pid_file, 'r') do |f|
58
- return f.gets.to_i
59
- end
60
- rescue Errno::ENOENT
61
- puts "Error: PID File not found #{@pid_file}"
62
- end
63
-
64
- def start
65
- puts 'Starting...'
66
- if File.exist?(@pid_file) && running?(pid)
67
- puts "Error: #{$PROGRAM_NAME} already running"
68
- abort
3
+ require 'timeout'
4
+
5
+ module Gemirro
6
+ module CLI
7
+ # `gemirro server` command
8
+ module ServerCommand
9
+ ##
10
+ # @param [Array] argv
11
+ #
12
+ def self.run(argv)
13
+ options = {}
14
+ option_parser(options).parse!(argv)
15
+
16
+ config = load_configuration(options)
17
+ pid_file = pid_file_for(config)
18
+
19
+ start(pid_file) if options[:start]
20
+ stop(pid_file) if options[:stop]
21
+ restart(pid_file) if options[:restart]
22
+ status(pid_file) if options[:status]
23
+ end
24
+
25
+ ##
26
+ # @param [Hash] options
27
+ # @return [Gemirro::Configuration]
28
+ #
29
+ def self.load_configuration(options)
30
+ Gemirro::CLI.load_configuration(options[:config])
31
+ config = Gemirro.configuration
32
+ config.logger_level = options[:log_level] if options[:log_level]
33
+ Gemirro::CLI.ensure_destination!(config)
34
+ require 'gemirro/server'
35
+
36
+ config
37
+ end
38
+
39
+ ##
40
+ # @param [Gemirro::Configuration] config
41
+ # @return [String]
42
+ #
43
+ def self.pid_file_for(config)
44
+ File.expand_path(File.join(config.destination, '..', 'gemirro.pid'))
45
+ end
46
+
47
+ ##
48
+ # @param [String] pid_file
49
+ # @param [IO] orig_stdout
50
+ #
51
+ def self.create_pid(pid_file, orig_stdout)
52
+ File.write(pid_file, Process.pid.to_s)
53
+ rescue Errno::EACCES
54
+ $stdout.reopen orig_stdout
55
+ puts "Error: Can't write to #{pid_file} - Permission denied"
56
+ exit!
57
+ end
58
+
59
+ ##
60
+ # @param [String] pid_file
61
+ #
62
+ def self.destroy_pid(pid_file)
63
+ File.delete(pid_file) if File.exist?(pid_file) && pid(pid_file) == Process.pid
64
+ end
65
+
66
+ ##
67
+ # @param [String] pid_file
68
+ # @return [Integer]
69
+ #
70
+ def self.pid(pid_file)
71
+ File.open(pid_file, 'r') do |f|
72
+ return f.gets.to_i
73
+ end
74
+ rescue Errno::ENOENT
75
+ puts "Error: PID File not found #{pid_file}"
76
+ end
77
+
78
+ ##
79
+ # @param [String] pid_file
80
+ #
81
+ def self.start(pid_file)
82
+ puts 'Starting...'
83
+ if File.exist?(pid_file) && running?(pid(pid_file))
84
+ puts "Error: #{$PROGRAM_NAME} already running"
85
+ abort
86
+ end
87
+
88
+ # Copy stdout because we'll need to reopen it later on
89
+ orig_stdout = $stdout.clone
90
+
91
+ Process.daemon if Gemirro::Utils.configuration.server.daemonize
92
+ create_pid(pid_file, orig_stdout)
93
+ $stdout.reopen orig_stdout
94
+ puts "done! (PID is #{pid(pid_file)})\n"
95
+ Gemirro::Server.run!
96
+ destroy_pid(pid_file)
97
+ $stdout.reopen File::NULL, 'a'
98
+ end
99
+
100
+ ##
101
+ # @param [String] pid_file
102
+ #
103
+ def self.stop(pid_file)
104
+ process_pid = pid(pid_file)
105
+ return if process_pid.nil?
106
+
107
+ begin
108
+ Process.kill('TERM', process_pid)
109
+ Timeout.timeout(30) { sleep 0.1 while running?(process_pid) }
110
+ rescue Errno::ESRCH
111
+ puts "Error: Couldn't find process with PID #{process_pid}"
112
+ exit!
113
+ rescue Timeout::Error
114
+ puts 'timeout while sending TERM signal, sending KILL signal now... '
115
+ Process.kill('KILL', process_pid)
116
+ destroy_pid(pid_file)
117
+ end
118
+ puts 'done!'
119
+ end
120
+
121
+ ##
122
+ # @param [String] pid_file
123
+ #
124
+ def self.restart(pid_file)
125
+ stop(pid_file)
126
+ start(pid_file)
127
+ end
128
+
129
+ ##
130
+ # @param [String] pid_file
131
+ #
132
+ def self.status(pid_file)
133
+ if running?(pid(pid_file))
134
+ puts "#{$PROGRAM_NAME} is running"
135
+ else
136
+ puts "#{$PROGRAM_NAME} is not running"
137
+ abort
138
+ end
139
+ end
140
+
141
+ ##
142
+ # @param [Integer] process_id
143
+ # @return [TrueClass|FalseClass]
144
+ #
145
+ def self.running?(process_id)
146
+ return false if process_id.nil?
147
+
148
+ Process.getpgid(process_id.to_i) != -1
149
+ rescue Errno::ESRCH
150
+ false
151
+ end
152
+
153
+ ##
154
+ # @param [Hash] options
155
+ # @return [OptionParser]
156
+ #
157
+ def self.option_parser(options)
158
+ OptionParser.new do |opts|
159
+ opts.banner = 'Usage: gemirro server [OPTIONS]'
160
+ opts.separator ''
161
+ opts.separator 'Options:'
162
+ opts.separator ''
163
+
164
+ opts.on('--start', 'Run web server') { options[:start] = true }
165
+ opts.on('--stop', 'Stop web server') { options[:stop] = true }
166
+ opts.on('--restart', 'Restart web server') { options[:restart] = true }
167
+ opts.on('--status', 'Status of web server') { options[:status] = true }
168
+ opts.on('-c', '--config CONFIG', 'Path to the configuration file') { |v| options[:config] = v }
169
+ opts.on('-l', '--log_level LEVEL', 'Set logger level') { |v| options[:log_level] = v }
170
+ opts.on('-h', '--help', 'Display this help message.') do
171
+ puts opts
172
+ exit
173
+ end
174
+ end
175
+ end
69
176
  end
70
-
71
- Process.daemon if Gemirro::Utils.configuration.server.daemonize
72
- create_pid
73
- $stdout.reopen @orig_stdout
74
- puts "done! (PID is #{pid})\n"
75
- Gemirro::Server.run!
76
- destroy_pid
77
- $stdout.reopen File::NULL, 'a'
78
- end
79
-
80
- def stop
81
- process_pid = pid
82
- return if process_pid.nil?
83
-
84
- begin
85
- Process.kill('TERM', process_pid)
86
- Timeout.timeout(30) { sleep 0.1 while running?(process_pid) }
87
- rescue Errno::ESRCH
88
- puts "Error: Couldn't find process with PID #{process_pid}"
89
- exit!
90
- rescue Timeout::Error
91
- puts 'timeout while sending TERM signal, sending KILL signal now... '
92
- Process.kill('KILL', process_pid)
93
- destroy_pid
94
- end
95
- puts 'done!'
96
- end
97
-
98
- def restart
99
- stop
100
- start
101
- end
102
-
103
- def status
104
- if running?(pid)
105
- puts "#{$PROGRAM_NAME} is running"
106
- else
107
- puts "#{$PROGRAM_NAME} is not running"
108
- abort
109
- end
110
- end
111
-
112
- def running?(process_id)
113
- return false if process_id.nil?
114
-
115
- Process.getpgid(process_id.to_i) != -1
116
- rescue Errno::ESRCH
117
- false
118
177
  end
119
178
  end
@@ -1,27 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Gemirro::CLI.options.command 'update' do
4
- banner 'Usage: gemirro update [OPTIONS]'
5
- description 'Updates the list of Gems'
6
- separator "\nOptions:\n"
3
+ module Gemirro
4
+ module CLI
5
+ # `gemirro update` command
6
+ module UpdateCommand
7
+ ##
8
+ # @param [Array] argv
9
+ #
10
+ def self.run(argv)
11
+ options = {}
12
+ option_parser(options).parse!(argv)
7
13
 
8
- on :c=, :config=, 'Path to the configuration file'
9
- on :l=, :log_level=, 'Set logger level'
14
+ Gemirro::CLI.load_configuration(options[:config])
15
+ config = Gemirro.configuration
16
+ config.logger_level = options[:log_level] if options[:log_level]
10
17
 
11
- run do |opts, _args|
12
- Gemirro::CLI.load_configuration(opts[:c])
13
- config.logger_level = opts[:l] if opts[:l]
18
+ source = config.source
19
+ versions = Gemirro::VersionsFetcher.new(source).fetch
20
+ gems = Gemirro::GemsFetcher.new(source, versions)
14
21
 
15
- source = Gemirro.configuration.source
16
- versions = Gemirro::VersionsFetcher.new(source).fetch
17
- gems = Gemirro::GemsFetcher.new(source, versions)
22
+ gems.fetch
18
23
 
19
- gems.fetch
24
+ source.gems.each do |gem|
25
+ gem.gemspec = true
26
+ end
20
27
 
21
- source.gems.each do |gem|
22
- gem.gemspec = true
23
- end
28
+ gems.fetch
29
+ end
30
+
31
+ ##
32
+ # @param [Hash] options
33
+ # @return [OptionParser]
34
+ #
35
+ def self.option_parser(options)
36
+ OptionParser.new do |opts|
37
+ opts.banner = 'Usage: gemirro update [OPTIONS]'
38
+ opts.separator ''
39
+ opts.separator 'Options:'
40
+ opts.separator ''
24
41
 
25
- gems.fetch
42
+ opts.on('-c', '--config CONFIG', 'Path to the configuration file') { |v| options[:config] = v }
43
+ opts.on('-l', '--log_level LEVEL', 'Set logger level') { |v| options[:log_level] = v }
44
+ opts.on('-h', '--help', 'Display this help message.') do
45
+ puts opts
46
+ exit
47
+ end
48
+ end
49
+ end
50
+ end
26
51
  end
27
52
  end
data/lib/gemirro/cli.rb CHANGED
@@ -6,21 +6,62 @@ module Gemirro
6
6
  # CLI mode
7
7
  module CLI
8
8
  ##
9
- # Hash containing the default Slop options.
9
+ # Names of the commands available, in the order they're listed by
10
+ # `--help`, together with a short description.
10
11
  #
11
12
  # @return [Hash]
12
13
  #
13
- SLOP_OPTIONS = {
14
- strict: true,
15
- help: true,
16
- banner: 'Usage: gemirro [COMMAND] [OPTIONS]'
14
+ COMMANDS = {
15
+ 'index' => 'Retrieve specs list from source.',
16
+ 'init' => 'Sets up a new mirror',
17
+ 'list' => 'List available gems.',
18
+ 'server' => 'Manage web server',
19
+ 'update' => 'Updates the list of Gems'
17
20
  }.freeze
18
21
 
19
22
  ##
20
- # @return [Slop]
23
+ # Entry point, called from `bin/gemirro`.
21
24
  #
22
- def self.options
23
- @options ||= default_options
25
+ # @param [Array] argv
26
+ #
27
+ def self.run(argv = ARGV)
28
+ $PROGRAM_NAME = 'gemirro'
29
+ command = argv.first
30
+ case command
31
+ when '-v', '--version'
32
+ puts version_information
33
+ when '-h', '--help', nil
34
+ puts usage
35
+ when *COMMANDS.keys
36
+ argv.shift
37
+ require "gemirro/cli/#{command}"
38
+ const_get("#{command.capitalize}Command").run(argv)
39
+ else
40
+ puts "Unknown command: #{command}"
41
+ puts usage
42
+ exit 1
43
+ end
44
+ rescue OptionParser::ParseError => e
45
+ puts e.message
46
+ exit 1
47
+ end
48
+
49
+ ##
50
+ # Returns a String containing the top level usage/help banner.
51
+ #
52
+ # @return [String]
53
+ #
54
+ def self.usage
55
+ lines = ['Usage: gemirro [COMMAND] [OPTIONS]', '', 'Options:', '']
56
+ lines << ' -v, --version Shows the current version'
57
+ lines << ' -h, --help Display this help message.'
58
+ lines << '' << 'Available commands:' << ''
59
+ COMMANDS.each do |name, description|
60
+ lines << " #{name.ljust(9)}#{description}"
61
+ end
62
+ lines << '' << 'See `<command> --help` for more information on a specific command.'
63
+
64
+ lines.join("\n")
24
65
  end
25
66
 
26
67
  ##
@@ -41,19 +82,6 @@ module Gemirro
41
82
  require(config_file)
42
83
  end
43
84
 
44
- ##
45
- # @return [Slop]
46
- #
47
- def self.default_options
48
- Slop.new(SLOP_OPTIONS.dup) do
49
- separator "\nOptions:\n"
50
-
51
- on :v, :version, 'Shows the current version' do
52
- puts CLI.version_information
53
- end
54
- end
55
- end
56
-
57
85
  ##
58
86
  # Returns a String containing some platform/version related information.
59
87
  #
@@ -62,5 +90,18 @@ module Gemirro
62
90
  def self.version_information
63
91
  "gemirro v#{Gemirro::VERSION} on #{RUBY_DESCRIPTION}"
64
92
  end
93
+
94
+ ##
95
+ # Aborts with an error if the configured destination directory
96
+ # doesn't exist yet.
97
+ #
98
+ # @param [Gemirro::Configuration] config
99
+ #
100
+ def self.ensure_destination!(config)
101
+ return if File.directory?(config.destination)
102
+
103
+ config.logger.error("The directory #{config.destination} does not exist")
104
+ abort
105
+ end
65
106
  end
66
107
  end
@@ -166,9 +166,11 @@ module Gemirro
166
166
  # @param [String] version
167
167
  #
168
168
  def ignore_gem(name, version, platform)
169
- ignored_gems[platform] ||= {}
170
- ignored_gems[platform][name] ||= []
171
- ignored_gems[platform][name] << version
169
+ ignored_gems_mutex.synchronize do
170
+ ignored_gems[platform] ||= {}
171
+ ignored_gems[platform][name] ||= []
172
+ ignored_gems[platform][name] << version
173
+ end
172
174
  end
173
175
 
174
176
  ##
@@ -179,13 +181,25 @@ module Gemirro
179
181
  # @return [TrueClass|FalseClass]
180
182
  #
181
183
  def ignore_gem?(name, version, platform)
182
- if ignored_gems[platform][name]
183
- ignored_gems[platform][name].include?(version)
184
- else
185
- false
184
+ ignored_gems_mutex.synchronize do
185
+ if ignored_gems[platform][name]
186
+ ignored_gems[platform][name].include?(version)
187
+ else
188
+ false
189
+ end
186
190
  end
187
191
  end
188
192
 
193
+ ##
194
+ # Guards {#ignored_gems} against concurrent mutation, since gems can
195
+ # be fetched from multiple threads at once (see {GemsFetcher#fetch}).
196
+ #
197
+ # @return [Mutex]
198
+ #
199
+ def ignored_gems_mutex
200
+ @ignored_gems_mutex ||= Mutex.new
201
+ end
202
+
189
203
  ##
190
204
  # Define the source to mirror.
191
205
  #
@@ -193,9 +207,9 @@ module Gemirro
193
207
  # @param [String] url
194
208
  # @param [Proc] block
195
209
  #
196
- def define_source(name, url, &block)
210
+ def define_source(name, url, &)
197
211
  source = Source.new(name, url)
198
- source.instance_eval(&block)
212
+ source.instance_eval(&)
199
213
 
200
214
  @source = source
201
215
  end
@@ -17,6 +17,7 @@ module Gemirro
17
17
  #
18
18
  class GemVersion
19
19
  include Comparable
20
+
20
21
  attr_reader :name, :number, :platform
21
22
 
22
23
  ##
@@ -59,8 +59,8 @@ module Gemirro
59
59
  ##
60
60
  # Each method
61
61
  #
62
- def each(&block)
63
- @gems.each(&block)
62
+ def each(&)
63
+ @gems.each(&)
64
64
  end
65
65
 
66
66
  ##
@@ -69,7 +69,7 @@ module Gemirro
69
69
  # @param [Proc] block
70
70
  # @return [Array]
71
71
  #
72
- def by_name(&block)
72
+ def by_name(&)
73
73
  if @grouped.nil?
74
74
  @grouped = @gems.group_by(&:name).map do |name, collection|
75
75
  [name, GemVersionCollection.new(collection)]
@@ -85,7 +85,7 @@ module Gemirro
85
85
  end
86
86
 
87
87
  if block_given?
88
- @grouped.each(&block)
88
+ @grouped.each(&)
89
89
  else
90
90
  @grouped
91
91
  end
@@ -23,10 +23,12 @@ module Gemirro
23
23
  end
24
24
 
25
25
  ##
26
- # Fetches the Gems.
26
+ # Fetches the Gems, in parallel across
27
+ # `Gemirro.configuration.update_thread_count` threads since each Gem is
28
+ # an independent network round-trip.
27
29
  #
28
30
  def fetch
29
- @source.gems.each do |gem|
31
+ Parallel.each(@source.gems, in_threads: Utils.configuration.update_thread_count) do |gem|
30
32
  versions_for(gem).each do |versions|
31
33
  gem.platform = versions[1] if versions
32
34
  version = versions[0] if versions
@@ -78,11 +80,7 @@ module Gemirro
78
80
  #
79
81
  def fetch_gemspec(gem, version)
80
82
  filename = gem.gemspec_filename(version)
81
- satisfied = if gem.only_latest?
82
- true
83
- else
84
- gem.requirement.satisfied_by?(version)
85
- end
83
+ satisfied = gem.only_latest? || gem.requirement.satisfied_by?(version)
86
84
 
87
85
  if gemspec_exists?(filename) || !satisfied
88
86
  Utils.logger.debug("Skipping #{filename}")
@@ -162,8 +160,8 @@ module Gemirro
162
160
  ##
163
161
  # @see Gemirro::Configuration#ignore_gem?
164
162
  #
165
- def ignore_gem?(*args)
166
- Utils.configuration.ignore_gem?(*args)
163
+ def ignore_gem?(*)
164
+ Utils.configuration.ignore_gem?(*)
167
165
  end
168
166
  end
169
167
  end