wonko_the_sane 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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