wonko_the_sane 0.1.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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/bin/wonko_the_sane +91 -0
  3. data/data/minecraft.json +668 -0
  4. data/data/mods.json +2475 -0
  5. data/data/sources.json +427 -0
  6. data/data/timestamps.json +13 -0
  7. data/lib/wonko_the_sane.rb +66 -0
  8. data/lib/wonko_the_sane/input/base_input.rb +55 -0
  9. data/lib/wonko_the_sane/input/forge_installer_profile_input.rb +134 -0
  10. data/lib/wonko_the_sane/input/forgefiles_mods_input.rb +30 -0
  11. data/lib/wonko_the_sane/input/jenkins_input.rb +46 -0
  12. data/lib/wonko_the_sane/input/mojang_input.rb +214 -0
  13. data/lib/wonko_the_sane/reader_writer.rb +163 -0
  14. data/lib/wonko_the_sane/registry.rb +61 -0
  15. data/lib/wonko_the_sane/rules.rb +69 -0
  16. data/lib/wonko_the_sane/timestamps.rb +30 -0
  17. data/lib/wonko_the_sane/tools/update_nem.rb +85 -0
  18. data/lib/wonko_the_sane/util/configuration.rb +35 -0
  19. data/lib/wonko_the_sane/util/deep_storage_cache.rb +65 -0
  20. data/lib/wonko_the_sane/util/extraction_cache.rb +30 -0
  21. data/lib/wonko_the_sane/util/file_hash_cache.rb +42 -0
  22. data/lib/wonko_the_sane/util/http_cache.rb +143 -0
  23. data/lib/wonko_the_sane/util/maven_identifier.rb +39 -0
  24. data/lib/wonko_the_sane/util/task_stack.rb +21 -0
  25. data/lib/wonko_the_sane/version.rb +3 -0
  26. data/lib/wonko_the_sane/version_index.rb +33 -0
  27. data/lib/wonko_the_sane/version_parser.rb +115 -0
  28. data/lib/wonko_the_sane/versionlists/base_version_list.rb +103 -0
  29. data/lib/wonko_the_sane/versionlists/curse_version_list.rb +52 -0
  30. data/lib/wonko_the_sane/versionlists/forge_version_list.rb +142 -0
  31. data/lib/wonko_the_sane/versionlists/forgefiles_mods_list.rb +24 -0
  32. data/lib/wonko_the_sane/versionlists/jenkins_version_list.rb +29 -0
  33. data/lib/wonko_the_sane/versionlists/liteloader_version_list.rb +66 -0
  34. data/lib/wonko_the_sane/versionlists/vanilla_legacy_version_list.rb +39 -0
  35. data/lib/wonko_the_sane/versionlists/vanilla_version_list.rb +35 -0
  36. data/lib/wonko_the_sane/wonko_version.rb +184 -0
  37. metadata +324 -0
