u3d 0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.licenses.json +19 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +43 -0
  6. data/Gemfile +5 -0
  7. data/Gemfile.lock +98 -0
  8. data/LICENSE +21 -0
  9. data/LICENSE.fastlane +22 -0
  10. data/LOG_RULES.md +170 -0
  11. data/README.md +72 -0
  12. data/Rakefile +28 -0
  13. data/TODO.md +15 -0
  14. data/build.sh +5 -0
  15. data/config/log_rules.json +230 -0
  16. data/examples/Example1/.gitignore +19 -0
  17. data/examples/Example1/Assets/Editor.meta +9 -0
  18. data/examples/Example1/Assets/Editor/EditorRun.cs +23 -0
  19. data/examples/Example1/Assets/Editor/EditorRun.cs.meta +12 -0
  20. data/examples/Example1/Assets/Editor/FileSystemUtil.cs +26 -0
  21. data/examples/Example1/Assets/Editor/FileSystemUtil.cs.meta +12 -0
  22. data/examples/Example1/Assets/Scene1.unity +264 -0
  23. data/examples/Example1/Assets/Scene1.unity.meta +8 -0
  24. data/examples/Example1/Gemfile +8 -0
  25. data/examples/Example1/Gemfile.lock +165 -0
  26. data/examples/Example1/ProjectSettings/AudioManager.asset +16 -0
  27. data/examples/Example1/ProjectSettings/ClusterInputManager.asset +6 -0
  28. data/examples/Example1/ProjectSettings/DynamicsManager.asset +18 -0
  29. data/examples/Example1/ProjectSettings/EditorBuildSettings.asset +7 -0
  30. data/examples/Example1/ProjectSettings/EditorSettings.asset +14 -0
  31. data/examples/Example1/ProjectSettings/GraphicsSettings.asset +61 -0
  32. data/examples/Example1/ProjectSettings/InputManager.asset +295 -0
  33. data/examples/Example1/ProjectSettings/NavMeshAreas.asset +89 -0
  34. data/examples/Example1/ProjectSettings/NetworkManager.asset +8 -0
  35. data/examples/Example1/ProjectSettings/Physics2DSettings.asset +35 -0
  36. data/examples/Example1/ProjectSettings/ProjectSettings.asset +591 -0
  37. data/examples/Example1/ProjectSettings/ProjectVersion.txt +1 -0
  38. data/examples/Example1/ProjectSettings/QualitySettings.asset +180 -0
  39. data/examples/Example1/ProjectSettings/TagManager.asset +43 -0
  40. data/examples/Example1/ProjectSettings/TimeManager.asset +9 -0
  41. data/examples/Example1/ProjectSettings/UnityConnectSettings.asset +32 -0
  42. data/examples/Example1/README.md +5 -0
  43. data/examples/Example1/Rakefile +5 -0
  44. data/examples/Example1/fastlane/Fastfile +4 -0
  45. data/examples/Example1/fastlane/Pluginfile +1 -0
  46. data/examples/Example1/run.sh +1 -0
  47. data/examples/Example2/.gitignore +20 -0
  48. data/examples/Example2/Assets/Editor.meta +9 -0
  49. data/examples/Example2/Assets/Editor/EditorRun.cs +33 -0
  50. data/examples/Example2/Assets/Editor/EditorRun.cs.meta +12 -0
  51. data/examples/Example2/Assets/Editor/PostprocessBuildPlayer.cs +92 -0
  52. data/examples/Example2/Assets/Editor/PostprocessBuildPlayer.cs.meta +8 -0
  53. data/examples/Example2/Assets/Editor/PostprocessBuildPlayer_log.sh +31 -0
  54. data/examples/Example2/Assets/Editor/PostprocessBuildPlayer_log.sh.meta +8 -0
  55. data/examples/Example2/Assets/Editor/SimpleBuildSetup.cs +20 -0
  56. data/examples/Example2/Assets/Editor/SimpleBuildSetup.cs.meta +12 -0
  57. data/examples/Example2/Assets/Scene.unity +278 -0
  58. data/examples/Example2/Assets/Scene.unity.meta +8 -0
  59. data/examples/Example2/Gemfile +8 -0
  60. data/examples/Example2/Gemfile.lock +165 -0
  61. data/examples/Example2/ProjectSettings/AudioManager.asset +17 -0
  62. data/examples/Example2/ProjectSettings/ClusterInputManager.asset +6 -0
  63. data/examples/Example2/ProjectSettings/DynamicsManager.asset +19 -0
  64. data/examples/Example2/ProjectSettings/EditorBuildSettings.asset +10 -0
  65. data/examples/Example2/ProjectSettings/EditorSettings.asset +14 -0
  66. data/examples/Example2/ProjectSettings/GraphicsSettings.asset +63 -0
  67. data/examples/Example2/ProjectSettings/InputManager.asset +295 -0
  68. data/examples/Example2/ProjectSettings/NavMeshAreas.asset +89 -0
  69. data/examples/Example2/ProjectSettings/NetworkManager.asset +8 -0
  70. data/examples/Example2/ProjectSettings/Physics2DSettings.asset +36 -0
  71. data/examples/Example2/ProjectSettings/ProjectSettings.asset +591 -0
  72. data/examples/Example2/ProjectSettings/ProjectVersion.txt +1 -0
  73. data/examples/Example2/ProjectSettings/QualitySettings.asset +193 -0
  74. data/examples/Example2/ProjectSettings/TagManager.asset +43 -0
  75. data/examples/Example2/ProjectSettings/TimeManager.asset +9 -0
  76. data/examples/Example2/ProjectSettings/UnityConnectSettings.asset +34 -0
  77. data/examples/Example2/README.md +10 -0
  78. data/examples/Example2/fastlane/Fastfile +4 -0
  79. data/examples/Example2/fastlane/Pluginfile +1 -0
  80. data/exe/u3d +7 -0
  81. data/fastlane-plugin-u3d/.gitignore +10 -0
  82. data/fastlane-plugin-u3d/.licenses.json +9 -0
  83. data/fastlane-plugin-u3d/.rspec +3 -0
  84. data/fastlane-plugin-u3d/.rubocop.yml +253 -0
  85. data/fastlane-plugin-u3d/.travis.yml +4 -0
  86. data/fastlane-plugin-u3d/Gemfile +6 -0
  87. data/fastlane-plugin-u3d/LICENSE +21 -0
  88. data/fastlane-plugin-u3d/README.md +52 -0
  89. data/fastlane-plugin-u3d/Rakefile +9 -0
  90. data/fastlane-plugin-u3d/circle.yml +9 -0
  91. data/fastlane-plugin-u3d/fastlane-plugin-u3d.gemspec +31 -0
  92. data/fastlane-plugin-u3d/fastlane/Fastfile +3 -0
  93. data/fastlane-plugin-u3d/fastlane/Pluginfile +1 -0
  94. data/fastlane-plugin-u3d/lib/fastlane/plugin/u3d.rb +38 -0
  95. data/fastlane-plugin-u3d/lib/fastlane/plugin/u3d/actions/u3d_action.rb +80 -0
  96. data/fastlane-plugin-u3d/lib/fastlane/plugin/u3d/helper/u3d_helper.rb +34 -0
  97. data/fastlane-plugin-u3d/lib/fastlane/plugin/u3d/version.rb +27 -0
  98. data/fastlane-plugin-u3d/spec/spec_helper.rb +32 -0
  99. data/lib/u3d.rb +33 -0
  100. data/lib/u3d/cache.rb +120 -0
  101. data/lib/u3d/commands.rb +307 -0
  102. data/lib/u3d/commands_generator.rb +163 -0
  103. data/lib/u3d/downloader.rb +363 -0
  104. data/lib/u3d/iniparser.rb +83 -0
  105. data/lib/u3d/installer.rb +445 -0
  106. data/lib/u3d/log_analyzer.rb +221 -0
  107. data/lib/u3d/unity_version_number.rb +71 -0
  108. data/lib/u3d/unity_versions.rb +207 -0
  109. data/lib/u3d/utils.rb +121 -0
  110. data/lib/u3d/version.rb +31 -0
  111. data/lib/u3d_core.rb +30 -0
  112. data/lib/u3d_core/command_executor.rb +134 -0
  113. data/lib/u3d_core/command_runner.rb +93 -0
  114. data/lib/u3d_core/credentials.rb +116 -0
  115. data/lib/u3d_core/globals.rb +84 -0
  116. data/lib/u3d_core/helper.rb +149 -0
  117. data/lib/u3d_core/ui/disable_colors.rb +40 -0
  118. data/lib/u3d_core/ui/implementations/shell.rb +157 -0
  119. data/lib/u3d_core/ui/interface.rb +182 -0
  120. data/lib/u3d_core/ui/ui.rb +49 -0
  121. data/local_gem_install.sh +6 -0
  122. data/scripts/be +14 -0
  123. data/u3d.gemspec +41 -0
  124. metadata +388 -0
