rankmirror 1.0.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/COPYING +21 -0
- data/Gemfile +7 -0
- data/README.md +23 -0
- data/Rakefile +6 -0
- data/TODO +3 -0
- data/bin/rankmirror +147 -0
- data/lib/rankmirror.rb +14 -0
- data/lib/rankmirror/cache.rb +50 -0
- data/lib/rankmirror/config.rb +107 -0
- data/lib/rankmirror/exception.rb +12 -0
- data/lib/rankmirror/localoss.rb +18 -0
- data/lib/rankmirror/localpackman.rb +18 -0
- data/lib/rankmirror/mirrors.rb +37 -0
- data/lib/rankmirror/opensuse.mirrorlist +8 -0
- data/lib/rankmirror/options.rb +100 -0
- data/lib/rankmirror/packman.mirrorlist +2 -0
- data/lib/rankmirror/reachable.rb +25 -0
- data/lib/rankmirror/remoteoss.rb +47 -0
- data/lib/rankmirror/remotepackman.rb +23 -0
- data/lib/rankmirror/speed.rb +23 -0
- data/lib/rankmirror/status.rb +57 -0
- data/lib/rankmirror/version.rb +3 -0
- data/rankmirror.gemspec +39 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 69987a6f2bf817b2ff3779548e775e3c86042b00
|
4
|
+
data.tar.gz: fc92243a24cf327ada2a959163911ea6a968f8b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 468c53d68cb63777996a305174670af8be0de74f155ac31330ed9389fa0349abf21a6bcb426b5eaac8524a0726b3b3d7c1a59b53654d6e0fb5d7a2872c5229de
|
7
|
+
data.tar.gz: d71507f6738690b169adceb79e60d56570e6f717e97da0d6cec166d9c9547bdd75127aa71c236f77bf40ff9e762b3c2b3edfaa8fc1d2196978ad58fc20ab8efa
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/COPYING
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 marguerite
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Rankmirror
|
2
|
+
|
3
|
+
RankMirror is a ruby implementation of `rankmirrors` in ArchLinux but for all modern Linux distributions.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
$ gem install rankmirror
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
$ rankmirror --help
|
12
|
+
|
13
|
+
eg. `rankmirror -o opensuse --flavor tumbleweed --continent asia` will get you: the fastest Tumbleweed mirror located in Asia. Those 3 options
|
14
|
+
are mandatory for openSUSE.
|
15
|
+
|
16
|
+
`rankmirror -o packman -s` will get you: the fastest Packman mirror from the World. And save that to a packman.mirrorlist in your `~/.rankmirror`.
|
17
|
+
Next time, just `rankmirror -l -o packman -s`, which is faster^2. Because most of the times, there're only a few mirrors that are fast to you.
|
18
|
+
This technology applies to openSUSE too.
|
19
|
+
|
20
|
+
## License
|
21
|
+
|
22
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
23
|
+
|
data/Rakefile
ADDED
data/bin/rankmirror
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.push(File.expand_path(File.dirname(__FILE__) + "/../lib"))
|
4
|
+
|
5
|
+
require 'rankmirror'
|
6
|
+
require 'optparse'
|
7
|
+
require 'colorize'
|
8
|
+
|
9
|
+
options = RankMirror::Options.new
|
10
|
+
|
11
|
+
parser = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: rankmirror [options]"
|
13
|
+
opts.separator ""
|
14
|
+
opts.separator "Specific options:"
|
15
|
+
|
16
|
+
opts.on("-l", "--localonly",
|
17
|
+
"Check mirrors in your local mirrorlist file ONLY.") do |local|
|
18
|
+
options.local = true
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("-o", "--os [Distribution]",
|
22
|
+
"Check mirrors for this distro. Now supported: 'opensuse', 'packman'.") do |os|
|
23
|
+
case os
|
24
|
+
when "opensuse"
|
25
|
+
options.os = "opensuse"
|
26
|
+
options.path = "tumbleweed/repo/oss/suse/repodata/"
|
27
|
+
when "packman"
|
28
|
+
options.os = "packman"
|
29
|
+
options.path = "openSUSE_Tumbleweed/Essentials/repodata/"
|
30
|
+
else
|
31
|
+
raise RankMirror::DistributionNotImplemented
|
32
|
+
end
|
33
|
+
|
34
|
+
if options.os.nil?
|
35
|
+
raise RankMirror::MandatoryOptionNotSpecified
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("--continent [Continent]", "Check mirrors on this continent. openSUSE ONLY.
|
40
|
+
Available Continents: 'africa', 'asia', 'europe', 'northamerica',
|
41
|
+
'southamerica', 'oceania'.") do |cont|
|
42
|
+
unless options.os != "opensuse"
|
43
|
+
options.continent = cont.downcase.delete("\s")
|
44
|
+
else
|
45
|
+
raise RankMirror::SuboptionNotSupported
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on("--flavor [Flavor]","Check mirrors for this flavor. openSUSE ONLY.
|
50
|
+
Now supported: '4220', '4210', 'tumbleweed'.") do |flavor|
|
51
|
+
case options.os
|
52
|
+
when "opensuse","packman"
|
53
|
+
case flavor
|
54
|
+
when "4220"
|
55
|
+
options.flavor = "leap4220"
|
56
|
+
when "4210"
|
57
|
+
options.flavor = "leap4210"
|
58
|
+
when "tumbleweed","tw"
|
59
|
+
options.flavor = "tumbleweed"
|
60
|
+
else
|
61
|
+
raise RankMirror::FlavorNotImplemented
|
62
|
+
end
|
63
|
+
else
|
64
|
+
raise RankMirror::DistributionNotImplemented
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("-q", "--quick [1/0]", "Check mirrors quickly/slowly.
|
69
|
+
The quick check will download a tiny file from the mirror, thus
|
70
|
+
response quickly but the result will be less accurate. Default: 1") do |quick|
|
71
|
+
unless quick.to_i > 0
|
72
|
+
options.quick = false
|
73
|
+
case options.os
|
74
|
+
when "opensuse"
|
75
|
+
options.file = "appdata.xml.gz"
|
76
|
+
when "packman"
|
77
|
+
options.file = "primary.xml.gz"
|
78
|
+
else
|
79
|
+
raise RankMirror::DistributionNotImplemented
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on("-s","--save","Save the mirrorlist in your .rankmirror directory") do |save|
|
85
|
+
options.save = true
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.separator ""
|
89
|
+
opts.separator "Common Options:"
|
90
|
+
|
91
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
92
|
+
puts opts
|
93
|
+
exit
|
94
|
+
end
|
95
|
+
|
96
|
+
opts.on_tail("--version","Show version") do
|
97
|
+
puts RankMirror::VERSION
|
98
|
+
exit
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
parser.parse!(ARGV)
|
104
|
+
|
105
|
+
mirrors = Array.new
|
106
|
+
config = RankMirror::Config.new(options)
|
107
|
+
|
108
|
+
case options.os
|
109
|
+
when "opensuse"
|
110
|
+
local = RankMirror::LocalOSS.new(config.path,options).sort
|
111
|
+
mirrors = unless options.local
|
112
|
+
remote = RankMirror::RemoteOSS.new(options).fetch
|
113
|
+
remote.concat(local)
|
114
|
+
else
|
115
|
+
local
|
116
|
+
end
|
117
|
+
when "packman"
|
118
|
+
local = RankMirror::LocalPackman.new(config.path,options).sort
|
119
|
+
mirrors = unless options.local
|
120
|
+
remote = RankMirror::RemotePackman.new.fetch
|
121
|
+
remote.concat(local)
|
122
|
+
else
|
123
|
+
local
|
124
|
+
end
|
125
|
+
else
|
126
|
+
raise RankMirror::DistributionNotImplemented
|
127
|
+
end
|
128
|
+
|
129
|
+
sorted = RankMirror::Mirrors.new(mirrors).sort_by_speed(options).select {|k,v| v > 0}
|
130
|
+
|
131
|
+
if options.save
|
132
|
+
config.save(sorted.keys)
|
133
|
+
end
|
134
|
+
|
135
|
+
i = 1
|
136
|
+
|
137
|
+
sorted.each do |k,v|
|
138
|
+
speed = v.round(2)
|
139
|
+
if i < 4
|
140
|
+
puts "#{i}\t#{k}\t#{speed}\sKiB/s".green
|
141
|
+
elsif i > 3 && i < 6
|
142
|
+
puts "#{i}\t#{k}\t#{speed}\sKiB/s".yellow
|
143
|
+
else
|
144
|
+
puts "#{i}\t#{k}\t#{speed}\sKiB/s".red
|
145
|
+
end
|
146
|
+
i += 1
|
147
|
+
end
|
data/lib/rankmirror.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rankmirror/version'
|
2
|
+
require 'rankmirror/exception'
|
3
|
+
require 'rankmirror/reachable'
|
4
|
+
require 'rankmirror/speed'
|
5
|
+
require 'rankmirror/options'
|
6
|
+
require 'rankmirror/mirrors'
|
7
|
+
require 'rankmirror/remoteoss'
|
8
|
+
require 'rankmirror/localoss'
|
9
|
+
require 'rankmirror/cache'
|
10
|
+
require 'rankmirror/remotepackman'
|
11
|
+
require 'rankmirror/localpackman'
|
12
|
+
require 'rankmirror/status'
|
13
|
+
require 'rankmirror/config'
|
14
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'curb'
|
2
|
+
require 'nokogiri'
|
3
|
+
module RankMirror
|
4
|
+
class Cache
|
5
|
+
def initialize(uri)
|
6
|
+
@uri = uri
|
7
|
+
@host = @uri.gsub(/^http(s):\/\//,"").gsub("/","_")
|
8
|
+
@filename = File.join("/tmp",@host)
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch
|
12
|
+
unless is_recent?
|
13
|
+
buffer = open(@filename,'w')
|
14
|
+
r = Curl::Easy.new(@uri)
|
15
|
+
r.on_body do |b|
|
16
|
+
buffer.write b
|
17
|
+
end
|
18
|
+
r.perform
|
19
|
+
buffer.close
|
20
|
+
to_xml
|
21
|
+
end
|
22
|
+
return @filename + ".xml"
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_xml
|
26
|
+
buffer = open(@filename) {|f|
|
27
|
+
f.read
|
28
|
+
}
|
29
|
+
doc = Nokogiri::XML(buffer)
|
30
|
+
f = open(@filename + ".xml",'w')
|
31
|
+
doc.write_xml_to(f)
|
32
|
+
f.close
|
33
|
+
end
|
34
|
+
|
35
|
+
def is_recent?
|
36
|
+
if File.exist?(@filename + ".xml")
|
37
|
+
last_time = File.mtime(@filename + ".xml")
|
38
|
+
# one week
|
39
|
+
if Time.now - last_time < 60*60*24*7
|
40
|
+
true
|
41
|
+
else
|
42
|
+
false
|
43
|
+
end
|
44
|
+
else
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'uri'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module RankMirror
|
6
|
+
class Config
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@options = options
|
10
|
+
@name = @options.os
|
11
|
+
@file = @name + ".mirrorlist"
|
12
|
+
@systempath = File.expand_path(File.dirname(__FILE__))
|
13
|
+
@localpath = ENV['HOME'] + "/.rankmirror/"
|
14
|
+
end
|
15
|
+
|
16
|
+
def localconfig
|
17
|
+
File.join(@localpath,@file)
|
18
|
+
end
|
19
|
+
|
20
|
+
def systemconfig
|
21
|
+
File.join(@systempath,@file)
|
22
|
+
end
|
23
|
+
|
24
|
+
def path
|
25
|
+
if File.exist?(systemconfig) && File.readable?(systemconfig)
|
26
|
+
if File.exist?(localconfig)
|
27
|
+
m = merge(systemconfig,localconfig)
|
28
|
+
write(m,localconfig)
|
29
|
+
localconfig
|
30
|
+
else
|
31
|
+
systemconfig
|
32
|
+
end
|
33
|
+
else
|
34
|
+
if File.exist?(localconfig)
|
35
|
+
localconfig
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def merge(systemconfig,localconfig)
|
43
|
+
system_mirrors = parse(systemconfig)
|
44
|
+
local_mirrors = parse(localconfig)
|
45
|
+
system_mirrors.each do |system_mirror|
|
46
|
+
http_match = false
|
47
|
+
local_mirrors.each do |local_mirror|
|
48
|
+
if system_mirror.http == local_mirror.http
|
49
|
+
http_match = true
|
50
|
+
local_mirror.continent = system_mirror.continent
|
51
|
+
local_mirror.country = system_mirror.country
|
52
|
+
end
|
53
|
+
end
|
54
|
+
if http_match == false
|
55
|
+
local_mirrors << system_mirror
|
56
|
+
end
|
57
|
+
end
|
58
|
+
return local_mirrors
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse(config)
|
62
|
+
f = open(config)
|
63
|
+
mirrors = f.readlines.map!{|l|
|
64
|
+
unless l.start_with?("#")
|
65
|
+
elements = l.strip.split("\t")
|
66
|
+
mirror = OpenStruct.new
|
67
|
+
mirror.name = elements[0]
|
68
|
+
mirror.continent = elements[1]
|
69
|
+
mirror.country = elements[2]
|
70
|
+
mirror.http = elements[3]
|
71
|
+
mirror.tumbleweed = elements[4]
|
72
|
+
mirror.leap4220 = elements[5]
|
73
|
+
mirror.leap4210 = elements[6]
|
74
|
+
mirror.leap4230 = elements[7]
|
75
|
+
mirror
|
76
|
+
end
|
77
|
+
}.compact!
|
78
|
+
f.close
|
79
|
+
return mirrors
|
80
|
+
end
|
81
|
+
|
82
|
+
def write(mirrors_array,file)
|
83
|
+
f = open(file,'w')
|
84
|
+
f.write "#sitename\tcontinent\tcountry\thttp\ttumbleweed\tleap4220\tleap4210\tleap4230\n"
|
85
|
+
mirrors_array.each do |mirror_struct|
|
86
|
+
str = String.new
|
87
|
+
m = mirror_struct
|
88
|
+
str = m.name + "\t" + m.continent + "\t" + m.country + "\t" + m.http + "\t" + m.tumbleweed.to_s + "\t" + m.leap4220.to_s + "\t" + m.leap4210.to_s + "\t" + m.leap4230.to_s + "\n"
|
89
|
+
f.write str
|
90
|
+
end
|
91
|
+
f.close
|
92
|
+
end
|
93
|
+
|
94
|
+
def save(array)
|
95
|
+
FileUtils.mkdir_p @localpath unless File.directory?(@localpath)
|
96
|
+
mirrors_array = array.map! do |uri|
|
97
|
+
mirror = RankMirror::Status.new(uri,@options.os).get
|
98
|
+
mirror.name = URI.parse(uri).host if mirror.name.nil?
|
99
|
+
mirror.continent = "world"
|
100
|
+
mirror.country = "world"
|
101
|
+
mirror
|
102
|
+
end
|
103
|
+
write(mirrors_array,localconfig)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module RankMirror
|
2
|
+
class DistributionNotImplemented < StandardError
|
3
|
+
end
|
4
|
+
class MandatoryOptionNotSpecified < StandardError
|
5
|
+
end
|
6
|
+
class SuboptionNotSupported < StandardError
|
7
|
+
end
|
8
|
+
class FlavorNotImplemented < StandardError
|
9
|
+
end
|
10
|
+
class ConfigurationNotFound < StandardError
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module RankMirror
|
4
|
+
class LocalOSS
|
5
|
+
def initialize(mirrorlist,options)
|
6
|
+
@options = options
|
7
|
+
@mirrorlist = RankMirror::Config.new(@options).parse(mirrorlist)
|
8
|
+
end
|
9
|
+
|
10
|
+
def sort
|
11
|
+
sorted = @mirrorlist.map! { |m|
|
12
|
+
m.http if @options.continent == m.continent && m[@options.flavor] == "true"
|
13
|
+
}.compact!
|
14
|
+
return sorted
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module RankMirror
|
4
|
+
class LocalPackman
|
5
|
+
def initialize(mirrorlist,options)
|
6
|
+
@options = options
|
7
|
+
@mirrorlist = RankMirror::Config.new(@options).parse(mirrorlist)
|
8
|
+
end
|
9
|
+
|
10
|
+
def sort
|
11
|
+
sorted = @mirrorlist.map!{|m|
|
12
|
+
m.http if @options.continent == m.continent && m[@options.flavor] == "true"
|
13
|
+
}.compact!
|
14
|
+
return sorted
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RankMirror
|
2
|
+
class Mirrors
|
3
|
+
|
4
|
+
def initialize(mirrors)
|
5
|
+
@mirrors = mirrors
|
6
|
+
end
|
7
|
+
|
8
|
+
def sort_by_speed(options)
|
9
|
+
speed_matrix = Hash.new
|
10
|
+
size = @mirrors.length
|
11
|
+
jobs = Queue.new
|
12
|
+
@mirrors.each {|i| jobs.push i}
|
13
|
+
|
14
|
+
workers = size.times.map do
|
15
|
+
Thread.new do
|
16
|
+
begin
|
17
|
+
while x = jobs.pop(true)
|
18
|
+
x << "/" unless x.index(/\/$/)
|
19
|
+
uri = x + options.path + options.file
|
20
|
+
speed = RankMirror::Speed.new(uri).get
|
21
|
+
speed_matrix[x] = speed
|
22
|
+
end
|
23
|
+
rescue ThreadError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
workers.map(&:join)
|
29
|
+
|
30
|
+
speed_sorted = speed_matrix.values.sort.reverse
|
31
|
+
sorted = Hash.new
|
32
|
+
speed_sorted.each {|v| sorted[speed_matrix.key(v)] = v}
|
33
|
+
|
34
|
+
return sorted
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# sitename continent country http tumbleweed leap-4220 leap-4210 leap 4230
|
2
|
+
Aliyun asia china http://mirrors.aliyun.com/opensuse true true true false
|
3
|
+
Capital Online asia china http://mirrors.yun-idc.com/opensuse true true true true
|
4
|
+
Beijing Institute of Techfalselogy asia china http://mirror.bit.edu.cn/opensuse true true true true
|
5
|
+
Zhejiang University asia china http://mirrors.zju.edu.cn/opensuse true true true true
|
6
|
+
Xiamen University asia china http://mirrors.xmu.edu.cn/opensuse true false true false
|
7
|
+
Zhengzhou University asia china https://mirrors.zzu.edu.cn/opensuse true true true false
|
8
|
+
Chongqing University asia china https://mirrors.cqu.edu.cn/opensuse true true true false
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module RankMirror
|
4
|
+
class Options
|
5
|
+
def initialize
|
6
|
+
@options = OpenStruct.new
|
7
|
+
@options.local = false
|
8
|
+
@options.os = "opensuse"
|
9
|
+
@options.continent = "asia"
|
10
|
+
@options.flavor = "leap4220"
|
11
|
+
@options.quick = true
|
12
|
+
@options.path = nil
|
13
|
+
@options.file = "repomd.xml"
|
14
|
+
end
|
15
|
+
|
16
|
+
def show
|
17
|
+
return @options
|
18
|
+
end
|
19
|
+
|
20
|
+
def add(k,v)
|
21
|
+
@options[k] = v
|
22
|
+
return @options
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete_by_key(key)
|
26
|
+
@options.delete_field(key)
|
27
|
+
return @options
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete_by_value(value)
|
31
|
+
h = @options.to_h
|
32
|
+
@options.delete_field(h.key(value))
|
33
|
+
return @options
|
34
|
+
end
|
35
|
+
|
36
|
+
def local=(v)
|
37
|
+
@options.local = v
|
38
|
+
end
|
39
|
+
|
40
|
+
def local
|
41
|
+
@local ||= @options.local
|
42
|
+
end
|
43
|
+
|
44
|
+
def os=(v)
|
45
|
+
@options.os = v
|
46
|
+
end
|
47
|
+
|
48
|
+
def os
|
49
|
+
@os ||= @options.os
|
50
|
+
end
|
51
|
+
|
52
|
+
def continent=(v)
|
53
|
+
@options.continent = v
|
54
|
+
end
|
55
|
+
|
56
|
+
def continent
|
57
|
+
@continent ||= @options.continent
|
58
|
+
end
|
59
|
+
|
60
|
+
def flavor=(v)
|
61
|
+
@options.flavor = v
|
62
|
+
end
|
63
|
+
|
64
|
+
def flavor
|
65
|
+
@flavor ||= @options.flavor
|
66
|
+
end
|
67
|
+
|
68
|
+
def quick=(v)
|
69
|
+
@options.quick = v
|
70
|
+
end
|
71
|
+
|
72
|
+
def quick
|
73
|
+
@quick ||= @options.quick
|
74
|
+
end
|
75
|
+
|
76
|
+
def path=(v)
|
77
|
+
@options.path = v
|
78
|
+
end
|
79
|
+
|
80
|
+
def path
|
81
|
+
@path ||= @options.path
|
82
|
+
end
|
83
|
+
|
84
|
+
def file=(v)
|
85
|
+
@options.file = v
|
86
|
+
end
|
87
|
+
|
88
|
+
def file
|
89
|
+
@file ||= @options.file
|
90
|
+
end
|
91
|
+
|
92
|
+
def save=(v)
|
93
|
+
@options.save = v
|
94
|
+
end
|
95
|
+
|
96
|
+
def save
|
97
|
+
@save ||= @options.save
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'curb'
|
2
|
+
|
3
|
+
module RankMirror
|
4
|
+
class Reachable
|
5
|
+
def initialize(uri,timeout)
|
6
|
+
@uri = uri
|
7
|
+
@timeout = timeout
|
8
|
+
end
|
9
|
+
|
10
|
+
def reachable?
|
11
|
+
begin
|
12
|
+
r = Curl::Easy.new(@uri)
|
13
|
+
r.timeout_ms = @timeout
|
14
|
+
r.perform
|
15
|
+
if r.response_code == 404
|
16
|
+
false
|
17
|
+
else
|
18
|
+
true
|
19
|
+
end
|
20
|
+
rescue
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module RankMirror
|
4
|
+
class RemoteOSS
|
5
|
+
def initialize(options)
|
6
|
+
@mirrors = []
|
7
|
+
@continent = ""
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch
|
12
|
+
cache = RankMirror::Cache.new("http://mirrors.opensuse.org").fetch
|
13
|
+
doc = Nokogiri::HTML(open(cache))
|
14
|
+
doc.root.element_children.last.element_children[-2].element_children[-1].element_children.each do |tr|
|
15
|
+
unless tr.children[1].attribute("class").nil?
|
16
|
+
@continent = tr.children[1].inner_text.delete!(":").delete("\s").downcase!
|
17
|
+
else
|
18
|
+
if @continent == @options.continent || @options.continent == "world"
|
19
|
+
country = tr.children[1].inner_text.strip!
|
20
|
+
unless country.nil?
|
21
|
+
tumbleweed = tr.children[15].children[0].nil? ? false : true
|
22
|
+
leap4220 = tr.children[17].children[0].nil? ? false : true
|
23
|
+
leap4210 = tr.children[27].children[0].nil? ? false : true
|
24
|
+
|
25
|
+
ftpobj = tr.children[7].children[0]
|
26
|
+
ftp = ftpobj.nil? ? nil : ftpobj.attribute("href").inner_text
|
27
|
+
httpobj = tr.children[5].children[0]
|
28
|
+
http = httpobj.nil? ? ftp : httpobj.attribute("href").inner_text
|
29
|
+
|
30
|
+
unless leap4210 || leap4220 || tumbleweed
|
31
|
+
status = RankMirror::Status.new(http,@options.os).get
|
32
|
+
unless status.nil?
|
33
|
+
tumbleweed = status["tumbleweed"]
|
34
|
+
leap4220 = status["leap4220"]
|
35
|
+
leap4210 = status["leap4210"]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@mirrors << http if eval(@options.flavor)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
return @mirrors
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module RankMirror
|
4
|
+
class RemotePackman
|
5
|
+
def initialize
|
6
|
+
@mirrors = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def fetch
|
10
|
+
cache = RankMirror::Cache.new("http://packman.links2linux.de/mirrors").fetch
|
11
|
+
doc = Nokogiri::HTML(open(cache))
|
12
|
+
doc.xpath('//td[@class="mirrortable mirror"]').each do |td|
|
13
|
+
unless td.at_xpath("a").nil? # ignore rsync mirror
|
14
|
+
v = td.at_xpath("a/@href").value
|
15
|
+
v << "/" unless /^.*\/$/.match(v)
|
16
|
+
v << "suse/"
|
17
|
+
@mirrors << v unless v.index("ftp://")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
return @mirrors
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'curb'
|
2
|
+
|
3
|
+
module RankMirror
|
4
|
+
class Speed
|
5
|
+
def initialize(uri)
|
6
|
+
@uri = uri
|
7
|
+
end
|
8
|
+
|
9
|
+
def get
|
10
|
+
if RankMirror::Reachable.new(@uri,500).reachable?
|
11
|
+
r = Curl::Easy.new(@uri)
|
12
|
+
r.perform
|
13
|
+
return r.download_speed / 1024.0
|
14
|
+
#else
|
15
|
+
# usually in this case, the mirror is online but:
|
16
|
+
# 1. has bad repodata. eg. not fully rsync the origin, or even empty.
|
17
|
+
# 2. volatile. sometimes at good speed, sometimes unreachable.
|
18
|
+
# 3. too slow. takes more than 5 seconds to start a download.
|
19
|
+
# not break here because we're about to find out the good mirrors, not the bad ones.
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module RankMirror
|
4
|
+
class Status
|
5
|
+
def initialize(uri,distro)
|
6
|
+
uri << "/" unless /^.*\/$/.match(uri)
|
7
|
+
@uri = uri
|
8
|
+
@distro = distro
|
9
|
+
end
|
10
|
+
|
11
|
+
def get
|
12
|
+
if RankMirror::Reachable.new(@uri,500).reachable?
|
13
|
+
case @distro
|
14
|
+
when "opensuse"
|
15
|
+
tumbleweed = "tumbleweed/repo/oss/suse/repodata/"
|
16
|
+
leap4220 = "distribution/leap/42.2/repo/oss/suse/repodata/"
|
17
|
+
leap4210 = "distribution/leap/42.1/repo/oss/suse/repodata/"
|
18
|
+
leap4230 = "distribution/leap/42.3/repo/oss/suse/repodata/"
|
19
|
+
|
20
|
+
checklist = {"tumbleweed"=>tumbleweed,"leap4220"=>leap4220,"leap4210"=>leap4210,"leap4230"=>leap4230}
|
21
|
+
mirror = OpenStruct.new
|
22
|
+
mirror.http = @uri
|
23
|
+
|
24
|
+
checklist.each do |k,v|
|
25
|
+
if RankMirror::Reachable.new(@uri + v,500).reachable?
|
26
|
+
mirror[k] = true
|
27
|
+
else
|
28
|
+
mirror[k] = false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
return mirror
|
32
|
+
when "packman"
|
33
|
+
tumbleweed = "Tumbleweed"
|
34
|
+
leap4220 = "Leap_42.2"
|
35
|
+
leap4210 = "Leap_42.1"
|
36
|
+
leap4230 = "Leap_42.3"
|
37
|
+
checklist = {"tumbleweed"=>tumbleweed,"leap4220"=>leap4220,"leap4210"=>leap4210,"leap4230"=>leap4230}
|
38
|
+
|
39
|
+
mirror = OpenStruct.new
|
40
|
+
mirror.http = @uri
|
41
|
+
|
42
|
+
checklist.each do |k,v|
|
43
|
+
if RankMirror::Reachable.new(@uri + "openSUSE_" + v + "/Essentials/repodata/",500).reachable?
|
44
|
+
mirror[k] = true
|
45
|
+
else
|
46
|
+
mirror[k] = false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
return mirror
|
50
|
+
else
|
51
|
+
end
|
52
|
+
else
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/rankmirror.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rankmirror/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rankmirror"
|
8
|
+
spec.version = RankMirror::VERSION
|
9
|
+
spec.authors = ["marguerite"]
|
10
|
+
spec.email = ["i@marguerite.su"]
|
11
|
+
|
12
|
+
spec.summary = %q{Rank mirrors by speed for common Linux distributions}
|
13
|
+
spec.description = %q{rankmirror is a command-line tool to sort mirrors by the real download speed for common Linux distributions. it supports both mirrors listed on the distro's website and the ones held in your local mirrorlists.}
|
14
|
+
spec.homepage = "http://github.com/marguerite/rankmirror"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
"public gem pushes."
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = "bin"
|
30
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_runtime_dependency "nokogiri", ">= 1.6.0"
|
34
|
+
spec.add_runtime_dependency "colorize", ">= 0.8.0"
|
35
|
+
spec.add_runtime_dependency "curb", ">= 0.9.0"
|
36
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
37
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
38
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rankmirror
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- marguerite
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.6.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.6.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: colorize
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.8.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.8.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: curb
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.9.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.9.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.13'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.13'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.0'
|
97
|
+
description: rankmirror is a command-line tool to sort mirrors by the real download
|
98
|
+
speed for common Linux distributions. it supports both mirrors listed on the distro's
|
99
|
+
website and the ones held in your local mirrorlists.
|
100
|
+
email:
|
101
|
+
- i@marguerite.su
|
102
|
+
executables:
|
103
|
+
- rankmirror
|
104
|
+
extensions: []
|
105
|
+
extra_rdoc_files: []
|
106
|
+
files:
|
107
|
+
- ".gitignore"
|
108
|
+
- ".rspec"
|
109
|
+
- ".travis.yml"
|
110
|
+
- COPYING
|
111
|
+
- Gemfile
|
112
|
+
- README.md
|
113
|
+
- Rakefile
|
114
|
+
- TODO
|
115
|
+
- bin/rankmirror
|
116
|
+
- lib/rankmirror.rb
|
117
|
+
- lib/rankmirror/cache.rb
|
118
|
+
- lib/rankmirror/config.rb
|
119
|
+
- lib/rankmirror/exception.rb
|
120
|
+
- lib/rankmirror/localoss.rb
|
121
|
+
- lib/rankmirror/localpackman.rb
|
122
|
+
- lib/rankmirror/mirrors.rb
|
123
|
+
- lib/rankmirror/opensuse.mirrorlist
|
124
|
+
- lib/rankmirror/options.rb
|
125
|
+
- lib/rankmirror/packman.mirrorlist
|
126
|
+
- lib/rankmirror/reachable.rb
|
127
|
+
- lib/rankmirror/remoteoss.rb
|
128
|
+
- lib/rankmirror/remotepackman.rb
|
129
|
+
- lib/rankmirror/speed.rb
|
130
|
+
- lib/rankmirror/status.rb
|
131
|
+
- lib/rankmirror/version.rb
|
132
|
+
- rankmirror.gemspec
|
133
|
+
homepage: http://github.com/marguerite/rankmirror
|
134
|
+
licenses:
|
135
|
+
- MIT
|
136
|
+
metadata:
|
137
|
+
allowed_push_host: https://rubygems.org
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 2.6.8
|
155
|
+
signing_key:
|
156
|
+
specification_version: 4
|
157
|
+
summary: Rank mirrors by speed for common Linux distributions
|
158
|
+
test_files: []
|