u3d 0.9

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 (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