@@ -0,0 +1,221 @@
1
+ ## --- BEGIN LICENSE BLOCK ---
2
+ # Copyright (c) 2016-present WeWantToKnow AS
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ # SOFTWARE.
21
+ ## --- END LICENSE BLOCK ---
22
+
23
+ require 'json'
24
+
25
+ module U3d
26
+ # Analyzes log by filtering output along a set of rules
27
+ class LogAnalyzer
28
+ RULES_PATH = File.expand_path('../../../config/log_rules.json', __FILE__)
29
+ MEMORY_SIZE = 10
30
+
31
+ def initialize
32
+ @lines_memory = Array.new(MEMORY_SIZE)
33
+ @active_phase = nil
34
+ @active_rule = nil
35
+ @context = {}
36
+ @rule_lines_buffer = []
37
+ @generic_rules, @phases = load_rules
38
+ end
39
+
40
+ def load_rules
41
+ data = {}
42
+ generic_rules = {}
43
+ phases = {}
44
+ File.open(RULES_PATH, 'r') do |f|
45
+ data = JSON.parse(f.read)
46
+ end
47
+ if data['GENERAL'] && data['GENERAL']['active']
48
+ data['GENERAL']['rules'].each do |rn, r|
49
+ generic_rules[rn] = r if parse_rule(r)
50
+ end
51
+ end
52
+ data.delete('GENERAL')
53
+ data.each do |name, phase|
54
+ # Phase parsing
55
+ next unless phase['active']
56
+ next if phase['phase_start_pattern'].nil?
57
+ phase['phase_start_pattern'] = Regexp.new phase['phase_start_pattern']
58
+ phase['phase_end_pattern'] = Regexp.new phase['phase_end_pattern'] if phase['phase_end_pattern']
59
+ phase.delete('comment')
60
+ # Rules parsing
61
+ temp_rules = {}
62
+ unless phase['silent'] == true
63
+ phase['rules'].each do |rn, r|
64
+ temp_rules[rn] = r if parse_rule(r)
65
+ end
66
+ end
67
+ phase['rules'] = temp_rules
68
+ phases[name] = phase
69
+ end
70
+ return generic_rules, phases
71
+ end
72
+
73
+ def parse_line(line)
74
+ # Insert new line and remove last stored line
75
+ @lines_memory.push(line).shift
76
+
77
+ # Check if phase is changing
78
+ @phases.each do |name, phase|
79
+ next if name == @active_phase
80
+ next unless line =~ phase['phase_start_pattern']
81
+ if @active_rule
82
+ # Active rule should be finished
83
+ # If it is still active during phase change, it means that something went wrong
84
+ UI.error("[#{@active_phase}] Could not finish active rule '#{@active_rule}'. Aborting it.")
85
+ @active_rule = nil
86
+ end
87
+ @active_phase = name
88
+ @context.clear
89
+ @rule_lines_buffer.clear
90
+ UI.verbose("--- Beginning #{name} phase ---")
91
+ break
92
+ end
93
+
94
+ apply_ruleset = lambda do |ruleset, header|
95
+ # Apply the active rule
96
+ if @active_rule && ruleset[@active_rule]
97
+ rule = ruleset[@active_rule]
98
+ pattern = rule['end_pattern']
99
+
100
+ # Is it the end of the rule?
101
+ if line =~ pattern
102
+ unless @rule_lines_buffer.empty?
103
+ @rule_lines_buffer.each do |l|
104
+ UI.send(rule['type'], "[#{header}] " + l)
105
+ end
106
+ end
107
+ if rule['end_message'] != false
108
+ if rule['end_message']
109
+ match = line.match(pattern)
110
+ params = match.names.map(&:to_sym).zip(match.captures).to_h
111
+ message = rule['end_message'] % params.merge(@context)
112
+ else
113
+ message = line.chomp
114
+ end
115
+ message = "[#{header}] " + message
116
+ UI.send(rule['type'], message)
117
+ end
118
+ @active_rule = nil
119
+ @context.clear
120
+ @rule_lines_buffer.clear
121
+
122
+ # It's not the end of the rules, should the line be stored?
123
+ elsif rule['store_lines']
124
+ match = false
125
+ if rule['ignore_lines']
126
+ rule['ignore_lines'].each do |pat|
127
+ if line =~ pat
128
+ match = true
129
+ break
130
+ end
131
+ end
132
+ end
133
+ @rule_lines_buffer << line.chomp unless match
134
+ end
135
+ end
136
+
137
+ # If there is no active rule, try to apply a new one
138
+ if @active_rule.nil?
139
+ ruleset.each do |rn, r|
140
+ pattern = r['start_pattern']
141
+ next unless line =~ pattern
142
+ @active_rule = rn if r['end_pattern']
143
+ match = line.match(pattern)
144
+ @context = match.names.map(&:to_sym).zip(match.captures).to_h
145
+ if r['fetch_line_at_index'] || r['fetch_first_line_not_matching']
146
+ if r['fetch_line_at_index']
147
+ fetched_line = @lines_memory.reverse[r['fetch_line_at_index']]
148
+ else
149
+ fetched_line = nil
150
+ @lines_memory.reverse.each do |l|
151
+ match = false
152
+ r['fetch_first_line_not_matching'].each do |pat|
153
+ next unless l =~ pat
154
+ match = true
155
+ break
156
+ end
157
+ next if match
158
+ fetched_line = l
159
+ break
160
+ end
161
+ end
162
+ if fetched_line
163
+ if r['fetched_line_pattern']
164
+ match = fetched_line.match(r['fetched_line_pattern'])
165
+ @context.merge!(match.names.map(&:to_sym).zip(match.captures).to_h)
166
+ end
167
+ if r['fetched_line_message'] != false
168
+ message = if r['fetched_line_message']
169
+ r['fetched_line_message'] % @context
170
+ else
171
+ fetched_line.chomp
172
+ end
173
+ message = "[#{header}] " + message
174
+ UI.send(r['type'], message)
175
+ end
176
+ end
177
+ end
178
+ if r['start_message'] != false
179
+ message = if r['start_message']
180
+ r['start_message'] % @context
181
+ else
182
+ line.chomp
183
+ end
184
+ message = "[#{header}] " + message
185
+ UI.send(r['type'], message)
186
+ end
187
+ break
188
+ end
189
+ end
190
+ end
191
+
192
+ apply_ruleset.call(@phases[@active_phase]['rules'], @active_phase) if @active_phase
193
+ apply_ruleset.call(@generic_rules, 'GENERAL')
194
+ end
195
+
196
+ private
197
+
198
+ def parse_rule(r)
199
+ return false unless r['active']
200
+ return false if r['start_pattern'].nil?
201
+ r['start_pattern'] = Regexp.new r['start_pattern']
202
+ r['end_pattern'] = Regexp.new r['end_pattern'] if r['end_pattern']
203
+ if r['fetch_line_at_index']
204
+ r.delete('fetch_line_at_index') if r['fetch_line_at_index'] >= MEMORY_SIZE
205
+ r.delete('fetch_line_at_index') if r['fetch_line_at_index'] <= 0
206
+ elsif r['fetch_first_line_not_matching']
207
+ r['fetch_first_line_not_matching'].map! { |pat| Regexp.new pat }
208
+ end
209
+ if r['fetch_line_at_index'] || r['fetch_first_line_not_matching']
210
+ r['fetched_line_pattern'] = Regexp.new r['fetched_line_pattern'] if r['fetched_line_pattern']
211
+ end
212
+ r['type'] = 'important' if r['type'] == 'warning'
213
+ if r['type'] && r['type'] != 'error' && r['type'] != 'important' && r['type'] != 'success'
214
+ r['type'] = 'message'
215
+ end
216
+ r['type'] ||= 'message'
217
+ r['ignore_lines'].map! { |pat| Regexp.new pat } if r['ignore_lines']
218
+ true
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,71 @@
1
+ ## --- BEGIN LICENSE BLOCK ---
2
+ # Copyright (c) 2016-present WeWantToKnow AS
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ # SOFTWARE.
21
+ ## --- END LICENSE BLOCK ---
22
+
23
+ require 'u3d/utils'
24
+
25
+ module U3d
26
+ class UnityVersionNumber
27
+ attr_reader :parts
28
+
29
+ def initialize(version)
30
+ parsed = Utils.parse_unity_version(version)
31
+ parsed.each_with_index do |val, index|
32
+ next if val.nil? || (index == 3)
33
+ parsed[index] = val.to_i
34
+ end
35
+ @parts = parsed
36
+ end
37
+
38
+ def to_s
39
+ "#{parts[0]}.#{parts[1]}.#{parts[2]}#{parts[3]}#{parts[4]}"
40
+ end
41
+ end
42
+
43
+ class UnityVersionComparator
44
+ include Comparable
45
+
46
+ RELEASE_LETTER_STRENGTH = { a: 1, b: 2, f: 3, p: 4 }.freeze
47
+
48
+ attr_reader :version
49
+
50
+ def <=>(other)
51
+ comp = @version.parts[0] <=> other.version.parts[0]
52
+ return comp if comp.nonzero?
53
+ comp = @version.parts[1] <=> other.version.parts[1]
54
+ return comp if comp.nonzero?
55
+ comp = @version.parts[2] <=> other.version.parts[2]
56
+ return comp if comp.nonzero?
57
+ comp = RELEASE_LETTER_STRENGTH[@version.parts[3].to_sym] <=> RELEASE_LETTER_STRENGTH[other.version.parts[3].to_sym]
58
+ return comp if comp.nonzero?
59
+ return @version.parts[4] <=> other.version.parts[4]
60
+ end
61
+
62
+ def initialize(version)
63
+ version = UnityVersionNumber.new(version.to_s)
64
+ @version = version
65
+ end
66
+
67
+ def inspect
68
+ @version
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,207 @@
1
+ ## --- BEGIN LICENSE BLOCK ---
2
+ # Copyright (c) 2016-present WeWantToKnow AS
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ # SOFTWARE.
21
+ ## --- END LICENSE BLOCK ---
22
+
23
+ require 'u3d/iniparser'
24
+ require 'u3d_core/helper'
25
+ require 'net/http'
26
+
27
+ module U3d
28
+ # Takes care of fectching versions and version list
29
+ module UnityVersions
30
+ #####################################################
31
+ # @!group URLS: Locations to fetch information from
32
+ #####################################################
33
+ # URL for the forum thread listing all the Linux releases
34
+ UNITY_LINUX_DOWNLOADS = 'https://forum.unity3d.com/threads/unity-on-linux-release-notes-and-known-issues.350256/'.freeze
35
+ # URL for the main releases for Windows and Macintosh
36
+ UNITY_DOWNLOADS = 'https://unity3d.com/get-unity/download/archive'.freeze
37
+ # URL for the patch releases for Windows and Macintosh
38
+ UNITY_PATCHES = 'https://unity3d.com/unity/qa/patch-releases'.freeze
39
+ # URL for the beta releases list, they need to be accessed after
40
+ UNITY_BETAS = 'https://unity3d.com/unity/beta/archive'.freeze
41
+ # URL for a specific beta, takes into parameter a version string (%s)
42
+ UNITY_BETA_URL = 'https://unity3d.com/unity/beta/unity%s'.freeze
43
+
44
+ #####################################################
45
+ # @!group REGEX: expressions to interpret data
46
+ #####################################################
47
+ # Captures a version and its base url
48
+ MAC_DOWNLOAD = %r{"(https?://[\w/\.-]+/[0-9a-f]{12}/)MacEditorInstaller/[a-zA-Z0-9/\.]+-(\d+\.\d+\.\d+\w\d+)\.?\w+"}
49
+ WIN_DOWNLOAD = %r{"(https?://[\w/\.-]+/[0-9a-f]{12}/)Windows..EditorInstaller/[a-zA-Z0-9/\.]+-(\d+\.\d+\.\d+\w\d+)\.?\w+"}
50
+ LINUX_DOWNLOAD = %r{"(https?://[\w/\._-]+/unity\-editor\-installer\-(\d+\.\d+\.\d+\w\d+).*\.sh)"}
51
+ # Captures a beta version in html page
52
+ UNITY_BETAVERSION_REGEX = %r{\/unity\/beta\/unity(\d+\.\d+\.\d+\w\d+)"}
53
+ UNITY_EXTRA_DOWNLOAD_REGEX = %r{"(https?:\/\/[\w\/.-]+\.unity3d\.com\/(\w+))\/[a-zA-Z\/.-]+\/download.html"}
54
+
55
+ class << self
56
+ def list_available(os: nil)
57
+ os ||= U3dCore::Helper.operating_system
58
+
59
+ case os
60
+ when :linux
61
+ return U3d::UnityVersions::LinuxVersions.list_available
62
+ when :mac
63
+ return U3d::UnityVersions::MacVersions.list_available
64
+ when :win
65
+ return U3d::UnityVersions::WindowsVersions.list_available
66
+ else
67
+ raise ArgumentError, "Operating system #{os} not supported"
68
+ end
69
+ end
70
+
71
+ def fetch_version(url, pattern)
72
+ hash = {}
73
+ data = Utils.get_ssl(url)
74
+ results = data.scan(pattern)
75
+ results.each { |capt| hash[capt[1]] = capt[0] }
76
+ return hash
77
+ end
78
+
79
+ def fetch_betas(url, pattern)
80
+ hash = {}
81
+ data = Utils.get_ssl(url)
82
+ results = data.scan(UNITY_BETAVERSION_REGEX).uniq
83
+ results.each { |beta| hash.merge!(fetch_version(UNITY_BETA_URL % beta[0], pattern)) }
84
+ hash
85
+ end
86
+ end
87
+
88
+ class LinuxVersions
89
+ class << self
90
+ def list_available
91
+ UI.message 'Loading Unity releases'
92
+ request = nil
93
+ response = nil
94
+ data = ''
95
+ uri = URI(UNITY_LINUX_DOWNLOADS)
96
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
97
+ request = Net::HTTP::Get.new uri
98
+ request['Connection'] = 'keep-alive'
99
+ response = http.request request
100
+
101
+ case response
102
+ when Net::HTTPSuccess then
103
+ # Successfully retrieved forum content
104
+ data = response.body
105
+ when Net::HTTPRedirection then
106
+ # A session must be opened with the server before accessing forum
107
+ res = nil
108
+ cookie_str = ''
109
+ # Store the name and value of the cookies returned by the server
110
+ response['set-cookie'].gsub(/\s+/, '').split(',').each do |c|
111
+ cookie_str << c.split(';', 2)[0] + '; '
112
+ end
113
+ cookie_str.chomp!('; ')
114
+
115
+ # It should be the Unity register API
116
+ uri = URI(response['location'])
117
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http_api|
118
+ request = Net::HTTP::Get.new uri
119
+ request['Connection'] = 'keep-alive'
120
+ res = http_api.request request
121
+ end
122
+
123
+ raise 'Unexpected result' unless res.is_a? Net::HTTPRedirection
124
+ # It should be a redirection to the forum to perform authentication
125
+ uri = URI(res['location'])
126
+
127
+ request = Net::HTTP::Get.new uri
128
+ request['Connection'] = 'keep-alive'
129
+ request['Cookie'] = cookie_str
130
+
131
+ res = http.request request
132
+
133
+ raise 'Unable to establish a session with Unity forum' unless res.is_a? Net::HTTPRedirection
134
+
135
+ cookie_str << '; ' + res['set-cookie'].gsub(/\s+/, '').split(';', 2)[0]
136
+
137
+ uri = URI(res['location'])
138
+
139
+ request = Net::HTTP::Get.new uri
140
+ request['Connection'] = 'keep-alive'
141
+ request['Cookie'] = cookie_str
142
+
143
+ res = http.request request
144
+
145
+ data = res.body if res.is_a? Net::HTTPSuccess
146
+ else raise "Request failed with status #{response.code}"
147
+ end
148
+ end
149
+ data.gsub(/[ \t]+/, '').each_line { |l| puts l if /<a href=/ =~ l }
150
+ versions = {}
151
+ results = data.scan(LINUX_DOWNLOAD)
152
+ results.each do |capt|
153
+ versions[capt[1]] = capt[0]
154
+ end
155
+ if versions.count.zero?
156
+ UI.important 'Found no releases'
157
+ else
158
+ UI.success "Found #{versions.count} releases."
159
+ end
160
+ versions
161
+ end
162
+ end
163
+ end
164
+
165
+ class MacVersions
166
+ class << self
167
+ def list_available
168
+ versions = {}
169
+ UI.message 'Loading Unity releases'
170
+ current = UnityVersions.fetch_version(UNITY_DOWNLOADS, MAC_DOWNLOAD)
171
+ UI.success "Found #{current.count} releases." if current.count.nonzero?
172
+ versions = versions.merge(current)
173
+ UI.message 'Loading Unity patch releases'
174
+ current = UnityVersions.fetch_version(UNITY_PATCHES, MAC_DOWNLOAD)
175
+ UI.success "Found #{current.count} patch releases." if current.count.nonzero?
176
+ versions = versions.merge(current)
177
+ UI.message 'Loading Unity beta releases'
178
+ current = UnityVersions.fetch_betas(UNITY_BETAS, MAC_DOWNLOAD)
179
+ UI.success "Found #{current.count} beta releases." if current.count.nonzero?
180
+ versions = versions.merge(current)
181
+ versions
182
+ end
183
+ end
184
+ end
185
+
186
+ class WindowsVersions
187
+ class << self
188
+ def list_available
189
+ versions = {}
190
+ UI.message 'Loading Unity releases'
191
+ current = UnityVersions.fetch_version(UNITY_DOWNLOADS, WIN_DOWNLOAD)
192
+ UI.success "Found #{current.count} releases." if current.count.nonzero?
193
+ versions = versions.merge(current)
194
+ UI.message 'Loading Unity patch releases'
195
+ current = UnityVersions.fetch_version(UNITY_PATCHES, WIN_DOWNLOAD)
196
+ UI.success "Found #{current.count} patch releases." if current.count.nonzero?
197
+ versions = versions.merge(current)
198
+ UI.message 'Loading Unity beta releases'
199
+ current = UnityVersions.fetch_betas(UNITY_BETAS, WIN_DOWNLOAD)
200
+ UI.success "Found #{current.count} beta releases." if current.count.nonzero?
201
+ versions = versions.merge(current)
202
+ versions
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end