@@ -0,0 +1,65 @@
1
+ require 'aws-sdk-resources'
2
+ require 'wonko_the_sane/util/http_cache'
3
+
4
+ module WonkoTheSane
5
+ module Util
6
+ class DeepStorageCache
7
+ def initialize(manifest_file)
8
+ lines = File.read('.aws-credentials').split "\n"
9
+ @resource = Aws::S3::Resource.new region: 'eu-west-1', credentials: Aws::Credentials.new(lines[0], lines[1])
10
+ @bucket = @resource.bucket 'wonkoweb-02jandal-xyz'
11
+
12
+ @manifest = @bucket.object 'manifest.json'
13
+ @entries = @manifest.exists? ? JSON.parse(@manifest.get.body, symbolize_keys: true) : {}
14
+ end
15
+
16
+ def get_info(url, options = {})
17
+ return @entries[url] if @entries.key? url
18
+
19
+ ctxt = options[:ctxt] || 'DeepStorageCache'
20
+
21
+ file = HTTPCache.file url, check_stale: false, ctxt: options[:ctxt]
22
+ info = info_for_file file, url
23
+
24
+ @entries[url] = info
25
+ @manifest.put body: JSON.pretty_generate(@entries)
26
+
27
+ object = @bucket.object info[:file]
28
+ unless object.exists? && object.size == info[:size]
29
+ TaskStack.in_background do
30
+ # convert the hex-encoded md5 to a base64-encoded md5, which is what S3 expects
31
+ # http://anthonylewis.com/2011/02/09/to-hex-and-back-with-ruby/
32
+ md5 = [info[:md5].scan(/../).map { |x| x.hex.chr }.join].pack 'm0'
33
+
34
+ Logging.logger[ctxt].debug "Uploading backup of #{url} to S3..."
35
+ object.put body: file, content_md5: md5, metadata: Hash[info.map { |k,v| [k.to_s, v.to_s]}]
36
+ Logging.logger[ctxt].debug 'Backup successfully uploaded to S3'
37
+ end
38
+ end
39
+
40
+ info
41
+ end
42
+
43
+ def self.get_info(url, options = {})
44
+ if File.exists? '.aws-credentials'
45
+ @@instance ||= DeepStorageCache.new 'cache/deep_storage.json'
46
+ @@instance.get_info url, options
47
+ else
48
+ info_for_file HTTPCache.file(url, check_stale: false, ctxt: options[:ctxt]), url
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def info_for_file(file, url)
55
+ {
56
+ url: url,
57
+ file: url.gsub(/[&:$@=+,?\\^`><\{\}\[\]#%'"~|]/, '_'),
58
+ size: file.size,
59
+ md5: FileHashCache.get_md5(file),
60
+ sha256: FileHashCache.get(file)
61
+ }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,30 @@
1
+ class ExtractionCache
2
+ def initialize(basedir)
3
+ @basedir = basedir
4
+ FileUtils.mkdir_p @basedir unless Dir.exist? @basedir
5
+ end
6
+
7
+ def get(archive, type, file)
8
+ out = path(archive, type, file)
9
+ FileUtils.mkdir_p File.dirname(out) unless Dir.exist? File.dirname(out)
10
+ if not File.exist? out
11
+ if type == :zip
12
+ Zip::File.open archive do |arch|
13
+ File.write out, arch.glob(file).first.get_input_stream.read
14
+ end
15
+ end
16
+ end
17
+
18
+ return File.read out
19
+ end
20
+
21
+ @@defaultCache = ExtractionCache.new 'cache/extraction'
22
+ def self.get(archive, type, file)
23
+ @@defaultCache.get archive, type, file
24
+ end
25
+
26
+ private
27
+ def path(archive, type, file)
28
+ @basedir + '/' + File.basename(archive) + '/' + file
29
+ end
30
+ end
@@ -0,0 +1,42 @@
1
+ class FileHashCache
2
+ def initialize(file, algorithm)
3
+ @file = file
4
+ @algorithm = algorithm
5
+ @data = JSON.parse File.read(@file), symbolize_names: true if File.exists? @file
6
+ @data ||= {}
7
+ end
8
+
9
+ def get(file)
10
+ name = (file.is_a?(File) ? file.path : file).to_sym
11
+ timestamp = (file.is_a?(File) ? file.mtime : File.mtime(file)).to_i
12
+ size = file.is_a?(File) ? file.size : File.size(file)
13
+ if not @data[name] or not @data[name][:timestamp] == timestamp or not @data[name][:size] == size
14
+ hash = digest(file.is_a?(File) ? file.read : File.read(file))
15
+ @data[name] = {
16
+ timestamp: timestamp,
17
+ size: size,
18
+ hash: hash
19
+ }
20
+ File.write @file, JSON.pretty_generate(@data)
21
+ end
22
+ return @data[name][:hash]
23
+ end
24
+
25
+ def digest(data)
26
+ if @algorithm == :sha256
27
+ Digest::SHA256.hexdigest data
28
+ elsif @algorithm == :md5
29
+ Digest::MD5.hexdigest data
30
+ end
31
+ end
32
+
33
+ @@defaultCache = FileHashCache.new 'cache/filehashes', :sha256
34
+ def self.get(file)
35
+ @@defaultCache.get file
36
+ end
37
+
38
+ @@md5Cache = FileHashCache.new 'cache/filehashes.md5', :md5
39
+ def self.get_md5(file)
40
+ @@md5Cache.get file
41
+ end
42
+ end
@@ -0,0 +1,143 @@
1
+ # http://www.ericson.net/content/2011/04/caching-http-requests-with-ruby/
2
+ # TODO proper etags and other caching stuff
3
+ class HTTPCache
4
+ def initialize(basedir)
5
+ @basedir = basedir
6
+ FileUtils.mkdir_p @basedir unless Dir.exist? @basedir
7
+ @etags = {}
8
+ @etags = JSON.parse File.read(@basedir + '/etags.json') if File.exists? @basedir + '/etags.json'
9
+ end
10
+
11
+ # HTTP GETs a url if it doesn't exist locally
12
+ def get(ctxt, url, key, check_stale = true)
13
+ fetch ctxt, url, key, check_stale
14
+ IO.read @basedir + '/' + key
15
+ end
16
+
17
+ def file(ctxt, url, key, check_stale = true)
18
+ fetch ctxt, url, key, check_stale
19
+ File.new @basedir + '/' + key, 'r'
20
+ end
21
+
22
+ private
23
+ def fetch(ctxt, url, key, check_stale)
24
+ cached_path = @basedir + '/' + key
25
+ cached_dir = File.dirname cached_path
26
+ FileUtils.mkdir_p cached_dir unless Dir.exist? cached_dir
27
+
28
+ TaskStack.in_background do
29
+ if should_check cached_path, check_stale
30
+ Logging.logger[ctxt.to_s].debug "DL: #{url}"
31
+ resp = http_get ctxt.to_s, url, cached_path
32
+ unless resp == nil
33
+ File.open(cached_path, 'w') do |f|
34
+ f.write resp.body
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # get a file, using the local cached file modified timestamp to make sture we don't re-download stuff pointlessly
42
+ # this also *should* handle redirection properly
43
+ def http_get(ctxt, url, cached_path, limit = 10, http = nil)
44
+ # too many redirects...
45
+ raise ArgumentError, 'too many HTTP redirects' if limit == 0
46
+
47
+ uri = url.is_a?(URI) ? url : URI.parse(url)
48
+
49
+ local_date = Time.parse("1985-10-28")
50
+ local_date = File.mtime cached_path if File.exists? cached_path
51
+
52
+ if http.nil?
53
+ Net::HTTP.start uri.hostname, uri.port, :use_ssl => uri.scheme == 'https' do |http|
54
+ return http_get_internal ctxt, uri, cached_path, limit, http, local_date
55
+ end
56
+ else
57
+ return http_get_internal ctxt, uri, cached_path, limit, http, local_date
58
+ end
59
+ end
60
+ def http_get_internal(ctxt, uri, cached_path, limit = 10, http = nil, local_date = nil)
61
+ existing_etag = @etags[uri]
62
+
63
+ # start by doing a HEAD request
64
+ head_req = Net::HTTP::Head.new uri
65
+ head_req.add_field 'If-None-Match', existing_etag if existing_etag
66
+ head_req.add_field 'If-Modified-Since', local_date.httpdate
67
+ head_resp = http.request head_req
68
+
69
+ case head_resp
70
+ when Net::HTTPSuccess
71
+ # don't re-check this
72
+ checked cached_path
73
+
74
+ remote_date = head_resp['Last-Modified'] ? Time.httpdate(head_resp['Last-Modified']) : Time.now
75
+ new_etag = head_resp['ETag']
76
+
77
+ # if the remote resource has been modified later than the local file, grab it and return it
78
+ if remote_date > local_date || existing_etag != new_etag || !file_valid?(head_resp, cached_path)
79
+ req = Net::HTTP::Get.new(uri)
80
+ resp = http.request Net::HTTP::Get.new(uri)
81
+ Logging.logger[ctxt].debug 'GOT FULL FILE'
82
+
83
+ @etags[uri] = new_etag if new_etag
84
+ File.write @basedir + '/etags.json', JSON.pretty_generate(@etags)
85
+
86
+ return resp
87
+ else
88
+ Logging.logger[ctxt].debug 'CACHE HIT'
89
+ return nil
90
+ end
91
+ when Net::HTTPRedirection
92
+ if head_resp.code == "304"
93
+ Logging.logger[ctxt].debug 'CACHE HIT'
94
+ checked cached_path
95
+ return nil
96
+ end
97
+
98
+ location = head_resp['Location']
99
+ Logging.logger[ctxt].debug "Redirected to #{location} - code #{head_resp.code}"
100
+ newurl = URI.parse location
101
+ newurl = URI.join uri.to_s, location if newurl.relative?
102
+ return http_get ctxt, newurl, cached_path, limit - 1, http
103
+ else
104
+ Logging.logger[ctxt].warn "#{location} failed: #{head_resp.code}"
105
+ checked cached_path
106
+ return nil
107
+ end
108
+ end
109
+
110
+ def file_valid?(response, path)
111
+ if response['Content-Length']
112
+ return false if response['Content-Length'].to_i != File.size(path)
113
+ end
114
+ if response['Content-MD5']
115
+ return false if response['Content-MD5'] != FileHashCache.get_md5(path)
116
+ end
117
+ return true
118
+ end
119
+
120
+ @@checked_paths = Set.new
121
+ def should_check(cached_path, check_stale)
122
+ # if the file doesn't exist locally, or we should check for stale cache
123
+ if !File.exist? cached_path or check_stale
124
+ # but only once per run
125
+ return !@@checked_paths.include?(cached_path)
126
+ end
127
+ # otherwise don't check
128
+ return false
129
+ end
130
+
131
+ def checked(cached_path)
132
+ @@checked_paths.add cached_path
133
+ end
134
+
135
+ public
136
+ @@defaultCatcher = HTTPCache.new 'cache/network'
137
+ def self.get(url, options = {})
138
+ @@defaultCatcher.get(options[:ctxt] || 'Download', url, (options.key?(:key) ? options[:key] : url), options[:check_stale] || false)
139
+ end
140
+ def self.file(url, options = {})
141
+ @@defaultCatcher.file(options[:ctxt] || 'Download', url, (options.key?(:key) ? options[:key] : url), options[:check_stale] || false)
142
+ end
143
+ end
@@ -0,0 +1,39 @@
1
+ module WonkoTheSane
2
+ module Util
3
+ class MavenIdentifier
4
+ attr_accessor :group
5
+ attr_accessor :artifact
6
+ attr_accessor :version
7
+ attr_accessor :classifier
8
+ attr_accessor :extension
9
+
10
+ def initialize(string)
11
+ parts = string.match /(?<group>[^:@]+):(?<artifact>[^:@]+):(?<version>[^:@]+)(:(?<classifier>[^:@]+))?(@(?<extension>[^:@]+))?/
12
+ @group = parts[:group]
13
+ @artifact = parts[:artifact]
14
+ @version = parts[:version]
15
+ @classifier = parts[:classifier]
16
+ @extension = parts[:extension] ? parts[:extension] : 'jar'
17
+ end
18
+
19
+ def to_path
20
+ path = @group.gsub(/\./, '/') + '/' + @artifact + '/' + @version + '/' + @artifact + '-' + @version
21
+ if @classifier
22
+ path = path + '-' + @classifier
23
+ end
24
+ return path + '.' + @extension
25
+ end
26
+
27
+ def to_name
28
+ name = @group + ':' + @artifact + ':' + @version
29
+ if @classifier
30
+ name = name + ':' + @classifier
31
+ end
32
+ if @extension != 'jar'
33
+ name = name + '@' + @extension
34
+ end
35
+ return name
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ class TaskStack
2
+ @@queue = []
3
+ def self.push(task)
4
+ @@queue.push task
5
+ end
6
+ def self.push_defered(task)
7
+ @@queue.unshift task
8
+ end
9
+ def self.pop
10
+ task = @@queue.pop
11
+ task.call
12
+ end
13
+ def self.pop_all
14
+ self.pop until @@queue.empty?
15
+ end
16
+ def self.in_background(&block)
17
+ thread = Thread.new &block
18
+ TaskStack.pop_all
19
+ thread.join
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module WonkoTheSane
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,33 @@
1
+ class VersionIndex
2
+ attr_accessor :uid
3
+ attr_accessor :name
4
+ attr_reader :versions
5
+
6
+ def initialize(uid)
7
+ @uid = uid
8
+ @versions = []
9
+ end
10
+
11
+ def add_version(version)
12
+ if version.is_a? WonkoVersion
13
+ remove_version version # remove any previous versions
14
+ @versions << version
15
+ end
16
+ end
17
+ def remove_version(version)
18
+ @versions.select! do |ver|
19
+ version.version != ver.version
20
+ end
21
+ end
22
+ def self.get_full_version(version)
23
+ if File.exist? version.local_filename
24
+ Reader.read_version File.read(version.local_filename)
25
+ else
26
+ nil
27
+ end
28
+ end
29
+
30
+ def self.local_filename(uid)
31
+ 'files/' + uid + '.json'
32
+ end
33
+ end
@@ -0,0 +1,115 @@
1
+ class VersionParser
2
+ private
3
+ @@cache = {}
4
+ def self.parse(string)
5
+ if @@cache.has_key? string
6
+ return @@cache[string]
7
+ end
8
+ appendix = string.scan(/\-.*$/).first
9
+ string = string.sub /\-.*$/, ''
10
+ sections = string.split '.'
11
+ sections.map! do |sec|
12
+ test = Integer sec rescue nil
13
+ if test.nil?
14
+ sec
15
+ else
16
+ test
17
+ end
18
+ end
19
+
20
+ result = {
21
+ appendix: appendix,
22
+ sections: sections
23
+ }
24
+ @@cache[string] = result
25
+ return result
26
+ end
27
+
28
+ def self.appendix_values(appendix)
29
+ str = appendix.scan /[a-zA-Z]*/
30
+ digits = appendix.scan(/\d*/).join.to_i
31
+ ret = case str
32
+ when 'a'
33
+ [0, digits]
34
+ when 'alpha'
35
+ [0, digits]
36
+ when 'b'
37
+ [1, digits]
38
+ when 'beta'
39
+ [1, digits]
40
+ when 'rc'
41
+ [2, digits]
42
+ when 'pre'
43
+ [2, digits]
44
+ end
45
+ return ret ? ret : [-1, digits]
46
+ end
47
+
48
+ def self.compare_values(first, second)
49
+ if first < second
50
+ -1
51
+ elsif first > second
52
+ 1
53
+ else
54
+ 0
55
+ end
56
+ end
57
+
58
+ public
59
+ def self.compare(string1, string2)
60
+ par1 = VersionParser.parse string1
61
+ par2 = VersionParser.parse string2
62
+ size = [par1[:sections].length, par2[:sections].length].max
63
+ ret = 0
64
+ size.times do |index|
65
+ val1 = par1[:sections].length > index ? par1[:sections][index] : 0
66
+ val2 = par2[:sections].length > index ? par2[:sections][index] : 0
67
+ if val1.is_a? Integer and val2.is_a? Integer
68
+ ret = VersionParser.compare_values val1, val2
69
+ elsif val1.is_a? Integer
70
+ ret = VersionParser.compare_values val1.to_s, val2
71
+ elsif val2.is_a? Integer
72
+ ret = VersionParser.compare_values val1, val2.to_s
73
+ else
74
+ ret = VersionParser.compare_values val1.to_s, val2.to_s
75
+ end
76
+ break unless ret == 0
77
+ end
78
+ if ret == 0
79
+ if par1[:appendix] and par2[:appendix]
80
+ appendix1 = VersionParser.appendix_values par1[:appendix]
81
+ appendix2 = VersionParser.appendix_values par2[:appendix]
82
+ ret = VersionParser.compare_values appendix1[0], appendix2[0]
83
+ if ret == 0
84
+ ret = VersionParser.compare_values appendix1[1], appendix2[1]
85
+ end
86
+ elsif par1[:appendix]
87
+ ret = -1
88
+ elsif par2[:appendix]
89
+ ret = 1
90
+ end
91
+ end
92
+ return ret
93
+ end
94
+
95
+ def self.less?(string1, string2)
96
+ VersionParser.compare(string1, string2) == -1
97
+ end
98
+ def self.greater?(string1, string2)
99
+ VersionParser.compare(string1, string2) == 1
100
+ end
101
+ def self.equal?(string1, string2)
102
+ VersionParser.compare(string1, string2) == 0
103
+ end
104
+ def self.less_or_equal?(string1, string2)
105
+ ret = VersionParser.compare string1, string2
106
+ ret == 0 or ret == -1
107
+ end
108
+ def self.greater_or_equal?(string1, string2)
109
+ ret = VersionParser.compare string1, string2
110
+ ret == 0 or ret == 1
111
+ end
112
+ def self.not_equal?(string1, string2)
113
+ VersionParser.compare(string1, string2) != 0
114
+ end
115
+ end